fix(episodes): add publication status + set publication date to null when none has been set

- replace $is_published attribute by $publication_status to better handle episode's publication
state
- update publication date datepicker to include a clear button

fixes #70
This commit is contained in:
Yassine Doghri 2020-12-09 17:52:30 +00:00
parent 5dc0f19656
commit d882981b3a
14 changed files with 141 additions and 99 deletions

View File

@ -117,6 +117,7 @@ class Episode extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$publicationDate = $this->request->getPost('publication_date');
$newEpisode = new \App\Entities\Episode([ $newEpisode = new \App\Entities\Episode([
'podcast_id' => $this->podcast->id, 'podcast_id' => $this->podcast->id,
'title' => $this->request->getPost('title'), 'title' => $this->request->getPost('title'),
@ -141,11 +142,13 @@ class Episode extends BaseController
'is_blocked' => $this->request->getPost('block') == 'yes', 'is_blocked' => $this->request->getPost('block') == 'yes',
'created_by' => user(), 'created_by' => user(),
'updated_by' => user(), 'updated_by' => user(),
'published_at' => Time::createFromFormat( 'published_at' => $publicationDate
? Time::createFromFormat(
'Y-m-d H:i', 'Y-m-d H:i',
$this->request->getPost('publication_date'), $publicationDate,
$this->request->getPost('client_timezone') $this->request->getPost('client_timezone')
)->setTimezone('UTC'), )->setTimezone('UTC')
: null,
]); ]);
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
@ -231,11 +234,16 @@ class Episode extends BaseController
: null; : null;
$this->episode->type = $this->request->getPost('type'); $this->episode->type = $this->request->getPost('type');
$this->episode->is_blocked = $this->request->getPost('block') == 'yes'; $this->episode->is_blocked = $this->request->getPost('block') == 'yes';
$this->episode->published_at = Time::createFromFormat(
$publicationDate = $this->request->getPost('publication_date');
$this->episode->published_at = $publicationDate
? Time::createFromFormat(
'Y-m-d H:i', 'Y-m-d H:i',
$this->request->getPost('publication_date'), $publicationDate,
$this->request->getPost('client_timezone') $this->request->getPost('client_timezone')
)->setTimezone('UTC'); )->setTimezone('UTC')
: null;
$this->episode->updated_by = user(); $this->episode->updated_by = user();
$enclosure = $this->request->getFile('enclosure'); $enclosure = $this->request->getFile('enclosure');

View File

@ -89,9 +89,9 @@ class Episode extends Entity
protected $description; protected $description;
/** /**
* @var boolean * @var string
*/ */
protected $is_published; protected $publication_status;
protected $dates = [ protected $dates = [
'published_at', 'published_at',
@ -462,16 +462,21 @@ class Episode extends Entity
return $this; return $this;
} }
public function getIsPublished() public function getPublicationStatus()
{ {
if ($this->is_published) { if ($this->publication_status) {
return $this->is_published; return $this->publication_status;
}
if (!$this->published_at) {
return 'not_published';
} }
helper('date'); helper('date');
if ($this->published_at->isBefore(Time::now())) {
return 'published';
}
$this->is_published = $this->published_at->isBefore(Time::now()); return 'scheduled';
return $this->is_published;
} }
} }

View File

@ -271,27 +271,30 @@ if (!function_exists('publication_pill')) {
*/ */
function publication_pill( function publication_pill(
$publicationDate, $publicationDate,
$isPublished, $publicationStatus,
$customClass = '' $customClass = ''
): string { ): string {
$class = $isPublished $class =
$publicationStatus === 'published'
? 'text-green-500 border-green-500' ? 'text-green-500 border-green-500'
: 'text-orange-600 border-orange-600'; : 'text-orange-600 border-orange-600';
$label = lang( $transParam = [];
$isPublished ? 'Episode.published' : 'Episode.scheduled', if ($publicationDate) {
[ $transParam = [
'<time '<time pubdate datetime="' .
pubdate
datetime="' .
$publicationDate->format(DateTime::ATOM) . $publicationDate->format(DateTime::ATOM) .
'" '" title="' .
title="' .
$publicationDate . $publicationDate .
'">' . '">' .
lang('Common.mediumDate', [$publicationDate]) . lang('Common.mediumDate', [$publicationDate]) .
'</time>', '</time>',
] ];
}
$label = lang(
'Episode.publication_status.' . $publicationStatus,
$transParam
); );
return '<span class="px-1 border ' . return '<span class="px-1 border ' .

View File

@ -23,8 +23,11 @@ return [
'delete' => 'Delete', 'delete' => 'Delete',
'go_to_page' => 'Go to page', 'go_to_page' => 'Go to page',
'create' => 'Add an episode', 'create' => 'Add an episode',
'publication_status' => [
'published' => 'Published on {0}', 'published' => 'Published on {0}',
'scheduled' => 'Scheduled for {0}', 'scheduled' => 'Scheduled for {0}',
'not_published' => 'Not published',
],
'form' => [ 'form' => [
'enclosure' => 'Audio file', 'enclosure' => 'Audio file',
'enclosure_hint' => 'Choose an .mp3 or .m4a audio file.', 'enclosure_hint' => 'Choose an .mp3 or .m4a audio file.',
@ -58,6 +61,7 @@ return [
'publication_section_title' => 'Publication info', 'publication_section_title' => 'Publication info',
'publication_section_subtitle' => '', 'publication_section_subtitle' => '',
'publication_date' => 'Publication date', 'publication_date' => 'Publication date',
'publication_date_clear' => 'Clear publication date',
'publication_date_hint' => 'publication_date_hint' =>
'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm',
'parental_advisory' => [ 'parental_advisory' => [

View File

@ -23,8 +23,11 @@ return [
'delete' => 'Supprimer', 'delete' => 'Supprimer',
'go_to_page' => 'Voir', 'go_to_page' => 'Voir',
'create' => 'Ajouter un épisode', 'create' => 'Ajouter un épisode',
'publication_status' => [
'published' => 'Publié le {0}', 'published' => 'Publié le {0}',
'scheduled' => 'Planifié pour le {0}', 'scheduled' => 'Planifié pour le {0}',
'not_published' => 'Non publié',
],
'form' => [ 'form' => [
'enclosure' => 'Fichier audio', 'enclosure' => 'Fichier audio',
'enclosure_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.', 'enclosure_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.',
@ -58,6 +61,7 @@ return [
'publication_section_title' => 'Information de publication', 'publication_section_title' => 'Information de publication',
'publication_section_subtitle' => '', 'publication_section_subtitle' => '',
'publication_date' => 'Date de publication', 'publication_date' => 'Date de publication',
'publication_date_clear' => 'Effacer la date de publication',
'publication_date_hint' => 'publication_date_hint' =>
'Vous pouvez planifier la sortie de lépisode en saisissant une date de publication future. Ce champ doit être au format YYYY-MM-DD HH:mm', 'Vous pouvez planifier la sortie de lépisode en saisissant une date de publication future. Ce champ doit être au format YYYY-MM-DD HH:mm',
'parental_advisory' => [ 'parental_advisory' => [

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 280 B

View File

@ -11,20 +11,25 @@ const isBrowserLocale24h = () =>
.match(/AM/); .match(/AM/);
const DateTimePicker = (): void => { const DateTimePicker = (): void => {
const dateTimeContainers: NodeListOf<HTMLInputElement> = document.querySelectorAll( const dateTimeContainers: NodeListOf<HTMLDivElement> = document.querySelectorAll(
"input[data-picker='datetime']" "div[data-picker='datetime']"
); );
for (let i = 0; i < dateTimeContainers.length; i++) { for (let i = 0; i < dateTimeContainers.length; i++) {
const dateTimeContainer = dateTimeContainers[i]; const dateTimeContainer = dateTimeContainers[i];
const dateTimeInput: HTMLInputElement | null = dateTimeContainer.querySelector(
"input[data-input]"
);
if (dateTimeInput) {
const flatpickrInstance = flatpickr(dateTimeContainer, { const flatpickrInstance = flatpickr(dateTimeContainer, {
enableTime: true, enableTime: true,
time_24hr: isBrowserLocale24h(), time_24hr: isBrowserLocale24h(),
wrap: true,
}); });
// convert container UTC date value to user timezone // convert container UTC date value to user timezone
const dateTime = new Date(dateTimeContainer.value); const dateTime = new Date(dateTimeInput.value);
const dateUTC = Date.UTC( const dateUTC = Date.UTC(
dateTime.getFullYear(), dateTime.getFullYear(),
dateTime.getMonth(), dateTime.getMonth(),
@ -36,6 +41,7 @@ const DateTimePicker = (): void => {
// set converted date as field value // set converted date as field value
flatpickrInstance.setDate(new Date(dateUTC)); flatpickrInstance.setDate(new Date(dateUTC));
} }
}
}; };
export default DateTimePicker; export default DateTimePicker;

View File

@ -3,8 +3,6 @@ const Time = (): void => {
"time" "time"
); );
console.log(timeElements);
for (let i = 0; i < timeElements.length; i++) { for (let i = 0; i < timeElements.length; i++) {
const timeElement = timeElements[i]; const timeElement = timeElements[i];

View File

@ -199,13 +199,20 @@
[], [],
lang('Episode.form.publication_date_hint') lang('Episode.form.publication_date_hint')
) ?> ) ?>
<div class="flex mb-4" data-picker="datetime">
<?= form_input([ <?= form_input([
'id' => 'publication_date', 'id' => 'publication_date',
'name' => 'publication_date', 'name' => 'publication_date',
'class' => 'form-input mb-4', 'class' => 'form-input rounded-r-none flex-1',
'value' => old('publication_date', date('Y-m-d H:i')), 'value' => old('publication_date', date('Y-m-d H:i')),
'data-picker' => 'datetime', 'data-input' => '',
]) ?> ]) ?>
<button
class="p-3 bg-green-100 border border-l-0 focus:outline-none rounded-r-md hover:bg-green-200 focus:shadow-outline"
type="button"
title="<?= lang('Episode.form.publication_date_clear') ?>"
data-clear=""><?= icon('close') ?></button>
</div>
<?= form_fieldset('', ['class' => 'flex mb-6 gap-1']) ?> <?= form_fieldset('', ['class' => 'flex mb-6 gap-1']) ?>
<legend> <legend>

View File

@ -202,18 +202,25 @@
[], [],
lang('Episode.form.publication_date_hint') lang('Episode.form.publication_date_hint')
) ?> ) ?>
<div class="flex mb-4" data-picker="datetime">
<?= form_input([ <?= form_input([
'id' => 'publication_date', 'id' => 'publication_date',
'name' => 'publication_date', 'name' => 'publication_date',
'class' => 'form-input mb-4', 'class' => 'form-input rounded-r-none flex-1',
'value' => old( 'value' => old(
'publication_date', 'publication_date',
$episode->published_at $episode->published_at
? $episode->published_at->format('Y-m-d H:i') ? $episode->published_at->format('Y-m-d H:i')
: '' : ''
), ),
'data-picker' => 'datetime', 'data-input' => '',
]) ?> ]) ?>
<button
class="p-3 bg-green-100 border border-l-0 focus:outline-none rounded-r-md hover:bg-green-200 focus:shadow-outline"
type="button"
title="<?= lang('Episode.form.publication_date_clear') ?>"
data-clear=""><?= icon('close') ?></button>
</div>
<?= form_fieldset('', ['class' => 'mb-6']) ?> <?= form_fieldset('', ['class' => 'mb-6']) ?>
<legend> <legend>

View File

@ -65,9 +65,7 @@
'soundbites-edit', 'soundbites-edit',
$podcast->id, $podcast->id,
$episode->id $episode->id
) ?>"><?= lang( ) ?>"><?= lang('Episode.soundbites') ?></a>
'Episode.soundbites'
) ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode', 'episode',
$podcast->name, $podcast->name,
@ -84,7 +82,7 @@
<div class="mb-2 text-xs"> <div class="mb-2 text-xs">
<?= publication_pill( <?= publication_pill(
$episode->published_at, $episode->published_at,
$episode->is_published $episode->publication_status
) ?> ) ?>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->enclosure_duration ?>S">

View File

@ -8,7 +8,7 @@
<?= $episode->title . <?= $episode->title .
publication_pill( publication_pill(
$episode->published_at, $episode->published_at,
$episode->is_published, $episode->publication_status,
'text-sm ml-2 align-middle' 'text-sm ml-2 align-middle'
) ?> ) ?>
<?= $this->endSection() ?> <?= $this->endSection() ?>

View File

@ -33,6 +33,7 @@
'font-bold text-gray-600', 'font-bold text-gray-600',
true true
) ?> ) ?>
<?php if ($episode->published_at): ?>
<span class="mx-1"></span> <span class="mx-1"></span>
<time <time
pubdate pubdate
@ -44,6 +45,7 @@
$episode->published_at, $episode->published_at,
]) ?> ]) ?>
</time> </time>
<?php endif; ?>
</div> </div>
</div> </div>
<div class="relative" data-toggle="dropdown"> <div class="relative" data-toggle="dropdown">

12
package-lock.json generated
View File

@ -3944,9 +3944,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001077", "version": "1.0.30001165",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001077.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz",
"integrity": "sha512-AEzsGvjBJL0lby/87W96PyEvwN0GsYvk5LHsglLg9tW37K4BqvAvoSCdWIE13OZQ8afupqZ73+oL/1LkedN8hA==", "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==",
"dev": true "dev": true
}, },
"cardinal": { "cardinal": {
@ -16679,12 +16679,6 @@
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
} }
}, },
"caniuse-lite": {
"version": "1.0.30001142",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001142.tgz",
"integrity": "sha512-pDPpn9ankEpBFZXyCv2I4lh1v/ju+bqb78QfKf+w9XgDAFWBwSYPswXqprRdrgQWK0wQnpIbfwRjNHO1HWqvoQ==",
"dev": true
},
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",