castopod/app/Helpers/components_helper.php

485 lines
16 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
use App\Entities\Category;
use App\Entities\Location;
use CodeIgniter\I18n\Time;
use CodeIgniter\View\Table;
// ------------------------------------------------------------------------
if (! function_exists('hint_tooltip')) {
/**
* Hint component
*
* Used to produce tooltip with a question mark icon for hint texts
*
* @param string $hintText The hint text
*/
function hint_tooltip(string $hintText = '', string $class = ''): string
{
$tooltip =
'<span data-tooltip="bottom" tabindex="0" title="' .
$hintText .
'" class="inline-block align-middle opacity-75 focus:ring-accent';
if ($class !== '') {
$tooltip .= ' ' . $class;
}
return $tooltip . '">' . icon('question') . '</span>';
}
}
// ------------------------------------------------------------------------
if (! function_exists('data_table')) {
/**
* Data table component
*
* Creates a stylized table.
*
* @param array<array<string, mixed>> $columns array of associate arrays with `header` and `cell` keys where `cell` is a function with a row of $data as parameter
* @param mixed[] $data data to loop through and display in rows
* @param mixed ...$rest Any other argument to pass to the `cell` function
*/
function data_table(array $columns, array $data = [], string $class = '', mixed ...$rest): string
{
$table = new Table();
$template = [
'table_open' => '<table class="w-full whitespace-nowrap">',
'thead_open' =>
'<thead class="text-xs font-semibold text-left uppercase text-skin-muted">',
'heading_cell_start' => '<th class="px-4 py-2">',
'cell_start' => '<td class="px-4 py-2">',
'cell_alt_start' => '<td class="px-4 py-2">',
'row_start' => '<tr class="border-t border-subtle hover:bg-base">',
'row_alt_start' => '<tr class="border-t border-subtle hover:bg-base">',
];
$table->setTemplate($template);
$tableHeaders = [];
foreach ($columns as $column) {
$tableHeaders[] = $column['header'];
}
$table->setHeading($tableHeaders);
if (($dataCount = count($data)) !== 0) {
for ($i = 0; $i < $dataCount; ++$i) {
$row = $data[$i];
$rowData = [];
foreach ($columns as $column) {
$rowData[] = $column['cell']($row, ...$rest);
}
$table->addRow($rowData);
}
} else {
$table->addRow([
[
'colspan' => count($tableHeaders),
'class' => 'px-4 py-2 italic font-semibold text-center',
'data' => lang('Common.no_data'),
],
]);
}
return '<div class="overflow-x-auto rounded-lg bg-elevated border-3 border-subtle ' . $class . '" >' .
$table->generate() .
'</div>';
}
}
// ------------------------------------------------------------------------
if (! function_exists('publication_pill')) {
/**
* Publication pill component
*
* Shows the stylized publication datetime in regards to current datetime.
*/
function publication_pill(?Time $publicationDate, string $publicationStatus, string $customClass = ''): string
{
$class = match ($publicationStatus) {
'published' => 'text-pine-500 border-pine-500 bg-pine-50',
'scheduled' => 'text-red-600 border-red-600 bg-red-50',
'with_podcast' => 'text-blue-600 border-blue-600 bg-blue-50',
'not_published' => 'text-gray-600 border-gray-600 bg-gray-50',
default => 'text-gray-600 border-gray-600 bg-gray-50',
};
$title = match ($publicationStatus) {
'published', 'scheduled' => (string) $publicationDate,
'with_podcast' => lang('Episode.with_podcast_hint'),
'not_published' => '',
default => '',
};
$label = lang('Episode.publication_status.' . $publicationStatus);
return '<span ' . ($title === '' ? '' : 'title="' . $title . '"') . ' class="flex items-center px-1 font-semibold border rounded w-max ' .
$class .
' ' .
$customClass .
'">' .
$label .
($publicationStatus === 'with_podcast' ? '<Icon glyph="warning" class="flex-shrink-0 ml-1 text-lg" />' : '') .
'</span>';
}
}
// ------------------------------------------------------------------------
if (! function_exists('publication_button')) {
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
/**
* Publication button component for episodes
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
*
* Displays the appropriate publication button depending on the publication post.
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
*/
function publication_button(int $podcastId, int $episodeId, string $publicationStatus): string
{
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
switch ($publicationStatus) {
case 'not_published':
$label = lang('Episode.publish');
$route = route_to('episode-publish', $podcastId, $episodeId);
$variant = 'primary';
$iconLeft = 'upload-cloud';
break;
case 'with_podcast':
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
case 'scheduled':
$label = lang('Episode.publish_edit');
$route = route_to('episode-publish_edit', $podcastId, $episodeId);
$variant = 'warning';
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
$iconLeft = 'upload-cloud';
break;
case 'published':
$label = lang('Episode.unpublish');
$route = route_to('episode-unpublish', $podcastId, $episodeId);
$variant = 'danger';
$iconLeft = 'cloud-off';
break;
default:
$label = '';
$route = '';
$variant = '';
$iconLeft = '';
break;
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
}
return <<<HTML
<Button variant="{$variant}" uri="{$route}" iconLeft="{$iconLeft}" >{$label}</Button>
HTML;
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
}
}
feat(fediverse): implement activitypub protocols + update user interface - add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 19:20:02 +02:00
// ------------------------------------------------------------------------
if (! function_exists('publication_status_banner')) {
/**
* Publication status banner component for podcasts
*
* Displays the appropriate banner depending on the podcast's publication status.
*/
function publication_status_banner(?Time $publicationDate, int $podcastId, string $publicationStatus): string
{
switch ($publicationStatus) {
case 'not_published':
$bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode');
$bannerText = lang('Podcast.publication_status_banner.not_published');
$linkRoute = route_to('podcast-publish', $podcastId);
$linkLabel = lang('Podcast.publish');
break;
case 'scheduled':
$bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode');
$bannerText = lang('Podcast.publication_status_banner.scheduled', [
'publication_date' => local_datetime($publicationDate),
], null, false);
$linkRoute = route_to('podcast-publish_edit', $podcastId);
$linkLabel = lang('Podcast.publish_edit');
break;
default:
$bannerDisclaimer = '';
$bannerText = '';
$linkRoute = '';
$linkLabel = '';
break;
}
return <<<HTML
<div class="flex items-center px-12 py-1 border-b bg-stripes-gray border-subtle" role="alert">
<p class="text-gray-900">
<span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span>
<span class="ml-3 text-sm">{$bannerText}</span>
</p>
<a href="{$linkRoute}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$linkLabel}</a>
</div>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('episode_numbering')) {
/**
* Returns relevant translated episode numbering.
*
* @param bool $isAbbr component will show abbreviated numbering if true
*/
function episode_numbering(
?int $episodeNumber = null,
?int $seasonNumber = null,
string $class = '',
bool $isAbbr = false
): string {
if (! $episodeNumber && ! $seasonNumber) {
return '';
}
$transKey = '';
$args = [];
if ($episodeNumber !== null) {
$args['episodeNumber'] = sprintf('%02d', $episodeNumber);
}
if ($seasonNumber !== null) {
$args['seasonNumber'] = sprintf('%02d', $seasonNumber);
}
if ($episodeNumber !== null && $seasonNumber !== null) {
$transKey = 'Episode.season_episode';
} elseif ($episodeNumber !== null && $seasonNumber === null) {
$transKey = 'Episode.number';
} elseif ($episodeNumber === null && $seasonNumber !== null) {
$transKey = 'Episode.season';
}
if ($isAbbr) {
return '<abbr class="tracking-wider ' .
$class .
'" title="' .
lang($transKey, $args) .
'" data-tooltip="bottom">' .
lang($transKey . '_abbr', $args) .
'</abbr>';
}
return '<span class="' .
$class .
'">' .
lang($transKey, $args) .
'</span>';
}
}
// ------------------------------------------------------------------------
if (! function_exists('location_link')) {
2020-12-23 15:11:38 +01:00
/**
* Returns link to display from location info
*/
function location_link(?Location $location, string $class = ''): string
{
if (! $location instanceof Location) {
return '';
2020-12-23 15:11:38 +01:00
}
return anchor(
$location->url,
icon('map-pin', 'mr-2 flex-shrink-0') . '<span class="truncate">' . esc($location->name) . '</span>',
[
'class' =>
'w-full overflow-hidden inline-flex items-baseline hover:underline focus:ring-accent' .
($class === '' ? '' : " {$class}"),
'target' => '_blank',
'rel' => 'noreferrer noopener',
],
);
2020-12-23 15:11:38 +01:00
}
}
// ------------------------------------------------------------------------
if (! function_exists('audio_player')) {
/**
* Returns audio player
*/
function audio_player(string $source, string $mediaType, string $class = ''): string
{
$language = service('request')
->getLocale();
return <<<HTML
<vm-player
id="castopod-vm-player"
theme="light"
language="{$language}"
icons="castopod-icons"
class="{$class} relative z-0"
style="--vm-player-box-shadow:0; --vm-player-theme: hsl(var(--color-accent-base)); --vm-control-focus-color: hsl(var(--color-accent-contrast)); --vm-control-spacing: 4px; --vm-menu-item-focus-bg: hsl(var(--color-background-highlight));"
>
<vm-audio preload="none">
<source src="{$source}" type="{$mediaType}" />
</vm-audio>
<vm-ui>
<vm-icon-library name="castopod-icons"></vm-icon-library>
<vm-controls full-width>
<vm-playback-control></vm-playback-control>
<vm-volume-control></vm-volume-control>
<vm-current-time></vm-current-time>
<vm-scrubber-control></vm-scrubber-control>
<vm-end-time></vm-end-time>
<vm-settings-control></vm-settings-control>
<vm-default-settings></vm-default-settings>
</vm-controls>
</vm-ui>
</vm-player>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('relative_time')) {
function relative_time(Time $time, string $class = ''): string
{
$formatter = new IntlDateFormatter(service(
'request'
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
$translatedDate = $time->toLocalizedString($formatter->getPattern());
$datetime = $time->format(DateTime::ATOM);
return <<<HTML
<relative-time tense="past" class="{$class}" datetime="{$datetime}">
<time
datetime="{$datetime}"
title="{$time}">{$translatedDate}</time>
</relative-time>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('local_datetime')) {
function local_datetime(Time $time): string
{
$formatter = new IntlDateFormatter(service(
'request'
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG);
$translatedDate = $time->toLocalizedString($formatter->getPattern());
$datetime = $time->format(DateTime::ISO8601);
return <<<HTML
<relative-time datetime="{$datetime}"
prefix=""
threshold="PT0S"
weekday="long"
day="numeric"
month="long"
year="numeric"
hour="numeric"
minute="numeric">
<time
datetime="{$datetime}"
title="{$time}">{$translatedDate}</time>
</relative-time>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('local_date')) {
function local_date(Time $time): string
{
$formatter = new IntlDateFormatter(service(
'request'
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
$translatedDate = $time->toLocalizedString($formatter->getPattern());
return <<<HTML
<time title="{$time}">{$translatedDate}</time>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('explicit_badge')) {
function explicit_badge(bool $isExplicit, string $class = ''): string
{
if (! $isExplicit) {
return '';
}
$explicitLabel = lang('Common.explicit');
return <<<HTML
<span class="px-1 text-xs font-semibold leading-tight tracking-wider uppercase border md:border-white/50 {$class}">{$explicitLabel}</span>
HTML;
}
}
// ------------------------------------------------------------------------
if (! function_exists('category_label')) {
function category_label(Category $category): string
{
$categoryLabel = '';
if ($category->parent_id !== null) {
$categoryLabel .= lang('Podcast.category_options.' . $category->parent->code, [], null, false) . ' ';
}
return $categoryLabel . lang('Podcast.category_options.' . $category->code, [], null, false);
}
}
// ------------------------------------------------------------------------
if (! function_exists('downloads_abbr')) {
function downloads_abbr(int $downloads): string
{
if ($downloads < 1000) {
return (string) $downloads;
}
$option = match (true) {
$downloads < 1_000_000 => [
'divider' => 1_000,
'suffix' => 'K',
],
$downloads < 1_000_000_000 => [
'divider' => 1_000_000,
'suffix' => 'M',
],
default => [
'divider' => 1_000_000_000,
'suffix' => 'B',
],
};
$formatter = new NumberFormatter(service('request')->getLocale(), NumberFormatter::DECIMAL);
$formatter->setPattern('#,##0.##');
$abbr = $formatter->format($downloads / $option['divider']) . $option['suffix'];
return <<<HTML
<abbr title="{$downloads}">{$abbr}</abbr>
HTML;
}
}