fix: rename podcast name to podcast handle to clarify field usage

- podcast name was too vague and didn't come clearly for users: handle is more relevant
- update
package.json dependencies and remove unnused packages

closes #126
This commit is contained in:
Yassine Doghri 2021-07-26 13:10:46 +00:00
parent 3a0a76d705
commit 9dd4c7741e
75 changed files with 8758 additions and 7505 deletions

View File

@ -12,6 +12,7 @@
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[php]": {
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
"editor.formatOnSave": false

View File

@ -31,7 +31,7 @@ $routes->setAutoRoute(false);
* --------------------------------------------------------------------
*/
$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,32}');
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}');
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
@ -685,7 +685,7 @@ $routes->group(config('App')->authGateway, function ($routes): void {
});
// Podcast's Public routes
$routes->group('@(:podcastName)', function ($routes): void {
$routes->group('@(:podcastHandle)', function ($routes): void {
$routes->get('/', 'PodcastController::activity/$1', [
'as' => 'podcast-activity',
]);
@ -802,7 +802,7 @@ $routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [
/**
* Overwriting ActivityPub routes file
*/
$routes->group('@(:podcastName)', function ($routes): void {
$routes->group('@(:podcastHandle)', function ($routes): void {
$routes->post('statuses/new', 'StatusController::attemptCreate/$1', [
'as' => 'status-attempt-create',
'filter' => 'permission:podcast-manage_publications',

View File

@ -192,9 +192,9 @@ class PodcastController extends BaseController
}
$podcast = new Podcast([
'guid' => podcast_uuid(url_to('podcast_feed', $this->request->getPost('name'))),
'guid' => podcast_uuid(url_to('podcast_feed', $this->request->getPost('handle'))),
'title' => $this->request->getPost('title'),
'name' => $this->request->getPost('name'),
'handle' => $this->request->getPost('handle'),
'description_markdown' => $this->request->getPost('description'),
'image' => new Image($this->request->getFile('image')),
'language_code' => $this->request->getPost('language'),

View File

@ -131,14 +131,14 @@ class PodcastImportController extends BaseController
if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) {
$guid = (string) $nsPodcast->guid;
} else {
$guid = podcast_uuid(url_to('podcast_feed', $this->request->getPost('name')));
$guid = podcast_uuid(url_to('podcast_feed', $this->request->getPost('handle')));
}
$podcast = new Podcast([
'guid' => $guid,
'name' => $this->request->getPost('name'),
'handle' => $this->request->getPost('handle'),
'imported_feed_url' => $this->request->getPost('imported_feed_url'),
'new_feed_url' => url_to('podcast_feed', $this->request->getPost('name')),
'new_feed_url' => url_to('podcast_feed', $this->request->getPost('handle')),
'title' => (string) $feed->channel[0]->title,
'description_markdown' => $converter->convert($channelDescriptionHtml),
'description_html' => $channelDescriptionHtml,

View File

@ -41,7 +41,7 @@ class EpisodeController extends BaseController
}
if (
($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
) {
throw PageNotFoundException::forPageNotFound();
}
@ -50,7 +50,7 @@ class EpisodeController extends BaseController
if (
($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
) {
) {
throw PageNotFoundException::forPageNotFound();
}

View File

@ -20,11 +20,11 @@ use Opawg\UserAgentsPhp\UserAgentsRSS;
class FeedController extends Controller
{
public function index(string $podcastName): ResponseInterface
public function index(string $podcastHandle): ResponseInterface
{
helper('rss');
$podcast = (new PodcastModel())->where('name', $podcastName)
$podcast = (new PodcastModel())->where('handle', $podcastHandle)
->first();
if (! $podcast) {
throw PageNotFoundException::forPageNotFound();

View File

@ -31,7 +31,7 @@ class HomeController extends BaseController
// check if there's only one podcast to redirect user to it
if (count($allPodcasts) === 1) {
return redirect()->route('podcast-activity', [$allPodcasts[0]->name]);
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
}
// default behavior: list all podcasts on home page

View File

@ -20,7 +20,6 @@ use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use App\Models\StatusModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Response;
class PodcastController extends BaseController
@ -36,7 +35,7 @@ class PodcastController extends BaseController
}
if (
($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
) {
throw PageNotFoundException::forPageNotFound();
}
@ -48,7 +47,10 @@ class PodcastController extends BaseController
return $this->{$method}(...$params);
}
public function podcastActor(): RedirectResponse
/**
* @noRector ReturnTypeDeclarationRector
*/
public function podcastActor(): Response
{
$podcastActor = new PodcastActor($this->podcast);
@ -161,7 +163,7 @@ class PodcastController extends BaseController
'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'],
'route' =>
route_to('podcast-episodes', $this->podcast->name) .
route_to('podcast-episodes', $this->podcast->handle) .
'?year=' .
$year['year'],
'is_active' => $isActive,
@ -187,7 +189,7 @@ class PodcastController extends BaseController
]),
'number_of_episodes' => $season['number_of_episodes'],
'route' =>
route_to('podcast-episodes', $this->podcast->name) .
route_to('podcast-episodes', $this->podcast->handle) .
'?season=' .
$season['season_number'],
'is_active' => $isActive,

View File

@ -40,7 +40,7 @@ class StatusController extends ActivityPubStatusController
public function _remap(string $method, string ...$params): mixed
{
if (
($podcast = (new PodcastModel())->getPodcastByName($params[0],)) === null
($podcast = (new PodcastModel())->getPodcastByHandle($params[0],)) === null
) {
throw PageNotFoundException::forPageNotFound();
}
@ -127,7 +127,7 @@ class StatusController extends ActivityPubStatusController
if (
$episodeUri &&
($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastName'], $params['episodeSlug']))
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
) {
$newStatus->episode_id = $episode->id;
}

View File

@ -32,7 +32,7 @@ class AddPodcasts extends Migration
'type' => 'INT',
'unsigned' => true,
],
'name' => [
'handle' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
@ -193,7 +193,7 @@ class AddPodcasts extends Migration
$this->forge->addPrimaryKey('id');
// TODO: remove name in favor of username from actor
$this->forge->addUniqueKey('name');
$this->forge->addUniqueKey('handle');
$this->forge->addUniqueKey('guid');
$this->forge->addUniqueKey('actor_id');
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');

View File

@ -185,7 +185,7 @@ class Episode extends Entity
}
// Save image
$image->saveImage('podcasts/' . $this->getPodcast()->name, $this->attributes['slug']);
$image->saveImage('podcasts/' . $this->getPodcast()->handle, $this->attributes['slug']);
$this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = $image->path;
@ -214,7 +214,7 @@ class Episode extends Entity
$this->attributes['audio_file_path'] = save_media(
$audioFile,
'podcasts/' . $this->getPodcast()->name,
'podcasts/' . $this->getPodcast()->handle,
$this->attributes['slug'],
);
$this->attributes['audio_file_duration'] =
@ -237,7 +237,7 @@ class Episode extends Entity
$this->attributes['transcript_file_path'] = save_media(
$transcriptFile,
'podcasts/' . $this->getPodcast()
->name,
->handle,
$this->attributes['slug'] . '-transcript',
);
@ -254,7 +254,7 @@ class Episode extends Entity
$this->attributes['chapters_file_path'] = save_media(
$chaptersFile,
'podcasts/' . $this->getPodcast()
->name,
->handle,
$this->attributes['slug'] . '-chapters',
);
@ -430,11 +430,11 @@ class Episode extends Entity
? route_to(
'embeddable-player-theme',
$this->getPodcast()
->name,
->handle,
$this->attributes['slug'],
$theme,
)
: route_to('embeddable-player', $this->getPodcast() ->name, $this->attributes['slug']),
: route_to('embeddable-player', $this->getPodcast()->handle, $this->attributes['slug']),
);
}

View File

@ -26,7 +26,7 @@ use RuntimeException;
* @property string $guid
* @property int $actor_id
* @property Actor|null $actor
* @property string $name
* @property string $handle
* @property string $link
* @property string $feed_url
* @property string $title
@ -140,7 +140,7 @@ class Podcast extends Entity
'id' => 'integer',
'guid' => 'string',
'actor_id' => 'integer',
'name' => 'string',
'handle' => 'string',
'title' => 'string',
'description_markdown' => 'string',
'description_html' => 'string',
@ -193,7 +193,7 @@ class Podcast extends Entity
public function setImage(Image $image): static
{
// Save image
$image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
$image->saveImage('podcasts/' . $this->attributes['handle'], 'cover');
$this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = $image->path;
@ -208,12 +208,12 @@ class Podcast extends Entity
public function getLink(): string
{
return url_to('podcast-activity', $this->attributes['name']);
return url_to('podcast-activity', $this->attributes['handle']);
}
public function getFeedUrl(): string
{
return url_to('podcast_feed', $this->attributes['name']);
return url_to('podcast_feed', $this->attributes['handle']);
}
/**

View File

@ -251,7 +251,7 @@ if (! function_exists('get_rss_feed')) {
// add link to episode comments as podcast-activity format
$comments = $item->addChild('comments', null, $podcastNamespace);
$comments->addAttribute('uri', url_to('episode-comments', $podcast->name, $episode->slug));
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
$comments->addAttribute('contentType', 'application/podcast-activity+json');
if ($episode->transcript_file_url) {

View File

@ -40,7 +40,7 @@ if (! function_exists('extract_params_from_episode_uri')) {
function extract_params_from_episode_uri(URI $episodeUri): ?array
{
preg_match(
'~@(?P<podcastName>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~',
'~@(?P<podcastHandle>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~',
$episodeUri->getPath(),
$matches,
);
@ -50,14 +50,14 @@ if (! function_exists('extract_params_from_episode_uri')) {
}
if (
! array_key_exists('podcastName', $matches) ||
! array_key_exists('podcastHandle', $matches) ||
! array_key_exists('episodeSlug', $matches)
) {
return null;
}
return [
'podcastName' => $matches['podcastName'],
'podcastHandle' => $matches['podcastHandle'],
'episodeSlug' => $matches['episodeSlug'],
];
}

View File

@ -19,17 +19,17 @@ return [
'submit' => 'Proceed to follow',
],
'favourite' => [
'title' => "Favourite {actorDisplayName}'s note",
'title' => "Favourite {actorDisplayName}'s post",
'subtitle' => 'You are going to favourite:',
'submit' => 'Proceed to favourite',
],
'reblog' => [
'title' => "Share {actorDisplayName}'s note",
'title' => "Share {actorDisplayName}'s post",
'subtitle' => 'You are going to share:',
'submit' => 'Proceed to share',
],
'reply' => [
'title' => "Reply to {actorDisplayName}'s note",
'title' => "Reply to {actorDisplayName}'s post",
'subtitle' => 'You are going to reply to:',
'submit' => 'Proceed to reply',
],

View File

@ -10,7 +10,7 @@ declare(strict_types=1);
return [
'podcast_contributors' => 'Podcast contributors',
'view' => "{username}'s contribution to {podcastName}",
'view' => "{username}'s contribution to {podcastTitle}",
'add' => 'Add contributor',
'add_contributor' => 'Add a contributor for {0}',
'edit_role' => 'Update role for {0}',

View File

@ -28,9 +28,9 @@ return [
'identity_section_subtitle' => 'These fields allow you to get noticed.',
'image' => 'Cover image',
'title' => 'Title',
'name' => 'Name',
'name_hint' =>
'Used for generating the podcast URL. Uppercase, lowercase, numbers and underscores are accepted.',
'handle' => 'Handle',
'handle_hint' =>
'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.',
'type' => [
'label' => 'Type',
'hint' =>

View File

@ -17,8 +17,6 @@ return [
'imported_feed_url' => 'Feed URL',
'imported_feed_url_hint' => 'The feed must be in xml or rss format.',
'new_podcast_section_title' => 'The new podcast',
'name' => 'Name',
'name_hint' => 'Used for generating the podcast URL.',
'advanced_params_section_title' => 'Advanced parameters',
'advanced_params_section_subtitle' =>
'Keep the default values if you have no idea of what the fields are for.',

View File

@ -9,8 +9,8 @@ declare(strict_types=1);
*/
return [
'title' => "{actorDisplayName}'s Note",
'back_to_actor_statuses' => 'Back to {actor} notes',
'title' => "{actorDisplayName}'s post",
'back_to_actor_statuses' => 'Back to {actor} posts',
'actor_shared' => '{actor} shared',
'reply_to' => 'Reply to @{actorUsername}',
'form' => [
@ -33,8 +33,8 @@ return [
one {# reply}
other {# replies}
}',
'expand' => 'Expand note',
'expand' => 'Expand post',
'block_actor' => 'Block user @{actorUsername}',
'block_domain' => 'Block domain @{actorDomain}',
'delete' => 'Delete note',
'delete' => 'Delete post',
];

View File

@ -20,17 +20,17 @@ return [
'submit' => 'Poursuivre',
],
'favourite' => [
'title' => 'Mettez la note de {actorDisplayName} en favori',
'title' => 'Mettez la publication de {actorDisplayName} en favori',
'subtitle' => 'Vous allez mettre en favori :',
'submit' => 'Poursuivre',
],
'reblog' => [
'title' => 'Partagez la note de {actorDisplayName}',
'title' => 'Partagez la publication de {actorDisplayName}',
'subtitle' => 'Vous allez partager :',
'submit' => 'Poursuivre',
],
'reply' => [
'title' => 'Répondre à la note de {actorDisplayName}',
'title' => 'Répondre à la publication de {actorDisplayName}',
'subtitle' => 'Vous allez répondre à :',
'submit' => 'Poursuivre',
],

View File

@ -10,7 +10,7 @@ declare(strict_types=1);
return [
'podcast_contributors' => 'Contributeurs du podcast',
'view' => 'Contribution de {username} à {podcastName}',
'view' => 'Contribution de {username} à {podcastTitle}',
'add' => 'Ajouter un contributeur',
'add_contributor' => 'Ajouter un contributeur pour {0}',
'edit_role' => 'Modifier le rôle de {0}',

View File

@ -29,9 +29,9 @@ return [
'Ces champs vous permettent de vous faire remarquer.',
'image' => 'Image de couverture',
'title' => 'Titre',
'name' => 'Nom',
'name_hint' =>
'Utilisé pour ladresse du podcast. Les majuscules, les minuscules, les chiffres et le caractère souligné «_» sont acceptés.',
'handle' => 'Identifiant',
'handle_hint' =>
'Utilisé pour identifier le podcast. Les majuscules, les minuscules, les chiffres et le caractère souligné «_» sont acceptés.',
'type' => [
'label' => 'Type',
'hint' =>
@ -225,9 +225,9 @@ return [
one {<span class="font-semibold">#</span> abonné·e}
other {<span class="font-semibold">#</span> abonné·e·s}
}',
'notes' => '{numberOfStatuses, plural,
one {<span class="font-semibold">#</span> message}
other {<span class="font-semibold">#</span> messages}
'statuses' => '{numberOfStatuses, plural,
one {<span class="font-semibold">#</span> publication}
other {<span class="font-semibold">#</span> publications}
}',
'activity' => 'Activité',
'episodes' => 'Épisodes',

View File

@ -17,8 +17,6 @@ return [
'imported_feed_url' => 'Adresse du flux',
'imported_feed_url_hint' => 'Le flux doit être au format xml ou rss.',
'new_podcast_section_title' => 'Le nouveau podcast',
'name' => 'Nom',
'name_hint' => 'Utilisé pour ladresse du podcast.',
'advanced_params_section_title' => 'Paramètres avancés',
'advanced_params_section_subtitle' =>
'Si vous ne savez pas à quoi servent ces champs, conservez les valeurs par défaut.',

View File

@ -9,8 +9,8 @@ declare(strict_types=1);
*/
return [
'title' => 'Note de {actorDisplayName}',
'back_to_actor_statuses' => 'Retour aux notes de {actor}',
'title' => 'Publication de {actorDisplayName}',
'back_to_actor_statuses' => 'Retour aux publications de {actor}',
'actor_shared' => '{actor} a partagé',
'reply_to' => 'Répondre à @{actorUsername}',
'form' => [
@ -34,8 +34,8 @@ return [
one {# réponse}
other {# réponses}
}',
'expand' => 'Ouvrir la note',
'expand' => 'Ouvrir la publication',
'block_actor' => 'Bloquer lutilisateur @{actorUsername}',
'block_domain' => 'Bloquer le domaine @{actorDomain}',
'delete' => 'Supprimer la note',
'delete' => 'Supprimer la publication',
];

View File

@ -1,189 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use Analytics\AnalyticsTrait;
use App\Entities\Episode;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use SimpleXMLElement;
class EpisodeController extends BaseController
{
use AnalyticsTrait;
protected Podcast $podcast;
protected Episode $episode;
public function _remap(string $method, string ...$params): mixed
{
if (count($params) < 2) {
throw PageNotFoundException::forPageNotFound();
}
if (
($this->podcast = (new PodcastModel())->getPodcastByName($params[0])) === null
) {
throw PageNotFoundException::forPageNotFound();
}
if (
($this->episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) !== null
) {
unset($params[1]);
unset($params[0]);
return $this->{$method}(...$params);
}
throw PageNotFoundException::forPageNotFound();
}
public function index(): string
{
// Prevent analytics hit when authenticated
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->episode->podcast_id);
}
$locale = service('request')
->getLocale();
$cacheName =
"page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" .
(can_user_interact() ? '_authenticated' : '');
if (! ($cachedView = cache($cacheName))) {
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
if (can_user_interact()) {
helper('form');
return view('podcast/episode_authenticated', $data);
}
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('podcast/episode', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
public function embeddablePlayer(string $theme = 'light-transparent'): string
{
header('Content-Security-Policy: frame-ancestors https://* http://*');
// Prevent analytics hit when authenticated
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->episode->podcast_id);
}
$session = Services::session();
$session->start();
if (isset($_SERVER['HTTP_REFERER'])) {
$session->set('embeddable_player_domain', parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST));
}
$locale = service('request')
->getLocale();
$cacheName = "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_embeddable_player_{$theme}_{$locale}";
if (! ($cachedView = cache($cacheName))) {
$theme = EpisodeModel::$themes[$theme];
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'theme' => $theme,
];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('embeddable_player', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
public function oembedJSON(): ResponseInterface
{
return $this->response->setJSON([
'type' => 'rich',
'version' => '1.0',
'title' => $this->episode->title,
'provider_name' => $this->podcast->title,
'provider_url' => $this->podcast->link,
'author_name' => $this->podcast->title,
'author_url' => $this->podcast->link,
'html' =>
'<iframe src="' .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
'width' => 600,
'height' => 200,
'thumbnail_url' => $this->episode->image->large_url,
'thumbnail_width' => config('Images')
->largeSize,
'thumbnail_height' => config('Images')
->largeSize,
]);
}
public function oembedXML(): ResponseInterface
{
$oembed = new SimpleXMLElement("<?xml version='1.0' encoding='utf-8' standalone='yes'?><oembed></oembed>");
$oembed->addChild('type', 'rich');
$oembed->addChild('version', '1.0');
$oembed->addChild('title', $this->episode->title);
$oembed->addChild('provider_name', $this->podcast->title);
$oembed->addChild('provider_url', $this->podcast->link);
$oembed->addChild('author_name', $this->podcast->title);
$oembed->addChild('author_url', $this->podcast->link);
$oembed->addChild('thumbnail', $this->episode->image->large_url);
$oembed->addChild('thumbnail_width', config('Images')->largeSize);
$oembed->addChild('thumbnail_height', config('Images')->largeSize);
$oembed->addChild(
'html',
htmlentities(
'<iframe src="' .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
),
);
$oembed->addChild('width', '600');
$oembed->addChild('height', '200');
return $this->response->setXML((string) $oembed);
}
}

View File

@ -43,6 +43,6 @@ class PodcastActor extends ActorObject
$this->category = $category;
$this->episodes = url_to('podcast-episodes', $podcast->name);
$this->episodes = url_to('podcast-episodes', $podcast->handle);
}
}

View File

@ -72,7 +72,7 @@ class PodcastEpisode extends ObjectType
'chapters' => $episode->chapters_file_url,
];
$this->comments = url_to('episode-comments', $episode->podcast->name, $episode->slug);
$this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug);
if ($episode->published_at !== null) {
$this->published = $episode->published_at->format(DATE_W3C);

View File

@ -147,14 +147,14 @@ class EpisodeModel extends Model
*/
protected $beforeDelete = ['clearCache'];
public function getEpisodeBySlug(string $podcastName, string $episodeSlug): ?Episode
public function getEpisodeBySlug(string $podcastHandle, string $episodeSlug): ?Episode
{
$cacheName = "podcast-{$podcastName}_episode-{$episodeSlug}";
$cacheName = "podcast-{$podcastHandle}_episode-{$episodeSlug}";
if (! ($found = cache($cacheName))) {
$found = $this->select('episodes.*')
->join('podcasts', 'podcasts.id = episodes.podcast_id')
->where('slug', $episodeSlug)
->where('podcasts.name', $podcastName)
->where('podcasts.handle', $podcastHandle)
->where('`published_at` <= NOW()', null, false)
->first();
@ -292,7 +292,7 @@ class EpisodeModel extends Model
cache()
->deleteMatching("podcast#{$episode->podcast_id}*");
cache()
->deleteMatching("podcast-{$episode->podcast->name}*");
->deleteMatching("podcast-{$episode->podcast->handle}*");
cache()
->delete("podcast_episode#{$episode->id}");
cache()

View File

@ -35,7 +35,7 @@ class PodcastModel extends Model
'id',
'guid',
'title',
'name',
'handle',
'description_markdown',
'description_html',
'episode_description_footer_markdown',
@ -87,8 +87,8 @@ class PodcastModel extends Model
*/
protected $validationRules = [
'title' => 'required',
'name' =>
'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.name,id,{id}]',
'handle' =>
'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.handle,id,{id}]',
'description_markdown' => 'required',
'image_path' => 'required',
'language_code' => 'required',
@ -126,14 +126,14 @@ class PodcastModel extends Model
*/
protected $beforeDelete = ['clearCache'];
public function getPodcastByName(string $podcastName): ?Podcast
public function getPodcastByHandle(string $podcastHandle): ?Podcast
{
$cacheName = "podcast-{$podcastName}";
$cacheName = "podcast-{$podcastHandle}";
if (! ($found = cache($cacheName))) {
$found = $this->where('name', $podcastName)
$found = $this->where('handle', $podcastHandle)
->first();
cache()
->save("podcast-{$podcastName}", $found, DECADE);
->save("podcast-{$podcastHandle}", $found, DECADE);
}
return $found;
@ -239,7 +239,7 @@ class PodcastModel extends Model
->join('podcasts', 'podcasts.id = podcasts_users.podcast_id')
->where([
'user_id' => $userId,
'name' => $podcastId,
'handle' => $podcastId,
])
->get()
->getResultObject();
@ -385,7 +385,7 @@ class PodcastModel extends Model
cache()
->deleteMatching("podcast#{$podcast->id}*");
cache()
->delete("podcast-{$podcast->name}");
->delete("podcast-{$podcast->handle}");
}
// clear cache for every credit page
@ -413,7 +413,7 @@ class PodcastModel extends Model
$publickey = $rsaKey['publickey'];
$url = new URI(base_url());
$username = $data['data']['name'];
$username = $data['data']['handle'];
$domain =
$url->getHost() . ($url->getPort() ? ':' . $url->getPort() : '');

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="M20 12a8 8 0 1 0-3.562 6.657l1.11 1.664A9.953 9.953 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10v1.5a3.5 3.5 0 0 1-6.396 1.966A5 5 0 1 1 15 8H17v5.5a1.5 1.5 0 0 0 3 0V12zm-8-3a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@ -1,7 +1,6 @@
const Clipboard = (): void => {
const buttons: NodeListOf<HTMLButtonElement> | null = document.querySelectorAll(
"button[data-type='clipboard-copy']"
);
const buttons: NodeListOf<HTMLButtonElement> | null =
document.querySelectorAll("button[data-type='clipboard-copy']");
if (buttons) {
for (let i = 0; i < buttons.length; i++) {

View File

@ -11,15 +11,13 @@ const isBrowserLocale24h = () =>
.match(/AM/);
const DateTimePicker = (): void => {
const dateTimeContainers: NodeListOf<HTMLDivElement> = document.querySelectorAll(
"div[data-picker='datetime']"
);
const dateTimeContainers: NodeListOf<HTMLDivElement> =
document.querySelectorAll("div[data-picker='datetime']");
for (let i = 0; i < dateTimeContainers.length; i++) {
const dateTimeContainer = dateTimeContainers[i];
const dateTimeInput: HTMLInputElement | null = dateTimeContainer.querySelector(
"input[data-input]"
);
const dateTimeInput: HTMLInputElement | null =
dateTimeContainer.querySelector("input[data-input]");
if (dateTimeInput) {
const flatpickrInstance = flatpickr(dateTimeContainer, {

View File

@ -1,9 +1,8 @@
import { createPopper, Instance, Placement } from "@popperjs/core";
const Dropdown = (): void => {
const dropdownButtons: NodeListOf<HTMLButtonElement> = document.querySelectorAll(
"[data-dropdown='button']"
);
const dropdownButtons: NodeListOf<HTMLButtonElement> =
document.querySelectorAll("[data-dropdown='button']");
for (let i = 0; i < dropdownButtons.length; i++) {
const button = dropdownButtons[i];

View File

@ -1,7 +1,6 @@
const Modal = (): void => {
const modalTriggerElements: NodeListOf<HTMLElement> = document.querySelectorAll(
"[data-modal-target]"
);
const modalTriggerElements: NodeListOf<HTMLElement> =
document.querySelectorAll("[data-modal-target]");
for (let i = 0; i < modalTriggerElements.length; i++) {
const modalTrigger = modalTriggerElements[i];
@ -16,9 +15,8 @@ const Modal = (): void => {
modal.classList.toggle("hidden");
});
const closeButtonsElements: NodeListOf<HTMLElement> = modal.querySelectorAll(
"[data-modal-button]"
);
const closeButtonsElements: NodeListOf<HTMLElement> =
modal.querySelectorAll("[data-modal-button]");
for (let j = 0; j < closeButtonsElements.length; j++) {
const closeButton = closeButtonsElements[j];

View File

@ -2,9 +2,8 @@ import Choices from "choices.js";
const MultiSelect = (): void => {
// Pass single element
const multiSelects: NodeListOf<HTMLSelectElement> = document.querySelectorAll(
"select[multiple]"
);
const multiSelects: NodeListOf<HTMLSelectElement> =
document.querySelectorAll("select[multiple]");
for (let i = 0; i < multiSelects.length; i++) {
const multiSelect = multiSelects[i];

View File

@ -4,15 +4,13 @@ const PublishMessageWarning = (): void => {
);
if (publishForm) {
const messageTextArea: HTMLTextAreaElement | null = publishForm.querySelector(
"[name='message']"
);
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']"
);
const publishMessageWarning: HTMLDivElement | null =
publishForm.querySelector("[id='publish-warning']");
if (
messageTextArea &&

View File

@ -54,9 +54,8 @@ const Soundbites = (): void => {
}
}
const soundbitePlayButtons: NodeListOf<HTMLButtonElement> | null = document.querySelectorAll(
"button[data-type='play-soundbite']"
);
const soundbitePlayButtons: NodeListOf<HTMLButtonElement> | null =
document.querySelectorAll("button[data-type='play-soundbite']");
if (soundbitePlayButtons) {
for (let i = 0; i < soundbitePlayButtons.length; i++) {
const soundbitePlayButton: HTMLButtonElement = soundbitePlayButtons[i];
@ -70,15 +69,15 @@ const Soundbites = (): void => {
}
}
const inputFields: NodeListOf<HTMLInputElement> | null = document.querySelectorAll(
"input[data-type='soundbite-field']"
);
const inputFields: NodeListOf<HTMLInputElement> | null =
document.querySelectorAll("input[data-type='soundbite-field']");
if (inputFields) {
for (let i = 0; i < inputFields.length; i++) {
const inputField: HTMLInputElement = inputFields[i];
const soundbitePlayButton: HTMLButtonElement | null = document.querySelector(
`button[data-type="play-soundbite"][data-soundbite-id="${inputField.dataset.soundbiteId}"]`
);
const soundbitePlayButton: HTMLButtonElement | null =
document.querySelector(
`button[data-type="play-soundbite"][data-soundbite-id="${inputField.dataset.soundbiteId}"]`
);
if (soundbitePlayButton) {
if (inputField.dataset.fieldType == "start-time") {
inputField.addEventListener("input", () => {

View File

@ -1,16 +1,14 @@
const ThemePicker = (): void => {
const buttons: NodeListOf<HTMLButtonElement> | null = document.querySelectorAll(
"button[data-type='theme-picker']"
);
const buttons: NodeListOf<HTMLButtonElement> | null =
document.querySelectorAll("button[data-type='theme-picker']");
const iframe: HTMLIFrameElement | null = document.querySelector(
`iframe[id="embeddable_player"]`
);
const iframeTextArea: HTMLTextAreaElement | null = document.querySelector(
`textarea[id="iframe"]`
);
const urlTextArea: HTMLTextAreaElement | null = document.querySelector(
`textarea[id="url"]`
);
const urlTextArea: HTMLTextAreaElement | null =
document.querySelector(`textarea[id="url"]`);
if (buttons && iframe && iframeTextArea && urlTextArea) {
for (let i = 0; i < buttons.length; i++) {

View File

@ -1,7 +1,6 @@
const Time = (): void => {
const timeElements: NodeListOf<HTMLTimeElement> = document.querySelectorAll(
"time"
);
const timeElements: NodeListOf<HTMLTimeElement> =
document.querySelectorAll("time");
for (let i = 0; i < timeElements.length; i++) {
const timeElement = timeElements[i];

View File

@ -1,7 +1,6 @@
const Toggler = (): void => {
const togglerElements: NodeListOf<HTMLElement> = document.querySelectorAll(
"[data-toggle]"
);
const togglerElements: NodeListOf<HTMLElement> =
document.querySelectorAll("[data-toggle]");
for (let i = 0; i < togglerElements.length; i++) {
const toggler = togglerElements[i];

View File

@ -3,7 +3,7 @@
<?= $this->section('title') ?>
<?= lang('Contributor.view', [
'username' => $contributor->username,
'podcastName' => $contributor->podcast->name,
'podcastTitle' => $contributor->podcast->title,
]) ?>
<?= $this->endSection() ?>

View File

@ -91,7 +91,7 @@
) ?>"><?= lang('Episode.soundbites') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode',
$podcast->name,
$podcast->handle,
$episode->slug,
) ?>"><?= lang('Episode.go_to_page') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(

View File

@ -47,7 +47,7 @@
) ?>
<?= button(
lang('Episode.go_to_page'),
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
['variant' => 'secondary', 'iconLeft' => 'external-link'],
) ?>
<?= button(

View File

@ -56,11 +56,11 @@ $podcastNavigation = [
<span class="w-40 text-sm font-semibold truncate" title="<?= $podcast->title ?>"><?= $podcast->title ?></span>
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="inline-flex items-center text-xs underline outline-none hover:no-underline focus:ring"
data-toggle="tooltip" data-placement="bottom" title="<?= lang(
'PodcastNavigation.go_to_page',
) ?>">@<?= $podcast->name ?>
) ?>">@<?= $podcast->handle ?>
<?= icon('external-link', 'ml-1 text-gray-500') ?>
</a>
</div>

View File

@ -45,18 +45,21 @@
]) ?>
<?= form_label(
lang('Podcast.form.name'),
'name',
lang('Podcast.form.handle'),
'handle',
[],
lang('Podcast.form.name_hint'),
lang('Podcast.form.handle_hint'),
) ?>
<?= form_input([
'id' => 'name',
'name' => 'name',
'class' => 'form-input mb-4',
'value' => old('name'),
'required' => 'required',
]) ?>
<div class="relative mb-4">
<?= icon('at', 'absolute text-2xl h-full inset-0 text-gray-400 left-2') ?>
<?= form_input([
'id' => 'handle',
'name' => 'handle',
'class' => 'form-input w-full pl-8',
'value' => old('handle'),
'required' => 'required',
]) ?>
</div>
<?= form_fieldset('', ['class' => 'mb-4']) ?>
<legend>

View File

@ -51,18 +51,21 @@
<?= form_section(lang('PodcastImport.new_podcast_section_title'), '') ?>
<?= form_label(
lang('PodcastImport.name'),
'name',
lang('Podcast.form.handle'),
'handle',
[],
lang('PodcastImport.name_hint'),
lang('Podcast.form.handle_hint'),
) ?>
<?= form_input([
'id' => 'name',
'name' => 'name',
'class' => 'form-input mb-4',
'value' => old('name'),
'required' => 'required',
]) ?>
<div class="relative mb-4">
<?= icon('at', 'absolute text-2xl h-full inset-0 text-gray-400 left-2') ?>
<?= form_input([
'id' => 'handle',
'name' => 'handle',
'class' => 'form-input w-full pl-8',
'value' => old('handle'),
'required' => 'required',
]) ?>
</div>
<?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown('language', $languageOptions, old('language', $browserLang), [

View File

@ -83,7 +83,7 @@
) ?>"><?= lang('Person.persons') ?></a>
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
'episode',
$podcast->name,
$podcast->handle,
$episode->slug,
) ?>"><?= lang('Episode.go_to_page') ?></a>
<hr class="my-2 border border-gray-100">

View File

@ -37,7 +37,7 @@
$podcast->id,
) ?>" class="flex flex-col p-2 hover:underline">
<h2 class="font-semibold truncate"><?= $podcast->title ?></h2>
<p class="text-gray-600">@<?= $podcast->name ?></p>
<p class="text-gray-600">@<?= $podcast->handle ?></p>
</a>
<footer class="flex items-center justify-end p-2">
<a class="inline-flex p-2 mr-2 text-blue-700 bg-blue-100 rounded-full shadow-xs hover:bg-blue-200" href="<?= route_to(

View File

@ -8,8 +8,8 @@
$episode->description,
) ?>" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<?= service('vite')->asset('styles/index.css', 'css') ?>
<link rel="canonical" href="<?= $episode->link ?>" />
<?= service('vite')->asset('styles/index.css', 'css') ?>
</head>
<body class="flex w-full h-screen" style="background: <?= $theme[
@ -21,7 +21,7 @@
<div class="flex items-center">
<a href="<?= route_to(
'podcast',
$podcast->name,
$podcast->handle,
) ?>" style="color: <?= $theme[
'text'
] ?>;" class="mr-2 text-xs tracking-wider uppercase truncate opacity-75 hover:opacity-100" target="_blank">

View File

@ -33,7 +33,7 @@
src="<?= $podcast->image->medium_url ?>"
class="object-cover w-full h-48 mb-2" />
<h2 class="px-2 font-semibold leading-tight truncate"><?= $podcast->title ?></h2>
<p class="px-2 pb-2 text-gray-600">@<?= $podcast->name ?></p>
<p class="px-2 pb-2 text-gray-600">@<?= $podcast->handle ?></p>
</article>
</a>
<?php endforeach; ?>

View File

@ -36,10 +36,10 @@
</button>
<p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
<span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
<span class="text-xs">@<?= $podcast->name ?></span>
<span class="text-xs">@<?= $podcast->handle ?></span>
</p>
<?= anchor_popup(
route_to('follow', $podcast->name),
route_to('follow', $podcast->handle),
icon(
'social/castopod',
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',

View File

@ -79,10 +79,10 @@
</button>
<p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
<span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
<span class="text-xs">@<?= $podcast->name ?></span>
<span class="text-xs">@<?= $podcast->handle ?></span>
</p>
<?= anchor_popup(
route_to('follow', $podcast->name),
route_to('follow', $podcast->handle),
icon(
'social/castopod',
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',

View File

@ -5,7 +5,7 @@
<img src="<?= $podcast->image
->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-24 rounded-full shadow-xl xl:h-36 lg:h-28 ring-4 ring-pine-50" />
<?= anchor_popup(
route_to('follow', $podcast->name),
route_to('follow', $podcast->handle),
icon(
'social/castopod',
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
@ -25,7 +25,7 @@
lang('Common.explicit') .
'</span>'
: '') ?></h1>
<p class="mb-4 font-semibold text-gray-600">@<?= $podcast->name ?></p>
<p class="mb-4 font-semibold text-gray-600">@<?= $podcast->handle ?></p>
<div class="mb-2"><?= $podcast->description_html ?></div>
<?= location_link($podcast->location, 'text-sm mb-4') ?>
<div class="mb-6 space-x-4">
@ -49,7 +49,7 @@
]) ?></a>
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="hover:underline"><?= lang('Podcast.statuses', [
'numberOfStatuses' => $podcast->actor->statuses_count,
]) ?></a>

View File

@ -23,7 +23,7 @@
? ''
: '@' . $status->actor->domain) ?></span>
</a>
<a href="<?= route_to('status', $podcast->name, $status->id) ?>"
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
class="text-xs text-gray-500">
<time
itemprop="published"

View File

@ -23,7 +23,7 @@
? ''
: '@' . $status->actor->domain) ?></span>
</a>
<a href="<?= route_to('status', $podcast->name, $status->id) ?>"
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
class="text-xs text-gray-500">
<time
itemprop="published"

View File

@ -1,6 +1,6 @@
<footer class="mt-2 space-x-6 text-sm">
<?= anchor(
route_to('status', $podcast->name, $reply->id),
route_to('status', $podcast->handle, $reply->id),
icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
[
'class' => 'inline-flex items-center hover:underline',
@ -10,7 +10,7 @@
],
) ?>
<?= anchor_popup(
route_to('status-remote-action', $podcast->name, $reply->id, 'reblog'),
route_to('status-remote-action', $podcast->handle, $reply->id, 'reblog'),
icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count,
[
'class' => 'inline-flex items-center hover:underline',
@ -22,7 +22,7 @@
],
) ?>
<?= anchor_popup(
route_to('status-remote-action', $podcast->name, $reply->id, 'favourite'),
route_to('status-remote-action', $podcast->handle, $reply->id, 'favourite'),
icon('heart', 'text-xl mr-1 text-gray-400') . $reply->favourites_count,
[
'class' => 'inline-flex items-center hover:underline',

View File

@ -6,7 +6,7 @@
) ?>" method="POST" class="flex items-start">
<?= csrf_field() ?>
<?= anchor(
route_to('status', $podcast->name, $reply->id),
route_to('status', $podcast->handle, $reply->id),
icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
[
'class' => 'inline-flex items-center mr-6 hover:underline',
@ -40,7 +40,7 @@
'-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $reply->id .
'-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
<?= anchor(
route_to('status', $podcast->name, $reply->id),
route_to('status', $podcast->handle, $reply->id),
lang('Status.expand'),
[
'class' => 'px-4 py-1 hover:bg-gray-100',

View File

@ -40,7 +40,7 @@
<h2 class="mb-2 text-sm font-semibold"><?= lang('Podcast.listen_on') ?></h2>
<div class="grid items-center justify-center grid-cols-5 gap-3 mb-8">
<?= anchor(route_to('podcast_feed', $podcast->name), icon('rss'), [
<?= anchor(route_to('podcast_feed', $podcast->handle), icon('rss'), [
'class' =>
'bg-yellow-500 text-xl text-yellow-900 hover:bg-yellow-600 w-8 h-8 inline-flex items-center justify-center rounded-lg',
'target' => '_blank',

View File

@ -16,7 +16,7 @@
? ''
: '@' . $status->actor->domain) ?></span>
</a>
<a href="<?= route_to('status', $podcast->name, $status->id) ?>"
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
class="text-xs text-gray-500">
<time
itemprop="published"

View File

@ -1,6 +1,6 @@
<footer class="flex justify-around px-6 py-3">
<?= anchor(
route_to('status', $podcast->name, $status->id),
route_to('status', $podcast->handle, $status->id),
icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count,
[
'class' => 'inline-flex items-center hover:underline',
@ -10,7 +10,7 @@
],
) ?>
<?= anchor_popup(
route_to('status-remote-action', $podcast->name, $status->id, 'reblog'),
route_to('status-remote-action', $podcast->handle, $status->id, 'reblog'),
icon('repeat', 'text-2xl mr-1 text-gray-400') . $status->reblogs_count,
[
'class' => 'inline-flex items-center hover:underline',
@ -22,7 +22,7 @@
],
) ?>
<?= anchor_popup(
route_to('status-remote-action', $podcast->name, $status->id, 'favourite'),
route_to('status-remote-action', $podcast->handle, $status->id, 'favourite'),
icon('heart', 'text-2xl mr-1 text-gray-400') . $status->favourites_count,
[
'class' => 'inline-flex items-center hover:underline',

View File

@ -6,7 +6,7 @@
) ?>" method="POST" class="flex justify-around">
<?= csrf_field() ?>
<?= anchor(
route_to('status', $podcast->name, $status->id),
route_to('status', $podcast->handle, $status->id),
icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count,
[
'class' => 'inline-flex items-center hover:underline',
@ -40,7 +40,7 @@
'-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $status->id .
'-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
<?= anchor(
route_to('status', $podcast->name, $status->id),
route_to('status', $podcast->handle, $status->id),
lang('Status.expand'),
[
'class' => 'px-4 py-1 hover:bg-gray-100',

View File

@ -16,7 +16,7 @@
? ''
: '@' . $status->actor->domain) ?></span>
</a>
<a href="<?= route_to('status', $podcast->name, $status->id) ?>"
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
class="text-xs text-gray-500">
<time
itemprop="published"

View File

@ -3,7 +3,7 @@
<div class="px-6 pt-8 pb-4 bg-gray-50">
<?= anchor_popup(
route_to('status-remote-action', $podcast->name, $status->id, 'reply'),
route_to('status-remote-action', $podcast->handle, $status->id, 'reply'),
lang('Status.reply_to', ['actorUsername' => $status->actor->username]),
[
'class' =>

View File

@ -25,13 +25,13 @@
<nav class="sticky top-0 z-20 flex justify-center pt-2 text-lg bg-pine-50">
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 mr-8 font-semibold border-b-4 text-pine-800 border-pine-800"><?= lang(
'Podcast.activity',
) ?></a>
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 rounded-full hover:bg-pine-100"><?= lang(
'Podcast.episodes',
) ?></a>

View File

@ -25,13 +25,13 @@
<nav class="sticky top-0 z-20 flex justify-center pt-2 text-lg bg-pine-50">
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 mr-8 font-semibold border-b-4 text-pine-800 border-pine-800"><?= lang(
'Podcast.activity',
) ?></a>
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 rounded-full hover:bg-pine-100"><?= lang(
'Podcast.episodes',
) ?></a>

View File

@ -20,10 +20,10 @@
<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
<link rel="alternate" type="application/json+oembed" href="<?= base_url(
route_to('episode-oembed-json', $podcast->name, $episode->slug),
route_to('episode-oembed-json', $podcast->handle, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed json" />
<link rel="alternate" type="text/xml+oembed" href="<?= base_url(
route_to('episode-oembed-xml', $podcast->name, $episode->slug),
route_to('episode-oembed-xml', $podcast->handle, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed xml" />
<meta name="twitter:title" content="<?= $episode->title ?>" />
<meta name="twitter:description" content="<?= $episode->description ?>" />
@ -42,7 +42,7 @@
<div class="max-w-2xl mx-auto">
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon(
'arrow-left',
'mr-2 text-lg',
@ -73,7 +73,7 @@
</div>
<div class="mb-2 space-x-4 text-sm">
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('chat', 'text-xl mr-1 text-gray-400') .
$episode->statuses_total,
[
@ -85,7 +85,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('repeat', 'text-xl mr-1 text-gray-400') .
$episode->reblogs_total,
[
@ -98,7 +98,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('heart', 'text-xl mr-1 text-gray-400') .
$episode->favourites_total,
[

View File

@ -20,10 +20,10 @@
<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
<link rel="alternate" type="application/json+oembed" href="<?= base_url(
route_to('episode-oembed-json', $podcast->name, $episode->slug),
route_to('episode-oembed-json', $podcast->handle, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed json" />
<link rel="alternate" type="text/xml+oembed" href="<?= base_url(
route_to('episode-oembed-xml', $podcast->name, $episode->slug),
route_to('episode-oembed-xml', $podcast->handle, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed xml" />
<meta name="twitter:title" content="<?= $episode->title ?>" />
<meta name="twitter:description" content="<?= $episode->description ?>" />
@ -42,7 +42,7 @@
<div class="max-w-2xl mx-auto">
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon(
'arrow-left',
'mr-2 mb- text-lg',
@ -73,7 +73,7 @@
</div>
<div class="mb-2 space-x-4 text-sm">
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('chat', 'text-xl mr-1 text-gray-400') .
$episode->statuses_total,
[
@ -85,7 +85,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('repeat', 'text-xl mr-1 text-gray-400') .
$episode->reblogs_total,
[
@ -98,7 +98,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('heart', 'text-xl mr-1 text-gray-400') .
$episode->favourites_total,
[
@ -130,7 +130,7 @@
<div class="tab-panels">
<section id="activity" class="space-y-8 tab-panel">
<?= form_open(route_to('status-attempt-create', $podcast->name), [
<?= form_open(route_to('status-attempt-create', $podcast->handle), [
'class' => 'flex p-4 bg-white shadow rounded-xl',
]) ?>
<?= csrf_field() ?>

View File

@ -24,13 +24,13 @@
<nav class="sticky top-0 flex items-center justify-center pt-2 text-lg bg-pine-50">
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 mr-8 rounded-full hover:bg-pine-100"><?= lang(
'Podcast.activity',
) ?></a>
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 font-semibold border-b-4 text-pine-800 border-pine-800"><?= lang(
'Podcast.episodes',
) ?></a>
@ -118,7 +118,7 @@
</div>
<div class="px-4 py-2 space-x-4 text-sm">
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('chat', 'text-xl mr-1 text-gray-400') .
$episode->statuses_total,
[
@ -130,7 +130,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('repeat', 'text-xl mr-1 text-gray-400') .
$episode->reblogs_total,
[
@ -144,7 +144,7 @@
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('heart', 'text-xl mr-1 text-gray-400') .
$episode->favourites_total,
[

View File

@ -24,13 +24,13 @@
<nav class="sticky top-0 flex items-center justify-center pt-2 text-lg bg-pine-50">
<a href="<?= route_to(
'podcast-activity',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 mr-8 rounded-full hover:bg-pine-100"><?= lang(
'Podcast.activity',
) ?></a>
<a href="<?= route_to(
'podcast-episodes',
$podcast->name,
$podcast->handle,
) ?>" class="px-4 py-1 font-semibold border-b-4 text-pine-800 border-pine-800"><?= lang(
'Podcast.episodes',
) ?></a>
@ -118,7 +118,7 @@
</div>
<div class="px-4 py-2 space-x-4 text-sm">
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('chat', 'text-xl mr-1 text-gray-400') .
$episode->statuses_total,
[
@ -130,7 +130,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('repeat', 'text-xl mr-1 text-gray-400') .
$episode->reblogs_total,
[
@ -143,7 +143,7 @@
],
) ?>
<?= anchor(
route_to('episode', $podcast->name, $episode->slug),
route_to('episode', $podcast->handle, $episode->slug),
icon('heart', 'text-xl mr-1 text-gray-400') .
$episode->favourites_total,
[

View File

@ -20,7 +20,7 @@
<?= $this->section('content') ?>
<div class="max-w-2xl px-6 mx-auto">
<nav class="py-3">
<a href="<?= route_to('podcast-activity', $podcast->name) ?>"
<a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
'arrow-left',
'mr-2 text-lg',

View File

@ -20,7 +20,7 @@
<?= $this->section('content') ?>
<div class="max-w-2xl px-6 mx-auto">
<nav class="py-3">
<a href="<?= route_to('podcast-activity', $podcast->name) ?>"
<a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
'arrow-left',
'mr-2 text-lg',

View File

@ -0,0 +1,470 @@
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://podlibre.org/
*/
/* Autogenerated from https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json on 2021-09-06T09:10:53+00:00 */
return array (
'persons' =>
array (
'creative_direction' =>
array (
'label' => 'Creative Direction',
'roles' =>
array (
'director' =>
array (
'label' => 'Director',
'description' => 'The Director is the head of the entire creative production, from creative details to logistics. There is typically a single director for a production. This role is primarily seen in fiction podcasts.',
'example' => 'Jenna Knorr for "Welcome to Tinsel Town"',
),
'assistant_director' =>
array (
'label' => 'Assistant Director',
'description' => 'The Assistant Director is a liaison between the director and the rest of the production, often coordinating the daily logistics of production. There may be multiple assistant directors on a project. This role is primarily seen in fiction podcasts.',
'example' => 'William Wright for "Inn Between"',
),
'executive_producer' =>
array (
'label' => 'Executive Producer',
'description' => 'The Executive Producer is the lead producer on a production. The role can range in terms of creative control with some "EP"s owning the creative direction of a podcast (in effect taking the role of director), while others may take a more hands off approach. Executive producer may have raised the money to fund the production, but it is not a necessary responsibility of the role.',
'example' => 'Jane Rotonda for "The Larry Meiller Show',
),
'senior_producer' =>
array (
'label' => 'Senior Producer',
'description' => 'The Senior Producer is the second most senior producer of the production (second to the Executive Producer). They supervise producers and the general direciton and logistics of the entire production.',
'example' => 'Dr. Jeremy Weisz from "INspired INsider"',
),
'producer' =>
array (
'label' => 'Producer',
'description' => 'The Producer coordinates and executes the production of the podcast. There duties can include helping craft the creative direction of a project, budgeting, research, scheduling, and overseeing editing and final production.',
'example' => '',
),
'associate_producer' =>
array (
'label' => 'Associate Producer',
'description' => 'The Associate Producer performs one or more producer functions as delegated to them by a Producer.',
'example' => 'Alex Baumhardt for "APM Reports"',
),
'development_producer' =>
array (
'label' => 'Development Producer',
'description' => 'The Development Producer coordinates and executes the pre-production create direction of a podcast. Their responsibilities include finding new episode and series ideas and working with writers and researchers to prepare the concept for production.',
'example' => '',
),
'creative_director' =>
array (
'label' => 'Creative Director',
'description' => 'The Creative Director is responsible for the creative strategy and execution of an entire series. Often this role reaches outside of content to affect accompanying artwork, music, marketing campaigns, and more.',
'example' => 'Neil Druckmann on "The Official The Last of Us"',
),
),
),
'cast' =>
array (
'label' => 'Cast',
'roles' =>
array (
'host' =>
array (
'label' => 'Host',
'description' => 'The Host is the on-air master of ceremonies of the podcast and a consistent presence on every episode (with the exception of guest hosts and alternative episodes). The Host\'s duties may include conducting interviews, introducing stories and segments, narrating, and more. There may be more than one Host per podcast or episode.',
'example' => 'Joe Rogan for "The Joe Rogan Experience"',
),
'co_host' =>
array (
'label' => 'Co-Host',
'description' => 'The Co-Host performs many of the same duties as the host, while taking a secondary presence on the podcast.',
'example' => 'Dax Shepard for "Armchair Expert"',
),
'guest_host' =>
array (
'label' => 'Guest Host',
'description' => 'The Guest Host performs all of the duties of the traditional Host role, but does so in a temporary capacity. Often as a single appearance or a short span of episodes.',
'example' => 'Erica Kelly on "Let\'s Taco \'Bout Women and True Crime"',
),
'guest' =>
array (
'label' => 'Guest',
'description' => 'The Guest is an outside party who makes an on-air appearance on an episode, often as a participant in a panel or the interview subject.',
'example' => 'Lewis Brindley for "Triforce!"',
),
'voice_actor' =>
array (
'label' => 'Voice Actor',
'description' => 'The Voice Actor gives a performance in which they lend their voice to the role of a character on a podcast episode. While the majority of voice acting roles will be fictional, the role of voice actor may also cover reenactments of real conversations and people.',
'example' => 'Venk Potula for "Masala Jones"',
),
'narrator' =>
array (
'label' => 'Narrator',
'description' => 'The Narrator gives a performance in which tell the exposition of a fictional or non-fictional story, often in a scripted manner. The Narrator may also perform voices of characters within the story, provided they still maintain the role of exposition storyteller or "voice of God".',
'example' => 'James Harvey Freetly for "Lakeshore & Limbo"',
),
'announcer' =>
array (
'label' => 'Announcer',
'description' => 'The Announcer gives short vocal performances for the introduction of the podcast, episode topics, segments, guests, prizes, etc. The Announcer is secondary to the host of the podcast and often performs their introductions in a scripted, produced manner.',
'example' => 'Lydia Kapp for "World Builders Anonymous"',
),
'reporter' =>
array (
'label' => 'Reporter',
'description' => 'The Reporter finds and investigates news or stories for the podcast, often interviewing subjects and conducting research. The Reporter can be an on-air position as well, as they convey the insights of their investigation.',
'example' => '',
),
),
),
'writing' =>
array (
'label' => 'Writing',
'roles' =>
array (
'author' =>
array (
'label' => 'Author',
'description' => 'The Author has written prose or poetry originally intended for text that is now being read verbatim on air.',
'example' => 'Heiko Martens for "The Sigmund Freud Files"',
),
'editorial_director' =>
array (
'label' => 'Editorial Director',
'description' => 'The Editorial Director heads all departments of the organization behind the podcast and is held accountable for delegating tasks to staff members and managing them. They are the highest-ranking editor and are responsible for the direction, accuracy, and decisions behind podcast content.',
'example' => 'Christopher Twarowski for "News Beat"',
),
'co_writer' =>
array (
'label' => 'Co-Writer',
'description' => 'The Co-Writer has written a podcast in partnership with 1-2 other writers, sharing credit together for the creative arc, dialogue, and narration.',
'example' => 'Max Eggers on "THE LIGHTHOUSE"',
),
'writer' =>
array (
'label' => 'Writer',
'description' => 'The Writer has written the story or dialogue of a podcast. The Writer is often involved in the creative arc of a production, but this is not a necessary requirement. Writers may work in fictional or non-fictional podcasts.',
'example' => '',
),
'songwriter' =>
array (
'label' => 'Songwriter',
'description' => 'The Songwriter has written the lyrics and/or accompanying music to an original song created for the podcast and played on an episode.',
'example' => 'Ben Lapidus for "Gay Future"',
),
'guest_writer' =>
array (
'label' => 'Guest Writer',
'description' => 'The Guest Writer performs the duties of a writer in a temporary capacity, often as a single episode or a short span of episodes. The distinction between writer and Guest Writer depends on the decision of the podcast itself.',
'example' => 'Beth Crane for "The Unseen Hour"',
),
'story_editor' =>
array (
'label' => 'Story Editor',
'description' => 'The Story Editor is responsible for broad stroke direction of the story arc and character development of a podcast. Often seen in fiction and documentary podcasts.',
'example' => 'Gabrielle Loux for "The NoSleep Podcast"',
),
'managing_editor' =>
array (
'label' => 'Managing Editor',
'description' => 'The Managing Editor oversees and coordinates the podcasts editorial activities, providing both detailed editing and managing a staff of writers and editors to ensure proper deadlines and budgets are being met.',
'example' => 'Flora Lichtman for "Every Little Thing"',
),
'script_editor' =>
array (
'label' => 'Script Editor',
'description' => 'The Script Editor provides notes and editing to the recording script in a very "hands on" role. The Script Editor is primarily used in fiction, documentary, and advertisements where scripted recordings are prevalent.',
'example' => 'Alex Rioux for "Welcome to Tinsel Town: A Christmas Adventure"',
),
'script_coordinator' =>
array (
'label' => 'Script Coordinator',
'description' => 'The Script Coordinator packages the final script with annotations that reflect specific logistics and creative cues for recording and production.',
'example' => 'Alex Rioux for "Welcome to Tinsel Town: A Christmas Adventure"',
),
'researcher' =>
array (
'label' => 'Researcher',
'description' => 'The Researcher coordinates the sourcing and verification of information that can then be used for the content of a podcast episode, often informing the direction of a story based on new insights uncovered.',
'example' => 'Dave Grave for "The Zero Brain Podcast"',
),
'editor' =>
array (
'label' => 'Editor',
'description' => 'The Editor reviews and prepares scripts for conveying information in a creative, accurate, and engaging manner.',
'example' => '',
),
'fact_checker' =>
array (
'label' => 'Fact Checker',
'description' => 'The Fact Checker reviews the content of a podcast for factual correctness and verifies that quote attribution is correct. They use a variety of tools including 3rd party research and individual outreach. Often the Fact Checker will also provide notes on how the production can avoid the confusion in the delivery of information in the episode.',
'example' => '',
),
'translator' =>
array (
'label' => 'Translator',
'description' => 'The Translator converts content from one language to another for the podcast. This can be interviews, dialogue, text documents, and more. The Translator\'s work may be used on-air or behind-the-scenes during the production/research process.',
'example' => '',
),
'transcriber' =>
array (
'label' => 'Transcriber',
'description' => 'The Transcriber turns dialogue and audio cues into text, which can be used internally for production processes or displayed publicly for listeners.',
'example' => '',
),
'logger' =>
array (
'label' => 'Logger',
'description' => 'The Logger reviews and documents the contents and timestamps of raw audio in service of producers and editors in the production process.',
'example' => '',
),
),
),
'audio_production' =>
array (
'label' => 'Audio Production',
'roles' =>
array (
'studio_coordinator' =>
array (
'label' => 'Studio Coordinator',
'description' => 'The Studio Coordinator manages the recording studio and audio technicians working within the studio at the time of recording.',
'example' => '',
),
'technical_director' =>
array (
'label' => 'Technical Director',
'description' => 'The Technical Director oversees the podcast\'s recording and production as it is involved with audio technologies including hardware and software, and managing roles involved these areas.',
'example' => 'Adam Raymonda on "Celebuzz\'d"',
),
'technical_manager' =>
array (
'label' => 'Technical Manager',
'description' => 'The Technical Manager coordinates a team of audio engineers and studio staff, in the recording and production as it is involved with audio technologies including hardware and software.',
'example' => '',
),
'audio_engineer' =>
array (
'label' => 'Audio Engineer',
'description' => 'The Audio Engineer helps record and produce audio by setting up recording environments, monitoring recoding, and providing technical adjustments throughout. The Audio Engineer is present during the recording process, most often making adjustments in real time. The Audio Engineer may work with conversation, music, foley, or any other type of audio.',
'example' => 'Peter Leonard from "Startup Podcast"',
),
'remote_recording_engineer' =>
array (
'label' => 'Remote Recording Engineer',
'description' => 'The Remote Recording Engineer ensures the proper recording of conversations taking place in multiple locations across a phone line or internet connection. The Remote Recording Engineer evaluates the different recording set ups and attempts to reconcile them into a cohesive sound, while also monitoring the recording process to capture the best possible audio.',
'example' => '',
),
'post_production_engineer' =>
array (
'label' => 'Post Production Engineer',
'description' => 'The Post Production Engineer evaluates audio technologies and their application as it pertains to the final steps of production and publication.',
'example' => 'Dick Wound for "Queens Next Door"',
),
),
),
'audio_post_production' =>
array (
'label' => 'Audio Post-Production',
'roles' =>
array (
'audio_editor' =>
array (
'label' => 'Audio Editor',
'description' => 'The Audio Editor cuts and rearranges audio for clarity and storytelling purposes. The Audio Editor may also perform general audio processing and mastering.',
'example' => '',
),
'sound_designer' =>
array (
'label' => 'Sound Designer',
'description' => 'The Sound Designer creates and composes a variety of audio elements. These elements are mostly secondary to speech, but a Sound Designer may creatively edit/produce speech elements in an artist manner.',
'example' => '',
),
'foley_artist' =>
array (
'label' => 'Foley Artist',
'description' => 'The Foley Artist sound effects for a podcast and can do so both via physical recording and digital processing, or a combination of the two.',
'example' => '',
),
'composer' =>
array (
'label' => 'Composer',
'description' => 'The Composer writes an original musical piece (or multiple) that is played on the published episode. The Composer will also often be the performer of said musical piece.',
'example' => 'Marcus Thorne Bagala from "This American Life"',
),
'theme_music' =>
array (
'label' => 'Theme Music',
'description' => 'Theme Music is a musical piece that accompanies the podcast across multiple episodes, most often at the beginning of an episode. The Theme Music is used to introduce the podcast as a brand. This role is for the creator of the theme music.',
'example' => 'Mark Philips from "Startup Podcast"',
),
'music_production' =>
array (
'label' => 'Music Production',
'description' => 'The Music Production role helps creatively craft music in a role separate from the writing of said music. Music Production often involves creative decisions per the method in which music is recorded, the arrangement of instruments, the use of effects, and more.',
'example' => 'Storm Duper for "Faking Star Wars Radio"',
),
'music_contributor' =>
array (
'label' => 'Music Contributor',
'description' => 'The Music Contributor is the creator of music that was used for the podcast but not necessarily produced specifically for the podcast. Often a podcast will use an existing musical piece and credit the original creator.',
'example' => 'Bobby Lord from "Startup Podcast"',
),
),
),
'administration' =>
array (
'label' => 'Administration',
'roles' =>
array (
'production_coordinator' =>
array (
'label' => 'Production Coordinator',
'description' => 'The Production Coordinator is responsible for managing the logistics of the production process from recording to publication, including attaining the required permissions and permits, connecting the various production and recording teams, coordinating the creation of post-production metadata, budgeting, and more.',
'example' => 'Taneya Boyde on "Ready For Change?"',
),
'booking_coordinator' =>
array (
'label' => 'Booking Coordinator',
'description' => 'The Booking Coordinator is responsible for bringing on new guests for interviews, including sourcing guests, scheduling interviews, onboarding materials, and post-publication processes.',
'example' => 'Meryl Klemow for "Campfire Sht Show"',
),
'production_assistant' =>
array (
'label' => 'Production Assistant',
'description' => 'The Production Assistant helps support an executive member of a podcast (often a director or producer), helping prepare them in a variety of ways including scheduling, logistics, communications, and more.',
'example' => 'Wallace Mack for "The Nod"',
),
'content_manager' =>
array (
'label' => 'Content Manager',
'description' => 'The Content Manager is responsible for the distribution of a podcast\'s content within and outside of episode, including but not limited to clips, newsletters, images, cross-promotions, and more.',
'example' => 'Kenneth Lee Johnson II for "Malice Corp Smack Talk"',
),
'marketing_manager' =>
array (
'label' => 'Marketing Manager',
'description' => 'The Marketing Manager is responsibile for the promotion of a podcast\'s content through various awareness strategies such as social media campaigns, cultivating a web presence, managing public relations and communications strategies, and other creative techniques to acquire and retain listeners.',
'example' => '',
),
'sales_representative' =>
array (
'label' => 'Sales Representative',
'description' => 'The Sales Representative is responsible for monetization of podcast content through managing and selling advertising inventory.',
'example' => '',
),
'sales_manager' =>
array (
'label' => 'Sales Manager',
'description' => 'The Sales Manager is responsible for all aspects of podcast monetization such as overseeing Sales Representatives, managing advertising inventory, and devising monetization strategies through channels such as affiliate partnerships, merchandise, live events, and other revenue strategies.',
'example' => '',
),
),
),
'visuals' =>
array (
'label' => 'Visuals',
'roles' =>
array (
'graphic_designer' =>
array (
'label' => 'Graphic Designer',
'description' => 'The Graphic Designer is someone who has created any custom visuals to accompany the podcast in a variety of ways.',
'example' => 'Sky Knight for "The XP Billionaires"',
),
'cover_art_designer' =>
array (
'label' => 'Cover Art Designer',
'description' => 'The Cover Art Designer creates the displayed cover art of a podcast or episode. For clarity, cover art is the main image (almost always square in dimensions) accompanying the podcast in directories, while episode cover art is displayed in a similar manner at the episode level. This role may be a digital designer, artist, photographer or any other visual creative.',
'example' => '',
),
),
),
'community' =>
array (
'label' => 'Community',
'roles' =>
array (
'social_media_manager' =>
array (
'label' => 'Social Media Manager',
'description' => 'The Social Media Manager runs the social media accounts of the podcast, including but not limited to the creation of content, posting, replies, monitoring, and more.',
'example' => 'Tom Joshi-Cale for "World on a String"',
),
),
),
'misc' =>
array (
'label' => 'Misc.',
'roles' =>
array (
'consultant' =>
array (
'label' => 'Consultant',
'description' => 'A Consultant is a third-party position where someone from outside the organization works on a project, often offering a specific expertise. This is a modifier role and can be applied to any work area.',
'example' => 'Ross Wilcock for "Being Kenzie-Feature Length Immersive Horror"',
),
'intern' =>
array (
'label' => 'Intern',
'description' => 'An Intern is an apprentice position where someone works for a limited time within an organization to gain work experience in a specific field. This is a modifier role and can be applied to any work area.',
'example' => '',
),
),
),
'video_production' =>
array (
'label' => 'Video Production',
'roles' =>
array (
'camera_operator' =>
array (
'label' => 'Camera Operator',
'description' => 'A camera operator is responsible for capturing and recording all aspects of a scene for film and television. They must understand the technicalities of how to operate a camera, frame a proper shot with respect to lighting and staging, focus the lens and have a visual eye to achieve a specific look.',
'example' => '',
),
'lighting_designer' =>
array (
'label' => 'Lighting Designer',
'description' => 'A lighting designer works with the DP and Director to craft a specific look and feel of a scene utilizing various lighting techniques. They must be able to interpret the creative direction and bring it to life.',
'example' => '',
),
'camera_grip' =>
array (
'label' => 'Camera Grip',
'description' => 'A camera grip is responsible for building and maintaining all the parts of a camera and its accessories such as the tripods, cranes, dollies, etc.',
'example' => '',
),
'assistant_camera' =>
array (
'label' => 'Assistant Camera',
'description' => '1st AC is responsible for the camera equipment, building the cameras before the start of each day, organizing all the parts and various accessories, swapping out lenses when necessary and also pulls focus for the DP and camera operators. The AC will also wrap out each day by cleaning the cameras, writing camera notes, marking the media cards, and delivering them to the DIT.',
'example' => '',
),
),
),
'video_post_production' =>
array (
'label' => 'Video Post-Production',
'roles' =>
array (
'editor' =>
array (
'label' => 'Editor',
'description' => 'Television editors are responsible for taking the shot footage and clips and blending them together to craft the director\'s vision and storytelling.',
'example' => '',
),
'assistant_editor' =>
array (
'label' => 'Assistant Editor',
'description' => 'The Assistant Editor is responsible for taking the media from the set, ingesting them into the designated editing software, and organizing the footage in an efficient way for the editor. They must also pay close attention to ensure that audio and video are synced and that all footage from set is ingested properly.',
'example' => '',
),
),
),
),
);

View File

@ -0,0 +1,470 @@
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://podlibre.org/
*/
/* Autogenerated from https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json on 2021-09-06T09:10:53+00:00 */
return array (
'persons' =>
array (
'creative_direction' =>
array (
'label' => 'Direction de Création',
'roles' =>
array (
'director' =>
array (
'label' => 'Réalisat·eur·rice',
'description' => 'Le directeur est à la tête de toute la production créative, des détails créatifs à la logistique. Il n\'y a généralement qu\'un seul réalisateur pour une production. Ce rôle est principalement vu dans les podcasts de fiction.',
'example' => 'Jenna Knorr pour «Bienvenue à Tinsel Town»',
),
'assistant_director' =>
array (
'label' => 'Réalisat·eur·rice-Assistant·e',
'description' => 'Le directeur adjoint est une liaison entre le réalisateur et le reste de la production, coordonnant souvent la logistique quotidienne de la production. Il peut y avoir plusieurs directeurs adjoints sur un projet. Ce rôle est principalement vu dans les podcasts de fiction.',
'example' => 'William Wright pour «Inn Between»',
),
'executive_producer' =>
array (
'label' => 'Product·eur·rice exécut·if·ive',
'description' => 'Le producteur exécutif est le producteur principal d\'une production. Le rôle peut varier en termes de contrôle créatif, certains «EP» détenant la direction créative d\'un podcast (en fait en assumant le rôle de réalisateur), tandis que d\'autres peuvent adopter une approche plus pratique. Le producteur exécutif a peut-être collecté des fonds pour financer la production, mais ce n\'est pas une responsabilité nécessaire du rôle.',
'example' => 'Jane Rotonda pour «The Larry Meiller Show»',
),
'senior_producer' =>
array (
'label' => 'Premi·er·ère Réalisat·eur·rice',
'description' => 'Le producteur principal est le deuxième producteur le plus ancien de la production (deuxième après le producteur exécutif). Ils supervisent les producteurs et la direction générale et la logistique de l\'ensemble de la production.',
'example' => 'Dr. Jeremy Weisz de «INspired INsider»',
),
'producer' =>
array (
'label' => 'Product·eur·rice',
'description' => 'Le producteur coordonne et exécute la production du podcast. Ces tâches peuvent inclure l\'aide à l\'élaboration de la direction créative d\'un projet, la budgétisation, la recherche, la planification et la supervision de l\'édition et de la production finale.',
'example' => '',
),
'associate_producer' =>
array (
'label' => 'Product·eur·rice Délégué·e',
'description' => 'Le producteur associé remplit une ou plusieurs fonctions de producteur qui lui sont déléguées par un producteur.',
'example' => 'Alex Baumhardt pour «APM Reports»',
),
'development_producer' =>
array (
'label' => 'Product·eur·rice au Développement',
'description' => 'Le producteur de développement coordonne et exécute la direction de création de pré-production d\'un podcast. Leurs responsabilités consistent à trouver de nouvelles idées d\'épisodes et de séries et à travailler avec des écrivains et des chercheurs pour préparer le concept pour la production.',
'example' => '',
),
'creative_director' =>
array (
'label' => 'Direct·eur·rice de la Création',
'description' => 'Le directeur de la création est responsable de la stratégie créative et de l\'exécution de toute une série. Souvent, ce rôle dépasse le contenu pour affecter les œuvres d\'art, la musique, les campagnes marketing, etc.',
'example' => 'Neil Druckmann sur «The Official The Last of Us»',
),
),
),
'cast' =>
array (
'label' => 'Distribution',
'roles' =>
array (
'host' =>
array (
'label' => 'Présentat·eur·rice',
'description' => 'L\'hôte est le maître des cérémonies à l\'antenne du podcast et une présence constante sur chaque épisode (à l\'exception des hôtes invités et des épisodes alternatifs). Les tâches de l\'hôte peuvent inclure la réalisation d\'entrevues, l\'introduction d\'histoires et de segments, la narration, etc. Il peut y avoir plus d\'un hôte par podcast ou épisode.',
'example' => 'Joe Rogan pour «The Joe Rogan Experience»',
),
'co_host' =>
array (
'label' => 'Co-Présentat·eur·rice',
'description' => 'Le co-animateur remplit bon nombre des mêmes tâches que l\'hôte, tout en prenant une présence secondaire sur le podcast.',
'example' => 'Dax Shepard pour «Armchair Expert»',
),
'guest_host' =>
array (
'label' => 'Présentat·eur·rice Exceptionnel·le',
'description' => 'L\'hôte invité remplit toutes les fonctions du rôle d\'hôte traditionnel, mais le fait à titre temporaire. Souvent en une seule apparition ou en une courte période d\'épisodes.',
'example' => 'Erica Kelly sur «Let\'s Taco \'Bout Women and True Crime»',
),
'guest' =>
array (
'label' => 'Invité·e',
'description' => 'L\'invité est une partie extérieure qui fait une apparition à l\'antenne sur un épisode, souvent en tant que participant à un panel ou sujet de l\'interview.',
'example' => 'Lewis Brindley pour «Triforce!"',
),
'voice_actor' =>
array (
'label' => 'Comédien·ne Voix',
'description' => 'The Voice Actor donne une performance dans laquelle ils prêtent leur voix au rôle d\'un personnage dans un épisode de podcast. Alors que la majorité des rôles de doublage seront fictifs, le rôle de doubleur peut également couvrir des reconstitutions de conversations et de personnes réelles.',
'example' => 'Venk Potula pour «Masala Jones»',
),
'narrator' =>
array (
'label' => 'Narrat·eur·rice',
'description' => 'The Narrator donne une performance dans laquelle racontent l\'exposition d\'une histoire fictive ou non fictive, souvent de manière scénarisée. Le narrateur peut également interpréter des voix de personnages dans l\'histoire, à condition qu\'ils conservent toujours le rôle de conteur d\'exposition ou de «voix de Dieu».',
'example' => 'James Harvey Freetly pour «Lakeshore & Limbo»',
),
'announcer' =>
array (
'label' => 'Annonc·eur·euse',
'description' => 'L\'annonceur donne de courtes performances vocales pour l\'introduction du podcast, des sujets d\'épisode, des segments, des invités, des prix, etc. L\'annonceur est secondaire par rapport à l\'hôte du podcast et effectue souvent ses introductions d\'une manière scénarisée et produite.',
'example' => 'Lydia Kapp pour «World Builders Anonymous»',
),
'reporter' =>
array (
'label' => 'Journaliste',
'description' => 'Le journaliste trouve et étudie des nouvelles ou des histoires pour le podcast, interviewant souvent des sujets et menant des recherches. Le journaliste peut également être un poste à l\'antenne, car il transmet les idées de son enquête.',
'example' => '',
),
),
),
'writing' =>
array (
'label' => 'Écriture',
'roles' =>
array (
'author' =>
array (
'label' => 'Aut·eur·rice',
'description' => 'L\'auteur a écrit de la prose ou de la poésie initialement destinée à un texte qui est maintenant lu textuellement à l\'antenne.',
'example' => 'Heiko Martens pour «The Sigmund Freud Files»',
),
'editorial_director' =>
array (
'label' => 'Direct·eur·rice de la Rédaction',
'description' => 'Le directeur de la rédaction dirige tous les départements de l\'organisation derrière le podcast et est responsable de la délégation des tâches aux membres du personnel et de leur gestion. Ils sont l\'éditeur le plus haut placé et sont responsables de la direction, de l\'exactitude et des décisions derrière le contenu de podcast.',
'example' => 'Christopher Twarowski pour «News Beat»',
),
'co_writer' =>
array (
'label' => 'Co-rédact·eur·rice',
'description' => 'Le co-scénariste a écrit un podcast en partenariat avec 1 ou 2 autres écrivains, partageant ainsi le mérite de l\'arc créatif, du dialogue et de la narration.',
'example' => 'Max Eggers dans «THE LIGHTHOUSE»',
),
'writer' =>
array (
'label' => 'Rédact·eur·rice',
'description' => 'The Writer a écrit l\'histoire ou le dialogue d\'un podcast. L\'écrivain est souvent impliqué dans l\'arc créatif d\'une production, mais ce n\'est pas une condition nécessaire. Les écrivains peuvent travailler dans des podcasts fictifs ou non fictifs.',
'example' => '',
),
'songwriter' =>
array (
'label' => 'Aut·eur·rice Composit·eur·rice',
'description' => 'L\'auteur-compositeur a écrit les paroles et / ou la musique d\'accompagnement d\'une chanson originale créée pour le podcast et jouée sur un épisode.',
'example' => 'Ben Lapidus pour «Gay Future»',
),
'guest_writer' =>
array (
'label' => 'Rédact·eur·rice Invité·e',
'description' => 'L\'écrivain invité remplit les fonctions d\'écrivain à titre temporaire, souvent sous la forme d\'un épisode unique ou d\'une courte période d\'épisodes. La distinction entre écrivain et écrivain invité dépend de la décision du podcast lui-même.',
'example' => 'Beth Crane pour «The Unseen Hour»',
),
'story_editor' =>
array (
'label' => 'Rédact·eur·rice en Chef ',
'description' => 'L\'éditeur d\'histoire est responsable de la direction générale de l\'arc de l\'histoire et du développement des personnages d\'un podcast. Souvent vu dans les podcasts de fiction et documentaires.',
'example' => 'Gabrielle Loux pour «The NoSleep Podcast»',
),
'managing_editor' =>
array (
'label' => 'Direct·eur·rice de la Publication',
'description' => 'Le rédacteur en chef supervise et coordonne les activités éditoriales des podcasts, en fournissant à la fois une édition détaillée et la gestion d\'une équipe de rédacteurs et d\'éditeurs pour s\'assurer que les délais et les budgets sont respectés.',
'example' => 'Flora Lichtman pour «Every Little Thing»',
),
'script_editor' =>
array (
'label' => 'Chef·fe-Scénariste',
'description' => 'L\'éditeur de script fournit des notes et des modifications au script d\'enregistrement dans un rôle très pratique. L\'éditeur de script est principalement utilisé dans la fiction, les documentaires et les publicités où les enregistrements scénarisés sont répandus.',
'example' => 'Alex Rioux pour «Bienvenue à Tinsel Town: A Christmas Adventure»',
),
'script_coordinator' =>
array (
'label' => 'Coordinat·eur·rice de scénario',
'description' => 'Le coordinateur du scénario emballe le script final avec des annotations qui reflètent une logistique spécifique et des indices créatifs pour l\'enregistrement et la production.',
'example' => 'Alex Rioux pour «Bienvenue à Tinsel Town: A Christmas Adventure»',
),
'researcher' =>
array (
'label' => 'Enquêt·eur·rice',
'description' => 'Le chercheur coordonne la recherche et la vérification des informations qui peuvent ensuite être utilisées pour le contenu d\'un épisode de podcast, informant souvent la direction d\'une histoire en fonction de nouvelles informations découvertes.',
'example' => 'Dave Grave pour «The Zero Brain Podcast»',
),
'editor' =>
array (
'label' => 'Édit·eur·rice',
'description' => 'L\'éditeur examine et prépare des scripts pour transmettre des informations de manière créative, précise et engageante.',
'example' => '',
),
'fact_checker' =>
array (
'label' => 'Contrôl·eur·euse Qualité',
'description' => 'Le vérificateur de faits examine le contenu d\'un podcast pour vérifier l\'exactitude des faits et vérifie que l\'attribution des citations est correcte. Ils utilisent une variété d\'outils, y compris la recherche de tiers et la sensibilisation individuelle. Souvent, le vérificateur de faits fournira également des notes sur la façon dont la production peut éviter la confusion dans la livraison des informations dans l\'épisode.',
'example' => '',
),
'translator' =>
array (
'label' => 'Traduct·eur·rice',
'description' => 'Le traducteur convertit le contenu d\'une langue à une autre pour le podcast. Cela peut être des interviews, des dialogues, des documents texte, etc. Le travail du traducteur peut être utilisé à l\'antenne ou en coulisses pendant le processus de production / recherche.',
'example' => '',
),
'transcriber' =>
array (
'label' => 'Transcript·eur·rice',
'description' => 'Le transcripteur transforme les dialogues et les signaux audio en texte, qui peut être utilisé en interne pour les processus de production ou affiché publiquement pour les auditeurs.',
'example' => '',
),
'logger' =>
array (
'label' => 'Archiviste',
'description' => 'The Logger examine et documente le contenu et les horodatages de l\'audio brut au service des producteurs et des éditeurs dans le processus de production.',
'example' => '',
),
),
),
'audio_production' =>
array (
'label' => 'Production Audio',
'roles' =>
array (
'studio_coordinator' =>
array (
'label' => 'Coordinat·eur·rice de Studio',
'description' => 'Le coordonnateur de studio gère le studio d\'enregistrement et les techniciens audio travaillant dans le studio au moment de l\'enregistrement.',
'example' => '',
),
'technical_director' =>
array (
'label' => 'Direct·eur·rice Technique',
'description' => 'Le directeur technique supervise l\'enregistrement et la production du podcast car il est impliqué dans les technologies audio, y compris le matériel et les logiciels, et la gestion des rôles impliqués dans ces domaines.',
'example' => 'Adam Raymonda sur «Celebuzz\'d»',
),
'technical_manager' =>
array (
'label' => 'Responsable Technique',
'description' => 'Le directeur technique coordonne une équipe d\'ingénieurs du son et de personnel de studio, dans l\'enregistrement et la production car il est impliqué dans les technologies audio, y compris le matériel et les logiciels.',
'example' => '',
),
'audio_engineer' =>
array (
'label' => 'Ingénieur·e du Son',
'description' => 'L\'ingénieur audio aide à enregistrer et à produire de l\'audio en configurant des environnements d\'enregistrement, en surveillant le recodage et en apportant des ajustements techniques tout au long. L\'ingénieur audio est présent pendant le processus d\'enregistrement, effectuant le plus souvent des ajustements en temps réel. L\'ingénieur du son peut travailler avec des conversations, de la musique, des chansons ou tout autre type d\'audio.',
'example' => 'Peter Leonard de «Startup Podcast»',
),
'remote_recording_engineer' =>
array (
'label' => 'Pren·eur·euse de Son sur Site',
'description' => 'L\'ingénieur d\'enregistrement à distance assure l\'enregistrement correct des conversations ayant lieu à plusieurs endroits sur une ligne téléphonique ou une connexion Internet. L\'ingénieur d\'enregistrement à distance évalue les différentes configurations d\'enregistrement et tente de les réconcilier en un son cohérent, tout en surveillant également le processus d\'enregistrement pour capturer le meilleur son possible.',
'example' => '',
),
'post_production_engineer' =>
array (
'label' => 'Ingénieur·e Post-Production',
'description' => 'L\'ingénieur postproduction évalue les technologies audio et leur application en ce qui concerne les étapes finales de production et de publication.',
'example' => 'Dick Wound pour «Queens Next Door»',
),
),
),
'audio_post_production' =>
array (
'label' => 'Post-Production Audio',
'roles' =>
array (
'audio_editor' =>
array (
'label' => 'Mont·eur·euse Son',
'description' => 'L\'éditeur audio coupe et réorganise l\'audio à des fins de clarté et de narration. L\'éditeur audio peut également effectuer un traitement et un mastering audio généraux.',
'example' => '',
),
'sound_designer' =>
array (
'label' => 'Concept·eur·rice Sonore',
'description' => 'Le Sound Designer crée et compose une variété d\'éléments audio. Ces éléments sont pour la plupart secondaires à la parole, mais un Sound Designer peut éditer / produire des éléments de discours de manière créative d\'une manière artistique.',
'example' => '',
),
'foley_artist' =>
array (
'label' => 'Illustrat·eur·rice Sonore',
'description' => 'Les effets sonores de l\'artiste Foley pour un podcast et peuvent le faire à la fois via un enregistrement physique et un traitement numérique, ou une combinaison des deux.',
'example' => '',
),
'composer' =>
array (
'label' => 'Composit·eur·rice',
'description' => 'Le compositeur écrit une pièce musicale originale (ou plusieurs) qui est jouée sur l\'épisode publié. Le compositeur sera également souvent l\'interprète de ladite pièce musicale.',
'example' => 'Marcus Thorne Bagala de «This American Life»',
),
'theme_music' =>
array (
'label' => 'Musique de Générique',
'description' => 'Theme Music est une pièce musicale qui accompagne le podcast à travers plusieurs épisodes, le plus souvent au début d\'un épisode. Le thème Musique est utilisé pour présenter le podcast en tant que marque. Ce rôle est pour le créateur de la musique du thème.',
'example' => 'Mark Philips de «Startup Podcast»',
),
'music_production' =>
array (
'label' => 'Production Musicale',
'description' => 'Le rôle de production musicale aide à créer de manière créative de la musique dans un rôle distinct de l\'écriture de ladite musique. La production musicale implique souvent des décisions créatives en fonction de la méthode d\'enregistrement de la musique, de l\'arrangement des instruments, de l\'utilisation d\'effets, etc.',
'example' => 'Storm Duper pour «Faking Star Wars Radio»',
),
'music_contributor' =>
array (
'label' => 'Contribution Musicale',
'description' => 'The Music Contributor est le créateur de la musique qui a été utilisée pour le podcast mais pas nécessairement produite spécifiquement pour le podcast. Souvent, un podcast utilisera une pièce musicale existante et créditera le créateur original.',
'example' => 'Bobby Lord de «Startup Podcast»',
),
),
),
'administration' =>
array (
'label' => 'Gestion',
'roles' =>
array (
'production_coordinator' =>
array (
'label' => 'Coordinat·eur·rice de Production',
'description' => 'Le coordonnateur de la production est responsable de la gestion de la logistique du processus de production de l\'enregistrement à la publication, y compris l\'obtention des autorisations et des permis requis, la connexion des différentes équipes de production et d\'enregistrement, la coordination de la création des métadonnées de post-production, la budgétisation, etc.',
'example' => 'Taneya Boyde sur «Prêt pour le changement? »',
),
'booking_coordinator' =>
array (
'label' => 'Programmat·eur·rice',
'description' => 'Le coordonnateur des réservations est chargé de faire venir de nouveaux invités pour les entrevues, y compris la recherche des invités, la planification des entrevues, le matériel d\'accueil et les processus post-publication.',
'example' => 'Meryl Klemow pour «Campfire Sht Show»',
),
'production_assistant' =>
array (
'label' => 'Assistant·e de Production',
'description' => 'L\'assistant de production aide à soutenir un membre de la direction d\'un podcast (souvent un réalisateur ou un producteur), en aidant à les préparer de diverses manières, y compris la planification, la logistique, les communications, etc.',
'example' => 'Wallace Mack pour «The Nod»',
),
'content_manager' =>
array (
'label' => 'Responsable des Contenus',
'description' => 'Le gestionnaire de contenu est responsable de la distribution du contenu d\'un podcast à l\'intérieur et à l\'extérieur de l\'épisode, y compris, mais sans s\'y limiter, les clips, les newsletters, les images, les promotions croisées, etc.',
'example' => 'Kenneth Lee Johnson II pour «Malice Corp Smack Talk»',
),
'marketing_manager' =>
array (
'label' => 'Responsable Marketing',
'description' => 'Le directeur du marketing est responsable de la promotion du contenu d\'un podcast par le biais de diverses stratégies de sensibilisation telles que des campagnes sur les médias sociaux, le développement d\'une présence sur le Web, la gestion des relations publiques et des stratégies de communication et d\'autres techniques créatives pour acquérir et fidéliser les auditeurs.',
'example' => '',
),
'sales_representative' =>
array (
'label' => 'Commercial·e',
'description' => 'Le représentant des ventes est responsable de la monétisation du contenu des balados en gérant et en vendant l\'inventaire publicitaire.',
'example' => '',
),
'sales_manager' =>
array (
'label' => 'Direct·eur·rice Commercial·e',
'description' => 'Le directeur des ventes est responsable de tous les aspects de la monétisation des podcasts, tels que la supervision des représentants des ventes, la gestion de l\'inventaire publicitaire et la conception de stratégies de monétisation via des canaux tels que les partenariats d\'affiliation, la marchandise, les événements en direct et d\'autres stratégies de revenus.',
'example' => '',
),
),
),
'visuals' =>
array (
'label' => 'Illustrations',
'roles' =>
array (
'graphic_designer' =>
array (
'label' => 'Infographiste',
'description' => 'Le graphiste est quelqu\'un qui a créé des visuels personnalisés pour accompagner le podcast de différentes manières.',
'example' => 'Sky Knight pour «The XP Billionaires»',
),
'cover_art_designer' =>
array (
'label' => 'Concept·eur·rice de la Couverture',
'description' => 'Le Cover Art Designer crée la pochette affichée d\'un podcast ou d\'un épisode. Pour plus de clarté, la pochette est l\'image principale (presque toujours carrée) accompagnant le podcast dans les répertoires, tandis que la pochette d\'épisode est affichée de la même manière au niveau de l\'épisode. Ce rôle peut être un concepteur numérique, un artiste, un photographe ou tout autre créatif visuel.',
'example' => '',
),
),
),
'community' =>
array (
'label' => 'Communauté',
'roles' =>
array (
'social_media_manager' =>
array (
'label' => 'Responsable Réseaux Sociaux',
'description' => 'Le gestionnaire de médias sociaux gère les comptes de médias sociaux du podcast, y compris, mais sans s\'y limiter, la création de contenu, la publication, les réponses, la surveillance, etc.',
'example' => 'Tom Joshi-Cale pour «World on a String»',
),
),
),
'misc' =>
array (
'label' => 'Divers',
'roles' =>
array (
'consultant' =>
array (
'label' => 'Consultant',
'description' => 'Un consultant est un poste de tiers où une personne extérieure à l\'organisation travaille sur un projet, offrant souvent une expertise spécifique. Il s\'agit d\'un rôle de modificateur et peut être appliqué à n\'importe quelle zone de travail.',
'example' => 'Ross Wilcock pour «Being Kenzie-Feature Long Immersive Horror»',
),
'intern' =>
array (
'label' => 'Stagiaire',
'description' => 'Un stagiaire est un poste d\'apprenti où quelqu\'un travaille pendant un temps limité au sein d\'une organisation pour acquérir une expérience de travail dans un domaine spécifique. Il s\'agit d\'un rôle de modificateur et peut être appliqué à n\'importe quelle zone de travail.',
'example' => '',
),
),
),
'video_production' =>
array (
'label' => 'Production Vidéo',
'roles' =>
array (
'camera_operator' =>
array (
'label' => 'Cadr·eur·euse',
'description' => 'Un caméraman est chargé de capturer et d\'enregistrer tous les aspects d\'une scène pour le cinéma et la télévision. Ils doivent comprendre les aspects techniques du fonctionnement d\'une caméra, cadrer une photo appropriée en ce qui concerne l\'éclairage et la mise en scène, mettre au point l\'objectif et avoir un œil visuel pour obtenir un look spécifique.',
'example' => '',
),
'lighting_designer' =>
array (
'label' => 'Concept·eur·rice Lumières',
'description' => 'Un concepteur d\'éclairage travaille avec le DP et le directeur pour créer un aspect et une sensation spécifiques d\'une scène en utilisant diverses techniques d\'éclairage. Ils doivent être capables d\'interpréter la direction créative et de lui donner vie.',
'example' => '',
),
'camera_grip' =>
array (
'label' => 'Machiniste',
'description' => 'Une poignée d\'appareil photo est responsable de la construction et de l\'entretien de toutes les pièces d\'un appareil photo et de ses accessoires tels que les trépieds, les grues, les chariots, etc.',
'example' => '',
),
'assistant_camera' =>
array (
'label' => 'Cadr·eur·euse Assistant·e',
'description' => '1st AC est responsable de l\'équipement de la caméra, de la construction des caméras avant le début de chaque journée, de l\'organisation de toutes les pièces et des divers accessoires, du remplacement des objectifs si nécessaire et également de la mise au point pour les opérateurs DP et caméra. Le CA terminera également chaque journée en nettoyant les appareils photo, en écrivant des notes sur l\'appareil photo, en marquant les cartes multimédias et en les remettant au DIT.',
'example' => '',
),
),
),
'video_post_production' =>
array (
'label' => 'Post-Production Vidéo',
'roles' =>
array (
'editor' =>
array (
'label' => 'Rédact·eur·rice en Chef·fe',
'description' => 'Les éditeurs de télévision sont chargés de prendre les séquences et les clips et de les mélanger pour créer la vision et la narration du réalisateur.',
'example' => '',
),
'assistant_editor' =>
array (
'label' => 'Rédact·eur·rice en Chef·fe Adjoint·e',
'description' => 'L\'assistant de montage est chargé de prendre les médias de l\'ensemble, de les intégrer dans le logiciel de montage désigné et d\'organiser les images de manière efficace pour l\'éditeur. Ils doivent également faire très attention pour s\'assurer que l\'audio et la vidéo sont synchronisés et que toutes les séquences du plateau sont correctement ingérées.',
'example' => '',
),
),
),
),
);

14641
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,14 +23,14 @@
"prettier": "prettier --check --ignore-path .gitignore .",
"prettier:fix": "prettier --write --ignore-path .gitignore .",
"typecheck": "tsc",
"commit": "git-cz",
"commit": "cz",
"release": "semantic-release",
"prepare": "is-ci || husky install"
},
"dependencies": {
"@amcharts/amcharts4": "^4.10.17",
"@amcharts/amcharts4-geodata": "^4.1.19",
"@popperjs/core": "^2.9.1",
"@amcharts/amcharts4": "^4.10.20",
"@amcharts/amcharts4-geodata": "^4.1.21",
"@popperjs/core": "^2.9.2",
"@rollup/plugin-multi-entry": "^4.0.0",
"choices.js": "^9.0.1",
"flatpickr": "^4.6.9",
@ -40,46 +40,44 @@
"prosemirror-example-setup": "^1.1.2",
"prosemirror-markdown": "^1.5.1",
"prosemirror-state": "^1.3.4",
"prosemirror-view": "^1.18.1"
"prosemirror-view": "^1.18.11"
},
"devDependencies": {
"@commitlint/cli": "^12.0.1",
"@commitlint/config-conventional": "^12.0.1",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/exec": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/gitlab": "^7.0.4",
"@tailwindcss/forms": "^0.2.1",
"@tailwindcss/line-clamp": "^0.2.0",
"@tailwindcss/typography": "^0.4.0",
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/exec": "^5.0.0",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.2.1",
"@tailwindcss/forms": "^0.3.3",
"@tailwindcss/line-clamp": "^0.2.1",
"@tailwindcss/typography": "^0.4.1",
"@types/leaflet": "^1.7.5",
"@types/prosemirror-markdown": "^1.5.1",
"@types/prosemirror-view": "^1.17.1",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"@types/prosemirror-markdown": "^1.5.2",
"@types/prosemirror-view": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"cpy-cli": "^3.1.1",
"cross-env": "^7.0.3",
"cssnano": "^4.1.10",
"cssnano": "^5.0.7",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"husky": "^6.0.0",
"eslint": "^7.31.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"husky": "^7.0.1",
"is-ci": "^3.0.0",
"lint-staged": "^10.5.4",
"postcss-import": "^14.0.0",
"lint-staged": "^11.1.1",
"postcss-import": "^14.0.2",
"postcss-preset-env": "^6.7.0",
"prettier": "2.2.1",
"prettier-plugin-organize-imports": "^1.1.1",
"rollup-plugin-multi-input": "^1.2.0",
"semantic-release": "^18.0.0",
"stylelint": "^13.12.0",
"stylelint-config-standard": "^21.0.0",
"svgo": "^2.2.2",
"tailwindcss": "^2.2.4",
"typescript": "^4.2.3",
"vite": "^2.3.8",
"vite-plugin-ruby": "^2.0.4"
"prettier": "2.3.2",
"prettier-plugin-organize-imports": "^2.3.3",
"semantic-release": "^17.4.4",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"svgo": "^2.3.1",
"tailwindcss": "^2.2.7",
"typescript": "^4.3.5",
"vite": "^2.4.3"
},
"lint-staged": {
"*.{js,ts,css,md,json}": "prettier --write",
@ -88,7 +86,7 @@
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
"path": "cz-conventional-changelog"
}
}
}