Skip to content
Snippets Groups Projects
Commit d783d16e authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(episode): add form to allow editing episode's publication date to a past date

This allows podcasters to reorganize their published episodes as they see fit

closes #97
parent 94c0b7c1
No related branches found
No related tags found
No related merge requests found
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" d="M0 0H24V24H0z"/>
<path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12h2c0 4.418 3.582 8 8 8s8-3.582 8-8-3.582-8-8-8C9.536 4 7.332 5.114 5.865 6.865L8 9H2V3l2.447 2.446C6.28 3.336 8.984 2 12 2zm1 5v4.585l3.243 3.243-1.415 1.415L11 12.413V7h2z"/>
</svg>
...@@ -4,27 +4,28 @@ declare(strict_types=1); ...@@ -4,27 +4,28 @@ declare(strict_types=1);
namespace App\Views\Components; namespace App\Views\Components;
use ViewComponents\Component; class IconButton extends Button
class IconButton extends Component
{ {
public string $glyph = ''; public string $glyph = '';
public function render(): string public function __construct(array $attributes)
{ {
$attributes = [ $iconButtonAttributes = [
'isSquared' => 'true', 'isSquared' => 'true',
'title' => $this->slot, 'title' => $attributes['slot'],
'data-tooltip' => 'bottom', 'data-tooltip' => 'bottom',
]; ];
$attributes = array_merge($attributes, $this->attributes); $glyphSize = [
'small' => 'text-sm',
'base' => 'text-lg',
'large' => 'text-2xl',
];
$attributes['slot'] = icon($this->glyph); $allAttributes = array_merge($attributes, $iconButtonAttributes);
unset($attributes['glyph']); parent::__construct($allAttributes);
$iconButton = new Button($attributes); $this->slot = icon($this->glyph, $glyphSize[$this->size]);
return $iconButton->render();
} }
} }
...@@ -327,6 +327,23 @@ $routes->group( ...@@ -327,6 +327,23 @@ $routes->group(
'permission:podcast-manage_publications', 'permission:podcast-manage_publications',
], ],
); );
$routes->get(
'publish-date-edit',
'EpisodeController::publishDateEdit/$1/$2',
[
'as' => 'episode-publish_date_edit',
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->post(
'publish-date-edit',
'EpisodeController::attemptPublishDateEdit/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get( $routes->get(
'unpublish', 'unpublish',
'EpisodeController::unpublish/$1/$2', 'EpisodeController::unpublish/$1/$2',
......
...@@ -683,29 +683,104 @@ class EpisodeController extends BaseController ...@@ -683,29 +683,104 @@ class EpisodeController extends BaseController
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
} }
public function unpublish(): string | RedirectResponse public function publishDateEdit(): string|RedirectResponse
{ {
if ($this->episode->publication_status === 'published') { // only accessible if episode is already published
helper(['form']); if ($this->episode->publication_status !== 'published') {
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
'error',
lang('Episode.publish_date_edit_error')
);
}
$data = [ helper('form');
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([ $data = [
0 => $this->podcast->title, 'podcast' => $this->podcast,
1 => $this->episode->title, 'episode' => $this->episode,
]); ];
return view('episode/unpublish', $data);
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('episode/publish_date_edit', $data);
}
/**
* Allows to set an episode's publication date to a past date
*
* Prevents setting a future date as it does not make sense to set a future published date to an already published
* episode. This also prevents any side-effects from occurring.
*/
public function attemptPublishDateEdit(): RedirectResponse
{
$rules = [
'new_publication_date' => 'valid_date[Y-m-d H:i]',
];
if (! $this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$newPublicationDate = $this->request->getPost('new_publication_date');
$newPublicationDate = Time::createFromFormat(
'Y-m-d H:i',
$newPublicationDate,
$this->request->getPost('client_timezone'),
)->setTimezone(app_timezone());
if ($newPublicationDate->isAfter(Time::now())) {
return redirect()
->back()
->withInput()
->with('error', lang('Episode.publish_date_edit_future_error'));
}
$this->episode->published_at = $newPublicationDate;
$episodeModel = new EpisodeModel();
if (! $episodeModel->update($this->episode->id, $this->episode)) {
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
} }
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
'error', 'message',
lang('Episode.unpublish_error') lang('Episode.publish_date_edit_success')
); );
} }
public function unpublish(): string | RedirectResponse
{
if ($this->episode->publication_status !== 'published') {
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
'error',
lang('Episode.unpublish_error')
);
}
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('episode/unpublish', $data);
}
public function attemptUnpublish(): RedirectResponse public function attemptUnpublish(): RedirectResponse
{ {
$rules = [ $rules = [
......
...@@ -25,6 +25,7 @@ return [ ...@@ -25,6 +25,7 @@ return [
'persons' => 'persons', 'persons' => 'persons',
'publish' => 'publish', 'publish' => 'publish',
'publish-edit' => 'edit publication', 'publish-edit' => 'edit publication',
'publish-date-edit' => 'edit publication date',
'unpublish' => 'unpublish', 'unpublish' => 'unpublish',
'delete' => 'delete', 'delete' => 'delete',
'fediverse' => 'fediverse', 'fediverse' => 'fediverse',
......
...@@ -24,10 +24,14 @@ return [ ...@@ -24,10 +24,14 @@ return [
'edit' => 'Edit', 'edit' => 'Edit',
'publish' => 'Publish', 'publish' => 'Publish',
'publish_edit' => 'Edit publication', 'publish_edit' => 'Edit publication',
'publish_date_edit' => 'Edit publication date',
'unpublish' => 'Unpublish', 'unpublish' => 'Unpublish',
'publish_error' => 'Episode is already published.', 'publish_error' => 'Episode is already published.',
'publish_edit_error' => 'Episode is already published.', 'publish_edit_error' => 'Episode is already published.',
'publish_cancel_error' => 'Episode is already published.', 'publish_cancel_error' => 'Episode is already published.',
'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.',
'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.',
'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!',
'unpublish_error' => 'Episode is not published.', 'unpublish_error' => 'Episode is not published.',
'delete' => 'Delete', 'delete' => 'Delete',
'go_to_page' => 'Go to page', 'go_to_page' => 'Go to page',
...@@ -178,6 +182,11 @@ return [ ...@@ -178,6 +182,11 @@ return [
'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.',
'message_warning_submit' => 'Publish anyways', 'message_warning_submit' => 'Publish anyways',
], ],
'publish_date_edit_form' => [
'new_publication_date' => 'New publication date',
'new_publication_date_hint' => 'Must be set to a past date.',
'submit' => 'Edit publication date',
],
'unpublish_form' => [ 'unpublish_form' => [
'disclaimer' => 'disclaimer' =>
"Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.",
......
<?php declare(strict_types=1);
$isPodcastArea = isset($podcast) && ! isset($episode);
$isEpisodeArea = isset($podcast) && isset($episode);
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?= service('request') <html lang="<?= service('request')
->getLocale() ?>"> ->getLocale() ?>">
...@@ -32,9 +38,9 @@ ...@@ -32,9 +38,9 @@
<?= render_breadcrumb('text-xs items-center flex') ?> <?= render_breadcrumb('text-xs items-center flex') ?>
<div class="flex justify-between py-1"> <div class="flex justify-between py-1">
<div class="flex flex-wrap items-center"> <div class="flex flex-wrap items-center">
<?php if ((isset($episode) && $episode->is_premium) || (isset($podcast) && $podcast->is_premium)): ?> <?php if (($isEpisodeArea && $episode->is_premium) || ($isPodcastArea && $podcast->is_premium)): ?>
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<IconButton uri="<?= route_to('subscription-list', $podcast->id) ?>" glyph="exchange-dollar" variant="secondary" class="p-0 mr-2 text-4xl border-0"><?= isset($episode) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?></IconButton> <IconButton uri="<?= route_to('subscription-list', $podcast->id) ?>" glyph="exchange-dollar" variant="secondary" size="large" class="p-0 mr-2 border-0"><?= ($isEpisodeArea && $episode->is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?></IconButton>
<Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></Heading> <Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></Heading>
</div> </div>
<?php else: ?> <?php else: ?>
...@@ -42,11 +48,11 @@ ...@@ -42,11 +48,11 @@
<?php endif; ?> <?php endif; ?>
<?= $this->renderSection('headerLeft') ?> <?= $this->renderSection('headerLeft') ?>
</div> </div>
<div class="flex flex-shrink-0 gap-x-2"><?= $this->renderSection('headerRight') ?></div> <div class="flex items-center flex-shrink-0 gap-x-2"><?= $this->renderSection('headerRight') ?></div>
</div> </div>
</div> </div>
</header> </header>
<?php if (isset($podcast) && $podcast->publication_status !== 'published'): ?> <?php if ($isPodcastArea && $podcast->publication_status !== 'published'): ?>
<?= publication_status_banner($podcast->published_at, $podcast->id, $podcast->publication_status) ?> <?= publication_status_banner($podcast->published_at, $podcast->id, $podcast->publication_status) ?>
<?php endif ?> <?php endif ?>
<div class="px-2 py-8 mx-auto md:px-12"> <div class="px-2 py-8 mx-auto md:px-12">
......
<?php declare(strict_types=1);
$isPodcastArea = isset($podcast) && ! isset($episode);
$isEpisodeArea = isset($podcast) && isset($episode);
?>
<div data-sidebar-toggler="backdrop" role="button" tabIndex="0" aria-label="<?= lang('Common.close') ?>" class="fixed z-50 hidden w-full h-full bg-gray-800/75 md:hidden"></div> <div data-sidebar-toggler="backdrop" role="button" tabIndex="0" aria-label="<?= lang('Common.close') ?>" class="fixed z-50 hidden w-full h-full bg-gray-800/75 md:hidden"></div>
<aside data-sidebar-toggler="sidebar" data-toggle-class="-translate-x-full" data-hide-class="-translate-x-full" class="h-full max-h-[calc(100vh-40px)] sticky z-50 flex flex-col row-start-2 col-start-1 text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-navigation bg-navigation md:translate-x-0"> <aside data-sidebar-toggler="sidebar" data-toggle-class="-translate-x-full" data-hide-class="-translate-x-full" class="h-full max-h-[calc(100vh-40px)] sticky z-50 flex flex-col row-start-2 col-start-1 text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-navigation bg-navigation md:translate-x-0">
<?php if (isset($podcast) && isset($episode)): ?> <?php if ($isEpisodeArea): ?>
<?= $this->include('episode/_sidebar') ?> <?= $this->include('episode/_sidebar') ?>
<?php elseif (isset($podcast)): ?> <?php elseif ($isPodcastArea): ?>
<?= $this->include('podcast/_sidebar') ?> <?= $this->include('podcast/_sidebar') ?>
<?php else: ?> <?php else: ?>
<?= $this->include('_sidebar') ?> <?= $this->include('_sidebar') ?>
......
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Episode.publish_date_edit') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Episode.publish_date_edit') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= anchor(
route_to('episode-view', $podcast->id, $episode->id),
icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'),
[
'class' => 'inline-flex items-center font-semibold mr-4 text-sm',
],
) ?>
<form action="<?= route_to('episode-publish_date_edit', $podcast->id, $episode->id) ?>" method="POST" class="flex flex-col items-start w-full max-w-lg mx-auto mt-4" data-submit="validate-message">
<?= csrf_field() ?>
<input type="hidden" name="client_timezone" value="UTC" />
<Forms.Field
as="DatetimePicker"
name="new_publication_date"
label="<?= lang('Episode.publish_date_edit_form.new_publication_date') ?>"
hint="<?= lang('Episode.publish_date_edit_form.new_publication_date_hint') ?>"
value="<?= $episode->published_at ?>"
required="true"
/>
<Button variant="primary" type="submit" class="mt-4"><?= lang('Episode.publish_date_edit_form.submit') ?></Button>
</form>
<?= $this->endSection() ?>
...@@ -17,6 +17,14 @@ ...@@ -17,6 +17,14 @@
<?= $this->endSection() ?> <?= $this->endSection() ?>
<?= $this->section('headerRight') ?> <?= $this->section('headerRight') ?>
<?php if ($episode->publication_status === 'published'): ?>
<IconButton
uri="<?= route_to('episode-publish_date_edit', $podcast->id, $episode->id) ?>"
glyph="history"
variant="secondary"
glyphClass="text-xl"
><?= lang('Episode.publish_date_edit') ?></IconButton>
<?php endif; ?>
<?= publication_button( <?= publication_button(
$podcast->id, $podcast->id,
$episode->id, $episode->id,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment