ShareLinks
Links that share a post on X, Bluesky, Mastodon, LinkedIn, Reddit, Hacker News and more, or by email: plain links, nothing loaded from the networks.
Demo
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/share_links.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/share_links.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/share_links.qwe
view ShareLinksDemo() = <div.demo-stack> <ShareLinks id="share-links-demo" url="https://blog.example.com/islands-resumed" title="Islands, resumed: how a page wakes up" via="quillolang"/> <ShareLinks id="share-links-demo-labels" url="https://blog.example.com/islands-resumed" title="Islands, resumed" text="Worth a read, on resuming pages:" networks={["mastodon", "bluesky", "hackernews", "whatsapp", "telegram", "email"]} mastodon="hachyderm.io" show_labels={true} variant="soft" size="sm" label="Share"/> <ShareLinks id="share-links-demo-side" url="https://blog.example.com/islands-resumed" title="Islands, resumed" label="" variant="ghost" layout="column" networks={["x", "linkedin", "email"]}/> </div>Source
ui/share_links.qwe
// ShareLinks: a row of links that share a post on X, Bluesky, Mastodon, LinkedIn,// Facebook, Reddit, Hacker News, WhatsApp, Telegram or by email: each opens that site's// own share page (in a new tab) with the post's address and title filled in. Plain// links: no script, no buttons from the networks, nothing loaded from them.//// <ShareLinks url="https://blog.example.com/islands" title="Islands, resumed"/>// <ShareLinks url={post_url} title={post.title} text="Worth a read:" via="quillolang"// networks={["mastodon", "bluesky", "hackernews", "email"]} mastodon="hachyderm.io" show_labels={true}/>//// url: the post's address, in full (a view cannot know the page's own). title: the// post's title (the subject of an email, the title on Reddit and Hacker News). text:// what a post on X, Bluesky, Mastodon, WhatsApp or Telegram says before the link (the// title). via: an X account to credit, without the @. networks: which, in order, from// "x", "bluesky", "mastodon", "linkedin", "facebook", "reddit", "hackernews", "whatsapp",// "telegram" and "email" (["x", "bluesky", "linkedin", "facebook", "reddit",// "hackernews", "email"]). mastodon: the server to share through ("mastodon.social").// label: the row's heading, and its name for screen readers ("Share this post"; "" shows// none, and the name stays). show_labels: the network's name beside each icon (false:// icons only). Screen readers hear "Share on X (opens in a new tab)" either way. size:// "sm", "md" (default) or "lg". variant: "outline" (default), "soft" or "ghost". layout:// "row" (default) or "column" (down the side of a post). id: the element's id.import ui/theme.qwe use ui_clslet share_links_css = css`.ui-bshare { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0; font: 14px/1 var(--ui-font); color: var(--ui-fg); --s: 38px; }.ui-bshare-sm { --s: 32px; } .ui-bshare-lg { --s: 46px; }.ui-bshare-label { margin: 0; color: var(--ui-muted); font-size: 13px; font-weight: 600; }.ui-bshare-list { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }.ui-bshare-list a { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: var(--s); height: var(--s); padding: 0; border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-surface); color: var(--ui-fg); text-decoration: none; transition: color .15s, background .15s, border-color .15s; }.ui-bshare-list a:hover { border-color: var(--ui-accent); color: var(--ui-accent); }.ui-bshare-list a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-bshare-list svg { flex: none; width: calc(var(--s) * .45); height: calc(var(--s) * .45); }.ui-bshare-soft .ui-bshare-list a { border-color: transparent; background: var(--ui-subtle); }.ui-bshare-soft .ui-bshare-list a:hover { background: var(--ui-accent-soft); }.ui-bshare-ghost .ui-bshare-list a { border-color: transparent; background: none; color: var(--ui-muted); }.ui-bshare-ghost .ui-bshare-list a:hover { background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-bshare-labels .ui-bshare-list a { padding: 0 14px 0 11px; font-weight: 500; }.ui-bshare-column { flex-direction: column; align-items: flex-start; }.ui-bshare-column .ui-bshare-list { flex-direction: column; }@media (prefers-reduced-motion: reduce) { .ui-bshare-list a { transition: none; } }`let share_links_default = ["x", "bluesky", "linkedin", "facebook", "reddit", "hackernews", "email"]let share_links_all = ["x", "bluesky", "mastodon", "linkedin", "facebook", "reddit", "hackernews", "whatsapp", "telegram", "email"]// The network's name.fn share_links_name(kind: str) -> str = match kind { "x" => "X" "bluesky" => "Bluesky" "mastodon" => "Mastodon" "linkedin" => "LinkedIn" "facebook" => "Facebook" "reddit" => "Reddit" "hackernews" => "Hacker News" "whatsapp" => "WhatsApp" "telegram" => "Telegram" _ => "Email" }// The address of the network's share page for a post.fn share_links_href(kind: str, url: str, title: str, text: str, via: str?, mastodon: str) -> str { let said = url_encode(`${text} ${url}`) match kind { "x" => "https://x.com/intent/post?" + url_encode({ text, url, via }) "bluesky" => "https://bsky.app/intent/compose?text=" + said "mastodon" => `https://${mastodon}/share?text=${said}` "linkedin" => "https://www.linkedin.com/sharing/share-offsite/?url=" + url_encode(url) "facebook" => "https://www.facebook.com/sharer/sharer.php?u=" + url_encode(url) "reddit" => "https://www.reddit.com/submit?" + url_encode({ url, title }) "hackernews" => "https://news.ycombinator.com/submitlink?" + url_encode({ u: url, t: title }) "whatsapp" => "https://wa.me/?text=" + said "telegram" => "https://t.me/share/url?" + url_encode({ url, text }) _ => "mailto:?" + url_encode({ subject: title, body: `${text}\n\n${url}` }) }}// The network's icon, on a 24 by 24 grid in currentColor.fn share_links_icon(kind: str) { let filled = (d: str) => <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={d}/></svg> let lined = (body) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{body}</svg> match kind { "x" => filled("M18.2 2h3.4l-7.4 8.5L23 22h-6.8l-5.3-7-6.1 7H1.4l7.9-9.1L1 2h7l4.8 6.4L18.2 2zm-1.2 18h1.9L7.1 3.9H5.1L17 20z") "bluesky" => filled("M12 10.8c-1.1-2.1-4-6-6.8-8C2.6 1 1.6 1.3.9 1.6.2 2 0 3.1 0 3.8c0 .7.4 5.6.6 6.4.8 2.7 3.7 3.6 6.4 3.3-3.9.6-7.4 2-2.8 7 5 5.2 6.9-1.1 7.8-4.3.9 3.2 2 9.3 7.7 4.3 4.3-4.3 1.2-6.4-2.7-7 2.7.3 5.6-.6 6.4-3.3.2-.8.6-5.7.6-6.4 0-.7-.2-1.8-.9-2.2-.7-.3-1.7-.6-4.3 1.2-2.8 2-5.7 5.9-6.8 8z") "mastodon" => filled("M21.3 13.6c-.3 1.5-2.6 3.1-5.2 3.4-1.4.2-2.7.3-4.1.2-2.3-.1-4.1-.5-4.1-.5v.6c.3 2.3 2.3 2.4 4.2 2.5 1.9.1 3.6-.5 3.6-.5l.1 1.7s-1.3.7-3.7.9c-1.3.1-2.9-.1-4.8-.5C3.2 20.4 2.5 16 2.4 11.6V8.1c0-4.5 3-5.8 3-5.8C6.9 1.6 9.4 1.3 12 1.3h.1c2.6 0 5.1.3 6.6 1 0 0 3 1.3 3 5.8 0 0 0 3.3-.4 5.5zm-3.1-5.2v5.5h-2.2V8.6c0-1.1-.5-1.7-1.4-1.7-1 0-1.6.7-1.6 2v2.9h-2.2V8.9c0-1.3-.5-2-1.6-2-1 0-1.4.6-1.4 1.7v5.3H5.6V8.4c0-1.1.3-2 .9-2.6.6-.7 1.4-1 2.4-1 1.1 0 2 .4 2.6 1.3l.5.9.6-.9c.6-.9 1.5-1.3 2.6-1.3 1 0 1.8.3 2.4 1 .6.6.9 1.5.9 2.6z") "linkedin" => filled("M20.4 2H3.6A1.6 1.6 0 0 0 2 3.6v16.8A1.6 1.6 0 0 0 3.6 22h16.8a1.6 1.6 0 0 0 1.6-1.6V3.6A1.6 1.6 0 0 0 20.4 2zM8 19H5V9.5h3V19zM6.5 8.2a1.7 1.7 0 1 1 0-3.4 1.7 1.7 0 0 1 0 3.4zM19 19h-3v-4.6c0-1.1 0-2.5-1.5-2.5s-1.8 1.2-1.8 2.4V19h-3V9.5h2.9v1.3h.1c.4-.8 1.4-1.5 2.8-1.5 3 0 3.5 2 3.5 4.5V19z") "facebook" => filled("M24 12a12 12 0 1 0-13.9 11.9v-8.4h-3V12h3V9.4c0-3 1.8-4.7 4.5-4.7 1.3 0 2.7.2 2.7.2v3h-1.5c-1.5 0-2 .9-2 1.9V12h3.3l-.5 3.5h-2.8v8.4A12 12 0 0 0 24 12z") "reddit" => lined([<ellipse cx="12" cy="14.5" rx="8.5" ry="6"/>, <path d="M12 8.5 13.5 3l4.5 1.2"/>, <circle cx="19.5" cy="4.5" r="1.5"/>, <circle cx="8.8" cy="13.5" r="1" fill="currentColor" stroke="none"/>, <circle cx="15.2" cy="13.5" r="1" fill="currentColor" stroke="none"/>, <path d="M9 17c1.8 1.2 4.2 1.2 6 0"/>]) "hackernews" => lined([<rect x="3" y="3" width="18" height="18" rx="3"/>, <path d="m8 7 4 6.5L16 7M12 13.5V18"/>]) "whatsapp" => lined([<path d="M3.5 20.5 5 16a8.5 8.5 0 1 1 3.3 3.2z"/>, <path d="M9.2 8.2c-.4 2.9 3.6 6.9 6.6 6.6l.9-1.5-2-1.1-1 .8a5 5 0 0 1-2.7-2.7l.8-1-1.1-2z"/>]) "telegram" => lined([<path d="M21 4 3 11l6 2.5L11 20l3.5-4.2L19 19z"/>, <path d="m9 13.5 7-5.5"/>]) _ => lined([<rect x="2.5" y="5" width="19" height="14" rx="2.5"/>, <path d="m3 7 9 6 9-6"/>]) }}view ShareLinks(url: str, title: str, text: str? = null, via: str? = null, networks: list? = null, mastodon: str = "mastodon.social", label: str = "Share this post", show_labels: bool = false, size: str = "md", variant: str = "outline", layout: str = "row", id: str? = null, class: str? = null) { style global share_links_css let said = text ?? title let name = label != "" ? label : "Share this post" <div id={id} class={ui_cls(["ui-bshare", size != "md" && `ui-bshare-${size}`, variant != "outline" && `ui-bshare-${variant}`, show_labels && "ui-bshare-labels", layout == "column" && "ui-bshare-column", class])}> {label != "" && <p.ui-bshare-label aria-hidden="true">{label}</p>} <ul.ui-bshare-list aria-label={name}> {for k in (networks ?? share_links_default) | filter((k) => contains(share_links_all, k)) => { let net = share_links_name(k) let mail = net == "Email" <li key={k}><a href={share_links_href(k, url, title, said, via, mastodon)} data-network={mail ? "email" : k} target={mail ? null : "_blank"} rel={mail ? null : "noopener noreferrer"} aria-label={mail ? "Share by email" : `Share on ${net} (opens in a new tab)`} title={show_labels ? null : (mail ? "Share by email" : `Share on ${net}`)}> {share_links_icon(k)}{show_labels && <span>{net}</span>} </a></li> }} </ul> </div>}Related
BlogrollThe blogs a writer recommends, in groups: name, a line about each, its latest post and how long ago, its feed, and the list as OPML.9 views · 8 downloadsClapButtonApplause for a post: each press adds a clap up to a limit per reader, a +3 rises for presses in a row, and a small × takes them back.8 views · 9 downloadsAuthorBioThe box at the end of a post about its author: picture or initials, name and role, a short bio, links elsewhere, their post count and a follow link.8 views · 9 downloadsFeedSubscribeFollow this blog: its RSS, Atom or JSON feeds with their addresses to copy, links into feed readers, what a feed is, and an email box.8 views · 7 downloadsPostArchiveA blog's posts by year and month, newest first: each year a disclosure with its count, its months and their posts, or only the months as links.10 views · 8 downloadsPostHeaderThe top of a post: category, title and standfirst, the authors with their pictures, published and updated dates, reading time, tags and a cover.8 views · 6 downloads
Thread
Thread 0 posts
On ShareLinks: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.