Commit 7d21b350 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(episode): add preview link in admin to view and share episode before publication

parent 7a1eea58
Loading
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -210,6 +210,15 @@ $routes->get('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioControl
    'as' => 'episode-audio',
], );

// episode preview link
$routes->get('/p/(:uuid)', 'EpisodePreviewController::index/$1', [
    'as' => 'episode-preview',
]);

$routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [
    'as' => 'episode-preview-activity',
]);

// Other pages
$routes->get('/credits', 'CreditsController', [
    'as' => 'credits',
+66 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * @copyright  2023 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Controllers;

use App\Entities\Episode;
use App\Models\EpisodeModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;

class EpisodePreviewController extends BaseController
{
    protected Episode $episode;

    public function _remap(string $method, string ...$params): mixed
    {
        if (count($params) < 1) {
            throw PageNotFoundException::forPageNotFound();
        }

        // find episode by previewUUID
        $episode = (new EpisodeModel())->getEpisodeByPreviewId($params[0]);

        if (! $episode instanceof Episode) {
            throw PageNotFoundException::forPageNotFound();
        }

        $this->episode = $episode;

        if ($episode->publication_status === 'published') {
            // redirect to episode page
            return redirect()->route('episode', [$episode->podcast->handle, $episode->slug]);
        }

        unset($params[0]);

        return $this->{$method}(...$params);
    }

    public function index(): RedirectResponse | string
    {
        helper('form');

        return view('episode/preview-comments', [
            'podcast' => $this->episode->podcast,
            'episode' => $this->episode,
        ]);
    }

    public function activity(): RedirectResponse | string
    {
        helper('form');

        return view('episode/preview-activity', [
            'podcast' => $this->episode->podcast,
            'episode' => $this->episode,
        ]);
    }
}
+36 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace App\Database\Migrations;

class AddEpisodePreviewId extends BaseMigration
{
    public function up(): void
    {
        $fields = [
            'preview_id' => [
                'type'       => 'BINARY',
                'constraint' => 16,
                'after'      => 'podcast_id',
            ],
        ];

        $this->forge->addColumn('episodes', $fields);

        // set preview_id as unique key
        $prefix = $this->db->getPrefix();
        $uniquePreviewId = <<<CODE_SAMPLE
            ALTER TABLE `{$prefix}episodes`
            ADD CONSTRAINT `preview_id` UNIQUE (`preview_id`);
        CODE_SAMPLE;

        $this->db->query($uniquePreviewId);
    }

    public function down(): void
    {
        $fields = ['preview_id'];
        $this->forge->dropColumn('episodes', $fields);
    }
}
+20 −1
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ use App\Entities\Clip\Soundbite;
use App\Libraries\SimpleRSSElement;
use App\Models\ClipModel;
use App\Models\EpisodeCommentModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PodcastModel;
use App\Models\PostModel;
@@ -21,6 +22,7 @@ use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time;
use Exception;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
@@ -39,6 +41,8 @@ use SimpleXMLElement;
 * @property int $id
 * @property int $podcast_id
 * @property Podcast $podcast
 * @property ?string $preview_id
 * @property string $preview_link
 * @property string $link
 * @property string $guid
 * @property string $slug
@@ -150,6 +154,7 @@ class Episode extends Entity
    protected $casts = [
        'id'                    => 'integer',
        'podcast_id'            => 'integer',
        'preview_id'            => '?string',
        'guid'                  => 'string',
        'slug'                  => 'string',
        'title'                 => 'string',
@@ -667,4 +672,18 @@ class Episode extends Entity
            urlencode((string) $this->attributes['guid']) .
            ($serviceSlug !== null ? '&_from=' . $serviceSlug : '');
    }

    public function getPreviewLink(): string
    {
        if ($this->preview_id === null) {
            // generate preview id
            if (! $previewUUID = (new EpisodeModel())->setEpisodePreviewId($this->id)) {
                throw new Exception('Could not set episode preview id');
            }

            $this->preview_id = $previewUUID;
        }

        return url_to('episode-preview', (string) $this->preview_id);
    }
}
+58 −5
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ declare(strict_types=1);
 */

use App\Entities\Category;
use App\Entities\Episode;
use App\Entities\Location;
use CodeIgniter\I18n\Time;
use CodeIgniter\View\Table;
@@ -218,8 +219,8 @@ if (! function_exists('publication_status_banner')) {
        }

        return <<<HTML
        <div class="flex items-center px-12 py-2 border-b bg-stripes-gray border-subtle" role="alert">
            <p class="flex items-center text-gray-900">
        <div class="flex flex-wrap items-baseline px-4 py-2 border-b md:px-12 bg-stripes-default border-subtle" role="alert">
            <p class="flex items-baseline text-gray-900">
                <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span>
                <span class="ml-3 text-sm">{$bannerText}</span>
            </p>
@@ -231,6 +232,58 @@ if (! function_exists('publication_status_banner')) {

// ------------------------------------------------------------------------

if (! function_exists('episode_publication_status_banner')) {
    /**
     * Publication status banner component for podcasts
     *
     * Displays the appropriate banner depending on the podcast's publication status.
     */
    function episode_publication_status_banner(Episode $episode, string $class = ''): string
    {
        switch ($episode->publication_status) {
            case 'not_published':
                $linkRoute = route_to('episode-publish', $episode->podcast_id, $episode->id);
                $publishLinkLabel = lang('Episode.publish');
                break;
            case 'scheduled':
            case 'with_podcast':
                $linkRoute = route_to('episode-publish_edit', $episode->podcast_id, $episode->id);
                $publishLinkLabel = lang('Episode.publish_edit');
                break;
            default:
                $bannerDisclaimer = '';
                $linkRoute = '';
                $publishLinkLabel = '';
                break;
        }

        $bannerDisclaimer = lang('Episode.publication_status_banner.draft_mode');
        $bannerText = lang('Episode.publication_status_banner.text', [
            'publication_status' => $episode->publication_status,
            'publication_date'   => $episode->published_at instanceof Time ? local_datetime(
                $episode->published_at
            ) : null,
        ], null, false);
        $previewLinkLabel = lang('Episode.publication_status_banner.preview');

        return <<<HTML
        <div class="flex flex-wrap gap-4 items-baseline px-4 md:px-12 py-2 bg-stripes-default border-subtle {$class}" role="alert">
            <p class="flex items-baseline text-gray-900">
                <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span>
                <span class="ml-3 text-sm">{$bannerText}</span>
            </p>
            <div class="flex items-baseline">    
                <a href="{$episode->preview_link}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$previewLinkLabel}</a>
                <span class="mx-1">•</span>
                <a href="{$linkRoute}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$publishLinkLabel}</a>
            </div>
        </div>
        HTML;
    }
}

// ------------------------------------------------------------------------

if (! function_exists('episode_numbering')) {
    /**
     * Returns relevant translated episode numbering.
@@ -360,7 +413,7 @@ if (! function_exists('relative_time')) {
        $datetime = $time->format(DateTime::ATOM);

        return <<<HTML
            <relative-time tense="past" class="{$class}" datetime="{$datetime}">
            <relative-time tense="auto" class="{$class}" datetime="{$datetime}">
                <time
                    datetime="{$datetime}"
                    title="{$time}">{$translatedDate}</time>
@@ -378,7 +431,7 @@ if (! function_exists('local_datetime')) {
            'request'
        )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG);
        $translatedDate = $time->toLocalizedString($formatter->getPattern());
        $datetime = $time->format(DateTime::ISO8601);
        $datetime = $time->format(DateTime::ATOM);

        return <<<HTML
            <relative-time datetime="{$datetime}"
Loading