From 33d01b8d4fd6ebf24e9f011aa705c456c846956c Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Tue, 22 Jun 2021 17:59:33 +0000 Subject: [PATCH] fix(ux): allow for empty message upon episode publication and warn user on submit - clarify distiction between the announcement post and the show notes - change "note" occurences in UI by "post" - show warning message explaining why the podcaster should fill the message area - the podcaster can choose to publish the episode with an empty message anyways - redirect user to episode dashboard with error message when episode publication pages are inaccessible instead of showing a 404 error - add a cancel publication button in publish-edit form when episode is scheduled closes #129 --- app/Config/Routes.php | 9 + app/Controllers/Admin/EpisodeController.php | 59 ++++++- app/Helpers/components_helper.php | 4 +- app/Language/en/Episode.php | 17 +- app/Language/en/Podcast.php | 4 +- app/Language/fr/Episode.php | 17 +- app/Language/fr/Podcast.php | 4 +- .../ActivityPub/Models/StatusModel.php | 2 +- app/Views/_assets/admin.ts | 2 + .../_assets/modules/PublishMessageWarning.ts | 51 ++++++ app/Views/admin/episode/publish.php | 157 +++++++++--------- app/Views/admin/episode/publish_edit.php | 114 ++++++++----- 12 files changed, 300 insertions(+), 140 deletions(-) create mode 100644 app/Views/_assets/modules/PublishMessageWarning.ts diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f93778a8..41f8be75 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -310,6 +310,15 @@ $routes->group( 'permission:podcast-manage_publications', ], ); + $routes->get( + 'publish-cancel', + 'EpisodeController::publishCancel/$1/$2', + [ + 'as' => 'episode-publish-cancel', + 'filter' => + 'permission:podcast-manage_publications', + ], + ); $routes->get( 'unpublish', 'EpisodeController::unpublish/$1/$2', diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php index 28ad1a85..91c9a699 100644 --- a/app/Controllers/Admin/EpisodeController.php +++ b/app/Controllers/Admin/EpisodeController.php @@ -388,7 +388,7 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function publish(): string + public function publish(): string | RedirectResponse { if ($this->episode->publication_status === 'not_published') { helper(['form']); @@ -405,7 +405,10 @@ class EpisodeController extends BaseController return view('admin/episode/publish', $data); } - throw PageNotFoundException::forPageNotFound(); + return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + 'error', + lang('Episode.publish_error') + ); } public function attemptPublish(): RedirectResponse @@ -478,7 +481,7 @@ class EpisodeController extends BaseController return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); } - public function publishEdit(): string + public function publishEdit(): string | RedirectResponse { if ($this->episode->publication_status === 'scheduled') { helper(['form']); @@ -500,7 +503,11 @@ class EpisodeController extends BaseController ]); return view('admin/episode/publish_edit', $data); } - throw PageNotFoundException::forPageNotFound(); + + return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + 'error', + lang('Episode.publish_edit_error') + ); } public function attemptPublishEdit(): RedirectResponse @@ -572,7 +579,44 @@ class EpisodeController extends BaseController return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); } - public function unpublish(): string + public function publishCancel(): RedirectResponse + { + if ($this->episode->publication_status === 'scheduled') { + $db = db_connect(); + $db->transStart(); + + $statusModel = new StatusModel(); + $status = $statusModel + ->where([ + 'actor_id' => $this->podcast->actor_id, + 'episode_id' => $this->episode->id, + ]) + ->first(); + $statusModel->removeStatus($status); + + $this->episode->published_at = null; + + $episodeModel = new EpisodeModel(); + if (! $episodeModel->update($this->episode->id, $this->episode)) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('errors', $episodeModel->errors()); + } + + $db->transComplete(); + + return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); + } + + return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + 'error', + lang('Episode.publish_cancel_error') + ); + } + + public function unpublish(): string | RedirectResponse { if ($this->episode->publication_status === 'published') { helper(['form']); @@ -589,7 +633,10 @@ class EpisodeController extends BaseController return view('admin/episode/unpublish', $data); } - throw PageNotFoundException::forPageNotFound(); + return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + 'error', + lang('Episode.unpublish_error') + ); } public function attemptUnpublish(): RedirectResponse diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 53d1f524..90e33641 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -86,11 +86,11 @@ if (! function_exists('button')) { } if ($options['iconLeft']) { - $label = icon($options['iconLeft'], 'mr-2') . $label; + $label = icon((string) $options['iconLeft'], 'mr-2') . $label; } if ($options['iconRight']) { - $label .= icon($options['iconRight'], 'ml-2'); + $label .= icon((string) $options['iconRight'], 'ml-2'); } if ($uri !== '') { diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 80fe2ec7..4cd45688 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -27,8 +27,8 @@ return [ other {# total shares} }', 'total_statuses' => '{numberOfTotalStatuses, plural, - one {# note} - other {# total notes} + one {# total post} + other {# total posts} }', 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', @@ -36,6 +36,10 @@ return [ 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'unpublish_error' => 'Episode is not published.', 'delete' => 'Delete', 'go_to_page' => 'Go to page', 'create' => 'Add an episode', @@ -112,9 +116,10 @@ return [ 'submit_edit' => 'Save episode', ], 'publish_form' => [ - 'status' => 'Your note', + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'status' => 'Your announcement post', 'status_hint' => - 'The message you write will be broadcasted to all your followers in the fediverse.', + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", 'publication_date' => 'Publication date', 'publication_method' => [ 'now' => 'Now', @@ -126,6 +131,10 @@ return [ 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', 'submit' => 'Publish', 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', ], 'unpublish_form' => [ 'disclaimer' => diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index fa9279f4..828ce4f2 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -224,8 +224,8 @@ return [ other {# followers} }', 'statuses' => '{numberOfStatuses, plural, - one {# note} - other {# notes} + one {# post} + other {# posts} }', 'activity' => 'Activity', 'episodes' => 'Episodes', diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php index 86233cfa..5f75b506 100644 --- a/app/Language/fr/Episode.php +++ b/app/Language/fr/Episode.php @@ -27,8 +27,8 @@ return [ other {# partages en tout} }', 'total_statuses' => '{numberOfTotalStatuses, plural, - one {# note} - other {# notes} + one {# message} + other {# messages} }', 'all_podcast_episodes' => 'Tous les épisodes du podcast', 'back_to_podcast' => 'Revenir au podcast', @@ -36,6 +36,10 @@ return [ 'publish' => 'Publier', 'publish_edit' => 'Modifier la publication', 'unpublish' => 'Dépublier', + 'publish_error' => 'L’épisode est déjà publié.', + 'publish_edit_error' => 'L’épisode est déjà publié.', + 'publish_cancel_error' => 'L’épisode est déjà publié.', + 'unpublish_error' => 'L’épisode n’est pas publié.', 'delete' => 'Supprimer', 'go_to_page' => 'Voir', 'create' => 'Ajouter un épisode', @@ -115,9 +119,10 @@ return [ 'submit_edit' => 'Enregistrer l’épisode', ], 'publish_form' => [ - 'status' => 'Votre note', + 'back_to_episode_dashboard' => 'Retour au tableau de bord de l’épisode', + 'status' => 'Votre message de publication', 'status_hint' => - 'Le message que vous écrirez sera diffusé à toutes les personnes qui vous suivent dans le fédiverse.', + 'Écrivez un message pour annoncer la publication de votre épisode. Le message sera diffusé à toutes les personnes qui vous suivent dans le fédiverse et mis en évidence sur la page d’accueil de votre podcast.', 'publication_date' => 'Date de publication', 'publication_date_clear' => 'Effacer la date de publication', 'publication_date_hint' => @@ -132,6 +137,10 @@ return [ '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', 'submit' => 'Publier', 'submit_edit' => 'Modifier la publication', + 'cancel_publication' => 'Annuler la publication', + 'message_warning' => 'Vous n’avez pas saisi de message pour l’annonce de votre épisode !', + 'message_warning_hint' => 'Ajouter un message augmente l’engagement social, menant à une meilleure visibilité pour votre épisode.', + 'message_warning_submit' => 'Publish quand même', ], 'soundbites' => 'Extraits sonores', 'soundbites_form' => [ diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php index c93d2893..b5ccb5b0 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -226,8 +226,8 @@ return [ other {# abonné·e·s} }', 'notes' => '{numberOfStatuses, plural, - one {# note} - other {# notes} + one {# message} + other {# messages} }', 'activity' => 'Activité', 'episodes' => 'Épisodes', diff --git a/app/Libraries/ActivityPub/Models/StatusModel.php b/app/Libraries/ActivityPub/Models/StatusModel.php index 3f911bab..941beb03 100644 --- a/app/Libraries/ActivityPub/Models/StatusModel.php +++ b/app/Libraries/ActivityPub/Models/StatusModel.php @@ -81,7 +81,7 @@ class StatusModel extends UuidModel */ protected $validationRules = [ 'actor_id' => 'required', - 'message_html' => 'required_without[reblog_of_id]|max_length[500]', + 'message_html' => 'max_length[500]', ]; /** diff --git a/app/Views/_assets/admin.ts b/app/Views/_assets/admin.ts index be731524..2580f6d5 100644 --- a/app/Views/_assets/admin.ts +++ b/app/Views/_assets/admin.ts @@ -4,6 +4,7 @@ import DateTimePicker from "./modules/DateTimePicker"; import Dropdown from "./modules/Dropdown"; import MarkdownEditor from "./modules/MarkdownEditor"; import MultiSelect from "./modules/MultiSelect"; +import PublishMessageWarning from "./modules/PublishMessageWarning"; import SidebarToggler from "./modules/SidebarToggler"; import Slugify from "./modules/Slugify"; import Soundbites from "./modules/Soundbites"; @@ -23,3 +24,4 @@ Time(); Soundbites(); Clipboard(); ThemePicker(); +PublishMessageWarning(); diff --git a/app/Views/_assets/modules/PublishMessageWarning.ts b/app/Views/_assets/modules/PublishMessageWarning.ts new file mode 100644 index 00000000..fbde59f4 --- /dev/null +++ b/app/Views/_assets/modules/PublishMessageWarning.ts @@ -0,0 +1,51 @@ +const PublishMessageWarning = (): void => { + const publishForm: HTMLFormElement | null = document.querySelector( + "form[data-submit='validate-message']" + ); + + if (publishForm) { + const messageTextArea: HTMLTextAreaElement | null = publishForm.querySelector( + "[name='message']" + ); + const submitButton: HTMLButtonElement | null = publishForm.querySelector( + "button[type='submit']" + ); + const publishMessageWarning: HTMLDivElement | null = publishForm.querySelector( + "[id='publish-warning']" + ); + + if ( + messageTextArea && + submitButton && + submitButton.dataset.btnTextWarning && + submitButton.dataset.btnText && + publishMessageWarning + ) { + publishForm.addEventListener("submit", (event) => { + if ( + messageTextArea.value === "" && + publishMessageWarning.classList.contains("hidden") + ) { + event.preventDefault(); + + publishMessageWarning.classList.remove("hidden"); + messageTextArea.focus(); + submitButton.innerText = submitButton.dataset + .btnTextWarning as string; + } + }); + + messageTextArea.addEventListener("input", () => { + if ( + submitButton.innerText !== submitButton.dataset.btnText && + messageTextArea.value !== "" + ) { + publishMessageWarning.classList.add("hidden"); + submitButton.innerText = submitButton.dataset.btnText as string; + } + }); + } + } +}; + +export default PublishMessageWarning; diff --git a/app/Views/admin/episode/publish.php b/app/Views/admin/episode/publish.php index d612d903..e1ed34bc 100644 --- a/app/Views/admin/episode/publish.php +++ b/app/Views/admin/episode/publish.php @@ -11,26 +11,34 @@ section('content') ?> +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'], +) ?> + id, $episode->id), [ 'method' => 'post', - 'class' => 'flex flex-col max-w-xl items-start', + 'class' => 'mx-auto flex flex-col max-w-xl items-start', + 'data-submit' => 'validate-message' ]) ?> + 'Episode.publish_form.status', + ) ?> +
<?= $podcast
-    ->actor->display_name ?> + ->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />

actor - ->display_name ?> + ->display_name ?> @actor - ->username ?> + ->username ?>

@@ -39,17 +47,15 @@ 'id' => 'message', 'name' => 'message', 'class' => 'form-textarea min-w-0 w-full', - 'required' => 'required', 'placeholder' => 'Write your message...', + 'autofocus' => '' ], old('message', '', false), ['rows' => 2], ) ?>
- <?= $episode->title ?> + <?= $episode->title ?>
@@ -68,34 +74,32 @@
+ 'chat', + 'text-xl mr-1 text-gray-400', + ) . '0' ?> + 'repeat', + 'text-xl mr-1 text-gray-400', + ) . '0' ?> + 'heart', + 'text-xl mr-1 text-gray-400', + ) . '0' ?>
'flex flex-col mb-4']) ?> - -