hypnagaga/src/components/Byline/Byline.svelte
2025-05-13 15:50:34 +01:00

188 lines
5 KiB
Svelte

<!-- @component `Byline` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-byline--docs) -->
<script lang="ts">
import { getAuthorPageUrl } from '../../utils';
import Block from '../Block/Block.svelte';
import { apdate } from 'journalize';
import type { Snippet } from 'svelte';
interface Props {
/**
* Array of author names, which will be slugified to create links to Reuters author pages
*/
authors?: string[];
/**
* Publish time as a datetime string.
*/
publishTime: string;
/**
* Update time as a datetime string.
*/
updateTime?: string;
/**
* Alignment of the byline.
*/
align?: 'auto' | 'center';
/**
* Add an id to to target with custom CSS.
* @type {string}
*/
id?: string;
/**
* Add extra classes to target with custom CSS.
* @type {string}
*/
cls?: string;
/**
* Custom function that returns an author page URL.
*/
getAuthorPage?: (author: string) => string;
/**
* Optional snippet for a custom byline.
*/
byline?: Snippet;
/**
* Optional snippet for a custom published dateline.
*/
published?: Snippet;
/**
* Optional snippet for a custom updated dateline.
*/
updated?: Snippet;
}
let {
authors = [],
publishTime,
updateTime,
align = 'auto',
id = '',
cls = '',
getAuthorPage = getAuthorPageUrl,
byline,
published,
updated,
}: Props = $props();
let alignmentClass = $derived(align === 'center' ? 'text-center' : '');
/**
/* Date validation and formatter functions
*/
const isValidDate = (datetime: string) => {
if (!datetime) return false;
if (!Date.parse(datetime)) return false;
return true;
};
const formatTime = (datetime: string) =>
new Date(datetime).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
});
const areSameDay = (first: Date, second: Date) =>
first.getFullYear() === second.getFullYear() &&
first.getMonth() === second.getMonth() &&
first.getDate() === second.getDate();
</script>
<Block {id} class="byline-container {alignmentClass} {cls}" width="normal">
<aside class="article-metadata font-subhed">
<div class="byline body-caption fmb-1">
{#if byline}
<!-- Custom byline -->
{@render byline()}
{:else}
By
{#if authors.length > 0}
{#each authors as author, i}
<a
class="no-underline whitespace-nowrap text-primary font-bold"
href={getAuthorPage(author)}
rel="author"
>
{author.trim()}</a
>{#if authors.length > 1 && i < authors.length - 2},{/if}
{#if authors.length > 1 && i === authors.length - 2}and&nbsp;{/if}
{/each}
{:else}
<a
href="https://www.reuters.com"
class="no-underline whitespace-nowrap text-primary font-bold"
>Reuters</a
>
{/if}
{/if}
</div>
<div class="dateline body-caption fmt-0">
{#if published}
<div class="whitespace-nowrap inline-block">
<!-- Custom published dateline snippet -->
<time datetime={publishTime}>
{@render published()}
</time>
</div>
{:else if isValidDate(publishTime)}
<div class="whitespace-nowrap inline-block">
Published
<time datetime={publishTime}>
{#if updateTime && isValidDate(updateTime)}
{apdate(new Date(publishTime))}
{:else}
{apdate(new Date(publishTime))}&nbsp;&nbsp;{formatTime(
publishTime
)}
{/if}
</time>
</div>
{/if}
{#if updated}
<div class="whitespace-nowrap inline-block">
<!-- Custom updated dateline snippet -->
<time datetime={updateTime}>
{@render updated()}
</time>
</div>
{:else if isValidDate(publishTime) && isValidDate(updateTime || '')}
<div class="whitespace-nowrap inline-block">
Last updated
<time datetime={updateTime}>
{#if areSameDay(new Date(publishTime), new Date(updateTime || new Date()))}
{formatTime(updateTime || '')}
{:else}
{apdate(
new Date(updateTime || new Date())
)}&nbsp;&nbsp;{formatTime(updateTime || '')}
{/if}
</time>
</div>
{/if}
</div>
</aside>
</Block>
<style lang="scss">
@use '../../scss/mixins' as *;
.byline {
a {
&:hover {
text-decoration-line: underline;
}
}
}
@media (min-width: $column-width-narrower) {
.dateline {
div {
&:not(:last-child) {
&:after {
content: '·';
margin: 0 2px 0 5px;
}
}
}
}
}
</style>