Add podcast listing page

This commit is contained in:
Oliver Davies 2025-05-25 19:48:07 +01:00
parent 203056d54a
commit fc1beaf3fb
10 changed files with 414 additions and 2 deletions

View file

@ -48,6 +48,7 @@ module:
metatag_twitter_cards: 0
node: 0
opd_daily_emails: 0
opd_podcast: 0
options: 0
page_cache: 0
path: 0

View file

@ -14,4 +14,4 @@ description: null
help: null
new_revision: true
preview_mode: 1
display_submitted: false
display_submitted: true

View file

@ -0,0 +1,204 @@
uuid: 18036b67-1a46-4b88-a11c-64253e3e2ada
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- node.type.podcast_episode
module:
- node
- user
id: podcast_episodes
label: 'Podcast episodes'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
id: default
display_title: Default
display_plugin: default
position: 0
display_options:
title: 'The Beyond Blocks Podcast'
fields:
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
entity_type: node
entity_field: title
plugin_id: field
label: ''
exclude: false
alter:
alter_text: false
make_link: false
absolute: false
word_boundary: false
ellipsis: false
strip_tags: false
trim: false
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
pager:
type: mini
options:
offset: 0
pagination_heading_level: h4
items_per_page: 10
total_pages: null
id: 0
tags:
next:
previous:
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
empty: { }
sorts:
field_episode_number_value:
id: field_episode_number_value
table: node__field_episode_number
field: field_episode_number_value
relationship: none
group_type: group
admin_label: ''
plugin_id: standard
order: DESC
expose:
label: ''
field_identifier: ''
exposed: false
arguments: { }
filters:
status:
id: status
table: node_field_data
field: status
entity_type: node
entity_field: status
plugin_id: boolean
value: '1'
group: 1
expose:
operator: ''
type:
id: type
table: node_field_data
field: type
entity_type: node
entity_field: type
plugin_id: bundle
value:
podcast_episode: podcast_episode
style:
type: default
row:
type: 'entity:node'
options:
relationship: none
view_mode: teaser
query:
type: views_query
options:
query_comment: ''
disable_sql_rewrite: false
distinct: false
replica: false
query_tags: { }
relationships: { }
header:
area:
id: area
table: views
field: area
relationship: none
group_type: group
admin_label: ''
plugin_id: text
empty: false
content:
value: "<p>A podcast about Drupal, PHP, open-source, and related software development topics.\r\nGuests include people like <a href=\"/podcast/1-retrofit\">Matt Glaman</a>, <a href=\"/podcast/8-eirik-morland-violinist\">Eirik Morland</a>, <a href=\"/podcast/9-tim-lehnen\">Tim Lehnen</a>, <a href=\"/podcast/13-ryan-szrama-centarro\">Ryan Szrama</a>, <a href=\"/podcast/19-sam-mortenson\">Sam Mortenson</a> and <a href=\"/podcast/25-jess-archer-drush-laravel-prompts\">Jess Archer</a>.</p>"
format: basic_html
tokenize: false
footer: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
id: page_1
display_title: Page
display_plugin: page
position: 1
display_options:
display_extenders: { }
path: podcast
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,5 @@
name: Podcast
description: Custom functionality for podcasts.
core_version_requirement: ^11
type: module
package: Custom

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use Drupal\node\NodeInterface;
function opd_podcast_node_links_alter(array &$links, NodeInterface $entity, array &$context): void {
if ($entity->bundle() !== 'podcast_episode') {
return;
}
$links['node']['#links']['node-readmore']['title'] = t('Listen now<span class="visually-hidden"> to @title</span> →');
$links['node']['#links']['node-readmore']['attributes']['class'] = [
'p-0',
];
$links['#attributes']['class'][] = 'list-none';
$links['#attributes']['class'][] = 'm-0';
$links['#attributes']['class'][] = 'p-0';
}

View file

@ -1620,6 +1620,11 @@ select {
margin-bottom: 0px;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
@ -1913,6 +1918,12 @@ select {
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.space-y-10 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));
@ -2909,4 +2920,3 @@ select {
border-color: rgb(168 162 158 / var(--tw-border-opacity));
}
}

View file

@ -0,0 +1,3 @@
ul.links.inline > li {
padding: 0;
}

View file

@ -2,3 +2,4 @@ global-styling:
css:
base:
build/tailwind.css: {}
css/overrides.css: {}

View file

@ -0,0 +1,89 @@
{#
/**
* @file
* Default theme implementation to display a node.
*
* Available variables:
* - node: The node entity with limited access to object properties and methods.
* Only method names starting with "get", "has", or "is" and a few common
* methods such as "id", "label", and "bundle" are available. For example:
* - node.getCreatedTime() will return the node creation timestamp.
* - node.hasField('field_example') returns TRUE if the node bundle includes
* field_example. (This does not indicate the presence of a value in this
* field.)
* - node.isPublished() will return whether the node is published or not.
* Calling other methods, such as node.delete(), will result in an exception.
* See \Drupal\node\Entity\Node for a full list of public properties and
* methods for the node object.
* - label: (optional) The title of the node.
* - content: All node items. Use {{ content }} to print them all,
* or print a subset such as {{ content.field_example }}. Use
* {{ content|without('field_example') }} to temporarily suppress the printing
* of a given child element.
* - author_picture: The node author user entity, rendered using the "compact"
* view mode.
* - date: (optional) Themed creation date field.
* - author_name: (optional) Themed author name field.
* - url: Direct URL of the current node.
* - display_submitted: Whether submission information should be displayed.
* - attributes: HTML attributes for the containing element.
* The attributes.class element may contain one or more of the following
* classes:
* - node: The current template type (also known as a "theming hook").
* - node--type-[type]: The current node type. For example, if the node is an
* "Article" it would result in "node--type-article". Note that the machine
* name will often be in a short form of the human readable label.
* - node--view-mode-[view_mode]: The View Mode of the node; for example, a
* teaser would result in: "node--view-mode-teaser", and
* full: "node--view-mode-full".
* The following are controlled through the node publishing options.
* - node--promoted: Appears on nodes promoted to the front page.
* - node--sticky: Appears on nodes ordered above other non-sticky nodes in
* teaser listings.
* - node--unpublished: Appears on unpublished nodes visible only to site
* admins.
* - title_attributes: Same as attributes, except applied to the main title
* tag that appears in the template.
* - content_attributes: Same as attributes, except applied to the main
* content tag that appears in the template.
* - author_attributes: Same as attributes, except applied to the author of
* the node tag that appears in the template.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - view_mode: View mode; for example, "teaser" or "full".
* - page: Flag for the full page state. Will be true if view_mode is 'full'.
*
* @see template_preprocess_node()
*
* @ingroup themeable
*/
#}
<article{{ attributes }}>
{{ title_prefix }}
{% if label and not page %}
<h2{{ title_attributes.addClass('font-bold text-xl') }}>
<a href="{{ url }}" rel="bookmark">{{ label }}</a>
</h2>
{% endif %}
{{ title_suffix }}
<div class="prose prose-p:text-black prose-a:font-light prose-a:text-blue-primary prose-p:text-lg prose-blockquote:border-blue-primary dark:marker:text-white prose-li:my-1 prose-li:text-lg prose-figcaption:text-white prose-li:text-black marker:text-black dark:prose-p:text-white dark:prose-invert dark:prose-a:text-blue-400 dark:prose-blockquote:border-blue-400 dark:prose-li:text-white hover:prose-a:no-underline prose-h2:text-xl prose-code:font-normal prose-h2:mb-4 prose-ul:my-3 dark:prose-hr:border-grey-400 prose-code:before:content-[''] prose-code:after:content-['']">
{% if display_submitted %}
<div class="my-2 text-base">
{% set created = node.getCreatedTime() %}
<time datetime="{{ created|format_date('html_date') }}">{{ created|format_date('short') }}</time>
</div>
{% endif %}
<div{{ content_attributes.addClass('mt-2') }}>
{{ content|without('links') }}
<div class="mt-4">
{{ content.links }}
</div>
</div>
</div>
</article>

View file

@ -0,0 +1,78 @@
{#
/**
* @file
* Default theme implementation for main view template.
*
* Available variables:
* - attributes: Remaining HTML attributes for the element.
* - css_name: A CSS-safe version of the view name.
* - css_class: The user-specified classes names, if any.
* - header: The optional header.
* - footer: The optional footer.
* - rows: The results of the view query, if any.
* - empty: The content to display if there are no rows.
* - pager: The optional pager next/prev links to display.
* - exposed: Exposed widget form/info to display.
* - feed_icons: Optional feed icons to display.
* - more: An optional link to the next page of results.
* - title: Title of the view, only used when displaying in the admin preview.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the view title.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the view title.
* - attachment_before: An optional attachment view to be displayed before the
* view content.
* - attachment_after: An optional attachment view to be displayed after the
* view content.
* - dom_id: Unique id for every view being printed to give unique class for
* JavaScript.
*
* @see template_preprocess_views_view()
*
* @ingroup themeable
*/
#}
{%
set classes = [
dom_id ? 'js-view-dom-id-' ~ dom_id,
]
%}
<div{{ attributes.addClass(classes) }}>
{{ title_prefix }}
{{ title }}
{{ title_suffix }}
<div>
{% if header %}
<header class="prose prose-p:text-black prose-a:font-light prose-a:text-blue-primary prose-p:text-lg prose-blockquote:border-blue-primary dark:marker:text-white prose-li:my-1 prose-li:text-lg prose-figcaption:text-white prose-li:text-black marker:text-black dark:prose-p:text-white dark:prose-invert dark:prose-a:text-blue-400 dark:prose-blockquote:border-blue-400 dark:prose-li:text-white hover:prose-a:no-underline prose-h2:text-xl prose-code:font-normal prose-h2:mb-4 prose-ul:my-3 dark:prose-hr:border-grey-400 prose-code:before:content-[''] prose-code:after:content-['']">
{{ header }}
</header>
{% endif %}
{{ exposed }}
{{ attachment_before }}
{% if rows -%}
<div class="mt-10">
<div class="space-y-8">
{{ rows }}
</div>
</div>
{% elseif empty -%}
{{ empty }}
{% endif %}
{{ pager }}
</div>
{{ attachment_after }}
{{ more }}
{% if footer %}
<footer>
{{ footer }}
</footer>
{% endif %}
{{ feed_icons }}
</div>