List shared links (#15)

* Exclude not published items in directus client

* Add link view to page controller

* Add page controller links list view to router

* Add links list view CSS

* Add permalink view for link

* Improve page titles

* Let user copy link permalink to clipboard

if alt, shift or ctrl are pressed, fallback to
default behavior.
This commit is contained in:
Anders Englöf Ytterström 2024-10-03 14:38:56 +02:00 committed by GitHub
parent fae04e3fd7
commit 89b93cf231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 200 additions and 49 deletions

View file

@ -118,7 +118,14 @@ main {
font-size: large; font-size: large;
box-sizing: border-box; box-sizing: border-box;
> pre { > footer {
font-style: italic;
text-align: right;
font-size: 0.8em;
}
}
pre {
margin: 2em 0; margin: 2em 0;
background-color: #022; background-color: #022;
overflow-y: auto; overflow-y: auto;
@ -133,13 +140,6 @@ main {
top: 0.25em; top: 0.25em;
right: 0.25em; right: 0.25em;
} }
}
> footer {
font-style: italic;
text-align: right;
font-size: 0.8em;
}
} }
code { code {
@ -193,3 +193,9 @@ section > h2 {
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
} }
} }
.links {
> :is(h2, h3) {
margin-top: 3em;
}
}

View file

@ -44,6 +44,10 @@
import "../app.css"; import "../app.css";
import copyToClipboard from "./copy-to-clipboard.js"; import {
copyCodeToClipboard,
copyUrlToClipboard,
} from "./copy-to-clipboard.js";
copyToClipboard(); copyCodeToClipboard();
copyUrlToClipboard();

View file

@ -1,14 +1,30 @@
export default () => { export const copyCodeToClipboard = () => {
const codeblocks = document.querySelectorAll("pre>code"); const codeblocks = document.querySelectorAll("pre>code");
for (const b of codeblocks) { for (const b of codeblocks) {
const button = document.createElement("button"); const button = document.createElement("button");
button.innerHTML = "Kopiera"; button.innerHTML = "Kopiera";
button.addEventListener("click", function ({target}) { button.addEventListener("click", function ({ target }) {
const text = target.previousSibling.innerHTML; const text = target.previousSibling.innerHTML;
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
}) });
b.parentNode.appendChild(button); b.parentNode.appendChild(button);
} }
} };
export const copyUrlToClipboard = () => {
const permalinks = document.querySelectorAll(".permalink");
for (const pl of permalinks) {
pl.setAttribute(
"title",
pl.getAttribute("title") + ", klicka för att kopiera",
);
pl.addEventListener("click", function (evt) {
const { target, shiftKey, ctrlKey, altKey } = evt;
if (!shiftKey && !ctrlKey && !altKey) {
evt.preventDefault();
const text = target.href;
navigator.clipboard.writeText(text);
}
});
}
};

View file

@ -1,4 +1,6 @@
defmodule Mse25.Directus do defmodule Mse25.Directus do
@draft_filter "filter[status][_eq]=published"
def get_article(slug) do def get_article(slug) do
get_item(:articles, slug) get_item(:articles, slug)
end end
@ -173,6 +175,12 @@ defmodule Mse25.Directus do
[base_url: base_url, token: token] = Application.fetch_env!(:mse25, :directus) [base_url: base_url, token: token] = Application.fetch_env!(:mse25, :directus)
req = Req.new(base_url: base_url <> "/items") req = Req.new(base_url: base_url <> "/items")
resource =
case String.contains?(resource, "?") do
true -> resource <> "&" <> @draft_filter
false -> resource <> "?" <> @draft_filter
end
case Req.get!(req, url: resource, auth: {:bearer, token}) case Req.get!(req, url: resource, auth: {:bearer, token})
|> payload do |> payload do
{:ok, payload} -> payload {:ok, payload} -> payload

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="csrf-token" content={get_csrf_token()} /> <meta name="csrf-token" content={get_csrf_token()} />
<title><%= assigns[:page_title] || "Anders Englöf Ytterström" %></title> <title><%= assigns[:page_title] || "Anders Englöf Ytterström" %> | madr.se</title>
<link rel="stylesheet" href={~p"/assets/app.css"} /> <link rel="stylesheet" href={~p"/assets/app.css"} />
</head> </head>
<body class="bg-white"> <body class="bg-white">

View file

@ -50,17 +50,16 @@ defmodule Mse25Web.ItemController do
"pubDate" => published_at, "pubDate" => published_at,
"date_updated" => updated_at "date_updated" => updated_at
}) do }) do
updated =
case updated_at do
nil -> published_at
s -> String.slice(s, 0..9)
end
[ [
page_title: heading,
heading: heading, heading: heading,
contents: Earmark.as_html!(contents), contents: Earmark.as_html!(contents),
published_at: published_at, published_at: published_at,
updated_at: updated, updated_at:
case updated_at do
nil -> published_at
ua -> String.slice(ua, 0..9)
end,
year: String.slice(published_at, 0..3) year: String.slice(published_at, 0..3)
] ]
end end
@ -72,11 +71,12 @@ defmodule Mse25Web.ItemController do
"lead" => lead "lead" => lead
}) do }) do
[ [
page_title: heading,
heading: heading, heading: heading,
contents: Earmark.as_html!(contents), contents: Earmark.as_html!(contents),
published_at: published_at, published_at: published_at,
lead: lead, lead: lead,
year: 2024 year: String.slice(published_at, 0..3)
] ]
end end
@ -84,15 +84,23 @@ defmodule Mse25Web.ItemController do
"title" => heading, "title" => heading,
"contents" => contents, "contents" => contents,
"pubDate" => published_at, "pubDate" => published_at,
"date_updated" => updated_at,
"source" => url, "source" => url,
"h1" => title "h1" => title
}) do }) do
[ [
page_title: heading,
heading: heading, heading: heading,
contents: Earmark.as_html!(contents), contents: Earmark.as_html!(contents),
published_at: published_at, published_at: published_at,
url: url, url: url,
title: title title: title,
year: String.slice(published_at, 0..3),
updated_at:
case updated_at do
nil -> published_at
ua -> String.slice(ua, 0..9)
end
] ]
end end

View file

@ -1,14 +1,35 @@
<article> <article class="article">
<header> <header>
<date><%= @published_at %></date> <ol class="breadcrumbs" itemscope itemtype="https://schema.org/BreadcrumbList">
<h1><%= @heading %></h1> <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
</header> <a href="/" rel="home">
<span itemprop="name">madr.se</span>
</a>
<meta itemprop="position" content="1" />
</li>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a href="/delningar">
<span itemprop="name">Delningar</span>
</a>
<meta itemprop="position" content="2" />
</li>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a href={"/" <> @year}>
<span itemprop="name"><%= @year %></span>
</a>
<meta itemprop="position" content="3" />
</li>
</ol>
<h1><%= @heading %></h1>
</header>
<%= raw @contents %> <%= raw(@contents) %>
<p> <p>
Källa: <a href={@url} rel="external"><%= @title %></a> Källa: <a href={@url} rel="external"><%= @title %></a>
</p> </p>
<footer> <footer>
<p>Skribent: Anders Englöf Ytterström. Publicerad <%= @published_at %>.</p> <p>
</footer> Publicerad <%= @published_at %> <br />och senast uppdaterad <%= @updated_at %>
</p>
</footer>
</article> </article>

View file

@ -10,7 +10,7 @@ defmodule Mse25Web.PageController do
brutal_legends = Directus.get_albums!(limit: 1) brutal_legends = Directus.get_albums!(limit: 1)
render(conn, :home, render(conn, :home,
page_title: "Anders Englöf Ytterström @ madr.se", page_title: "Anders Englöf Ytterström",
layout: false, layout: false,
recent_article: most_recent_article, recent_article: most_recent_article,
older_article: older_article, older_article: older_article,
@ -29,10 +29,26 @@ defmodule Mse25Web.PageController do
) )
end end
def links(conn, _params) do
links = Directus.get_links!(limit: 9999) |> group_by_date
render(conn, :links,
page_title: "Delningar",
links: links
)
end
defp group_annually(items) do defp group_annually(items) do
items items
|> Enum.group_by(fn %{"slug" => slug} -> String.slice(slug, 0..3) end) |> Enum.group_by(fn %{"slug" => slug} -> String.slice(slug, 0..3) end)
|> Map.to_list() |> Map.to_list()
|> Enum.sort(fn {a, _a}, {b, _b} -> b < a end) |> Enum.sort(fn {a, _a}, {b, _b} -> b < a end)
end end
defp group_by_date(items) do
items
|> Enum.group_by(fn %{"pubDate" => pub_date} -> pub_date end)
|> Map.to_list()
|> Enum.sort(fn {a, _a}, {b, _b} -> b < a end)
end
end end

View file

@ -0,0 +1,73 @@
<header>
<ol class="breadcrumbs" itemscope itemtype="https://schema.org/BreadcrumbList">
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a href="/" rel="home">
<span itemprop="name">madr.se</span>
</a>
<meta itemprop="position" content="1" />
</li>
</ol>
<h1>Delningar</h1>
<p>
Länkar som är värda att uppmärksammas och lämna åsikt om.
</p>
<%= for {date, links} <- @links do %>
<section id={"d" <> date}>
<div class="links">
<h2>
<%= date
|> Date.from_iso8601!()
|> Calendar.strftime(
"%A, %d %B %Y",
month_names: fn m ->
Enum.at(
[
"januari",
"februari",
"mars",
"april",
"maj",
"juni",
"juli",
"augusti",
"september",
"oktober",
"november",
"december"
],
m - 1
)
end,
day_of_week_names: fn d ->
Enum.at(
[
"måndag",
"tisdag",
"onsdag",
"torsdag",
"fredag",
"lördag",
"söndag"
],
d - 1
)
end
)
|> String.replace(~r/ 0/, " ") %>
</h2>
<%= for link <- links do %>
<article>
<h3>
<%= link["title"] %>
<a class="permalink" href={"/" <> link["slug"]} title="Permalänk">#</a>
</h3>
<%= link["contents"] |> Earmark.as_html!() |> raw %>
<div class="source">
Källa: <a href={link["source"]} rel="external"><%= link["h1"] %></a>
</div>
</article>
<% end %>
</div>
</section>
<% end %>
</header>

View file

@ -24,8 +24,7 @@ defmodule Mse25Web.Router do
# get "/event-map.js", EventController, :interactive_map # get "/event-map.js", EventController, :interactive_map
get "/webblogg", PageController, :articles get "/webblogg", PageController, :articles
get "/delningar", PageController, :links
# get "/delningar", ShareController, :index
# get "/:year", TimelineController, :annual # get "/:year", TimelineController, :annual
# get "/prenumerera.xml", TimelineController, :feed # get "/prenumerera.xml", TimelineController, :feed