================================================
FILE: assets/css/podcasting-edit-term.css
================================================
.taxonomy-podcasting_podcasts .term-parent-wrap {
display: none;
}
.podcast-image-thumbnail {
max-width: 300px;
max-height: 200px;
}
.column-podcasting_image img {
height: auto;
max-width: 100%;
width: 75px;
}
.simple_podcasting__platforms img {
display: block;
max-width: 42px;
margin: 0 auto;
height: auto;
}
.simple_podcasting__platforms th {
padding: 15px 10px;
text-align: center;
}
.simple_podcasting__platforms td {
vertical-align: middle;
padding: 15px 10px;
}
.simple_podcasting__platforms-url {
min-width: 220px;
}
.simple_podcasting__platforms-icon {
transition: background-color 0.2s ease-out;
}
.simple_podcasting__platforms-icon--darken-bg {
background-color: rgb(28 52 59 / 22%);
transition: background-color 0.2s ease-in;
}
================================================
FILE: assets/css/podcasting-editor-screen.css
================================================
.components-input-control,
.components-base-control {
width: 100%;
}
.cover-art-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.cover-art-container button {
margin: 10px 0;
}
================================================
FILE: assets/css/podcasting-onboarding.scss
================================================
* {
box-sizing: border-box;
}
.admin_page_simple-podcasting-onboarding #wpcontent {
padding-left: 0;
}
#simple-podcasting {
&__onboarding-header {
width: 100%;
height: 79px;
padding: 0 1.75rem;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
border-bottom: 1px solid #c0c0c1;
& > * {
flex-grow: 1;
flex-basis: 0;
}
}
&__branding {
display: flex;
align-items: center;
}
&__header-title {
text-align: center;
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-size: 17.8983px;
}
&__logo {
max-width: 56px;
height: auto;
img {
display: block;
width: 100%;
}
}
&__plugin-name {
margin-left: 11px;
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-size: 17.8983px;
line-height: 20px;
}
&__header-controls {
.simple-podcasting__btn {
float: right;
}
}
&__page-title {
margin-bottom: 30px;
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-size: 22px;
line-height: 24px;
}
&__upload-cover-image {
margin-top: 13px;
margin-bottom: 14px;
}
&__create-a-new-post-button {
padding: 10px 40px;
width: 240px;
display: block;
text-align: center;
text-decoration: none;
color: #fff !important;
}
&__cover-image-preview {
img {
display: block;
max-width: 256px;
}
}
}
.simple-podcasting {
// Body.
&__onboarding-body {
margin: 0 auto;
margin-top: 68px;
padding: 0 1.75rem;
&--step-1 {
max-width: 524px;
}
&--step-2 {
display: flex;
max-width: 1038px;
}
}
&__setting {
margin-bottom: 30px;
input,
textarea {
width: 100%;
padding: 11px 7px 10px 13px;
background: #FFFFFF;
border: 1px solid #828282;
border-radius: 5px;
}
select {
padding: 11px 7px 10px 13px;
width: 320px;
background: #FFFFFF;
border: 1px solid #828282;
border-radius: 5px;
}
}
&__setting-label {
display: block;
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-size: 16px;
line-height: 24px;
margin-bottom: 4px;
}
&__setting-description {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 24px;
color: #828282;
margin-top: 4px;
}
&__panel {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
flex-grow: 1;
flex-basis: 0;
p {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
}
&--left {
max-width: 483px;
a {
color: #000;
font-weight: 700;
}
}
}
&__podcast-block-preview {
width: 468px;
float: right;
box-shadow: 0px 0px 26px 8px rgba(0, 0, 0, 0.05);
img {
display: block;
width: 100%;
height: auto;
}
}
&__step-2-controls {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 64px;
a {
letter-spacing: 1px;
text-decoration-line: underline;
color: #4F4F4F !important;
}
}
}
.simple-podcasting__btn {
box-shadow: none;
border: 0;
outline: 0;
border-radius: 3px;
padding: 12px 40px;
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-size: 14px;
letter-spacing: 1px;
text-decoration: none;
cursor: pointer;
&--ghost {
border: 1px solid #000000;
background-color: rgba(0, 0, 0, 0);
color: #000;
}
&--black {
background: #4F4F4F;
border-radius: 3px;
color: #FFFFFF;
}
}
================================================
FILE: assets/css/podcasting-transcript.css
================================================
.wp-block-podcasting-podcast-transcript cite,
.wp-block-podcasting-podcast-transcript time {
display: block;
}
================================================
FILE: assets/js/blocks/latest-episode/index.js
================================================
import './index.scss';
================================================
FILE: assets/js/blocks/latest-episode/index.scss
================================================
.podcasting-latest-episode {
display: flex;
flex-direction: column;
justify-content: end;
min-height: 20rem;
overflow: hidden;
position: relative;
& .wp-block-post-featured-image {
height: 100%;
object-fit: fill;
object-position: center;
position: absolute;
width: 100%;
&::after {
background-color: rgb(0 0 0 / 75%);
content: "";
display: block;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
}
}
.podcasting-latest-episode__content {
color: #fff;
padding: 3rem;
position: relative;
z-index: 1;
@media (min-width: 768px) {
padding: 3rem;
}
& .wp-block-post-excerpt,
& .wp-block-post-excerpt__more-text {
margin-top: 0.25rem;
}
& .wp-block-post-excerpt__more-link {
color: #fff;
}
}
.editor-styles-wrapper .wp-block-post-content .podcasting-latest-episode__content .wp-block-post-excerpt__more-link:where(:not(.wp-element-button)) {
color: #fff;
}
================================================
FILE: assets/js/blocks/podcast/index.js
================================================
import './index.scss';
================================================
FILE: assets/js/blocks/podcast/index.scss
================================================
.wp-block-podcasting-podcast-outer {
border: 1px solid #707070;
border-radius: 4px;
padding: 20px;
}
.wp-block-podcasting-podcast__container {
margin-bottom: 10px;
@media (min-width: 768px) {
display: flex;
}
}
.wp-block-podcasting-podcast__show-art {
margin-bottom: 20px;
@media (min-width: 768px) {
flex-basis: 100px;
margin-bottom: 0;
margin-right: 20px;
}
}
.wp-block-podcasting-podcast__image {
aspect-ratio: 1/1;
height: auto;
position: relative;
& img {
display: block;
height: 100%;
object-fit: cover;
width: 100%;
}
}
.wp-block-podcasting-podcast__show-title {
margin: 0;
}
.wp-block-podcasting-podcast__show-details {
color: #575757;
font-size: 0.875rem;
text-transform: uppercase;
& span {
display: block;
margin-right: 6px;
@media (min-width: 768px) {
display: inline;
}
&::after {
@media (min-width: 768px) {
content: '/';
margin-left: 6px;
}
}
&:last-child {
margin-right: 0;
&::after {
display: none;
}
}
}
}
.wp-block-podcasting-podcast__caption {
margin-bottom: 10px;
}
.wp-block-podcasting-podcast {
margin: 0;
& audio {
display: block;
width: 100%;
}
}
================================================
FILE: assets/js/blocks/podcast-platforms/edit.js
================================================
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { useState, useEffect } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import { useDebounce } from 'use-debounce';
import {
Panel,
PanelBody,
PanelRow,
RangeControl,
SearchControl,
__experimentalItemGroup as ItemGroup,
__experimentalItem as Item,
BaseControl,
Button,
ButtonGroup,
Icon
} from '@wordpress/components';
function Edit( props ) {
const {
setAttributes,
isSelected,
attributes: {
showId,
iconSize,
align,
},
} = props;
/** State for the search text for the show name. Defaults to empty string. */
const [ searchText, setSearchText ] = useState( '' );
/** Debounced search text so that we don't trigger useEffect() for every character change. */
const [ debouncedSearchText ] = useDebounce( searchText, 300 );
/** Indicates when the ajax search for podcasts is completed. */
const [ isSearchCompleted, setIsSearchCompleted ] = useState( false );
/** State for search results matched by the search text. Defaults to array. */
const [ searchResults, setSearchResults ] = useState( [] );
/** State for the icon theme. Defaults to `color`. */
const [ iconTheme, setIconTheme ] = useState( 'color' );
/** State for platforms returned for a specific show. Defaults to array. */
const [ platforms, setPlatforms ] = useState( [] );
/**
* Hits the `/wp/v2/search` endpoint to search for
* podcast show by name.
*/
useEffect( () => {
const searchPodcastShow = async () => {
setIsSearchCompleted( false );
if ( ! searchText.length ) {
setSearchResults( [] );
return;
}
/** Query object required by `/wp/v2/search` to search for a term by name. */
const queryObject = {
search: searchText,
type: 'term',
subtype: 'podcasting_podcasts'
};
/** Converts an object to query-string. */
const queryString = new URLSearchParams( queryObject ).toString();
/** Returns the results of the search. */
const searchResults = await apiFetch( {
path: `/wp/v2/search?${ queryString }`,
} );
if ( ! searchResults.length ) {
setIsSearchCompleted( true );
}
setSearchResults( searchResults );
setIsSearchCompleted( true );
};
searchPodcastShow();
}, [ debouncedSearchText ] );
/**
* Fetches the podcasting platforms for a show whenever
* showId updates.
*/
useEffect( () => {
if ( ! showId ) {
return;
}
/**
* Responsible to fetch platforms for a show by show ID.
* @returns void
*/
const fetchPlatforms = async () => {
const result = await apiFetch( {
url: `${ ajaxurl }?show_id=${ showId }&action=get_podcast_platforms`,
} );
if ( ! result.success ) {
setPlatforms( [] );
return;
}
const {
data: { platforms, theme }
} = result;
setPlatforms( platforms );
setIconTheme( theme );
};
fetchPlatforms();
}, [ showId ] );
/**
* Handler to set the attribute showId.
*
* @param {Int} termId The show ID.
* @returns void
*/
const onShowSelect = ( termId ) => {
setAttributes( { showId: termId } );
setSearchResults( [] );
setIsSearchCompleted( false );
};
/**
* Handler to set size of the icon.
*
* @param {Int} size The icon size in `px`
*/
const setIconSize = ( size ) => {
setAttributes( { iconSize: size } );
};
/**
* Sets the HTML attributes for the root element.
*/
const blockProps = useBlockProps( {
className: isSelected ? 'simple-podcasting__podcast-platforms simple-podcasting__podcast-platforms--selected' : 'simple-podcasting__podcast-platforms',
} );
const platformSlugs = Object.keys( platforms );
return (
<>
{ __( 'No platforms are set for this podcast.', 'simple-podcasting' ) }
{__('The featured image of the current post is used as the episode cover art. Please select a featured image to set it.', 'simple-podcasting')}
here.', 'simple-podcasting' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_url( admin_url( 'edit-tags.php?taxonomy=podcasting_podcasts&podcasts=true' ) ) ); ?>
'; esc_html_e( 'Once at least one podcast exists, you can add episodes by creating a post, assigning it to the appropriate podcast, and inserting an audio player or podcast block into the content of the post. You can then submit the feed URL to podcast directories.', 'simple-podcasting' ); echo '
| $platform ) : ?> | ||
|---|---|---|
|
|
| term_id ); ?> |
>
>
>
>
post_content, $matches ) && array_key_exists( 2, $matches ) && in_array( 'audio', $matches[2], true ) ) { preg_match( '/.*mp3=\\"(.*)\\".*/', $matches[0][0], $matches2 ); if ( isset( $matches2[1] ) ) { $url = $matches2[1]; } } } /** * Retrieve the enclosure and store its metadata in post meta. * * @todo only retrieve enclosure metadata when a podcasting term id is selected and the url has changed. */ if ( $url ) { $podcast_meta = \tenup_podcasting\helpers\get_podcast_meta_from_url( $url ); if ( ! empty( $podcast_meta ) ) { update_post_meta( $post_id, 'podcast_url', $podcast_meta['url'] ); update_post_meta( $post_id, 'podcast_filesize', $podcast_meta['filesize'] ); update_post_meta( $post_id, 'podcast_duration', $podcast_meta['duration'] ); update_post_meta( $post_id, 'podcast_mime', $podcast_meta['mime'] ); // Add enclosure meta data $enclosure = $podcast_meta['url'] . "\n" . $podcast_meta['filesize'] . "\n" . $podcast_meta['mime']; update_post_meta( $post_id, 'enclosure', $enclosure ); } } update_post_meta( $post_id, 'podcast_explicit', $podcast_explicit ); update_post_meta( $post_id, 'podcast_captioned', $podcast_captioned ); update_post_meta( $post_id, 'podcast_season_number', $season_number ); update_post_meta( $post_id, 'podcast_episode_number', $episode_number ); update_post_meta( $post_id, 'podcast_episode_type', $episode_type ); } add_action( 'save_post_post', __NAMESPACE__ . '\save_meta_box' ); /** * Enqueue helper script for the post edit and new post screens. * * @param string $hook_suffix The current admin page. */ function edit_post_enqueues( $hook_suffix ) { $screens = array( 'post.php', 'post-new.php', ); if ( ! in_array( $hook_suffix, $screens, true ) ) { return; } wp_enqueue_script( 'podcasting_edit_post_screen', PODCASTING_URL . 'dist/podcasting-edit-post.js', array( 'jquery' ), PODCASTING_VERSION, true ); } add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\edit_post_enqueues' ); ================================================ FILE: includes/rest-external-url.php ================================================ \WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\handle_request', 'permission_callback' => function () { return true; }, 'args' => array( 'url' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); } /** * Callbakc for the external-url endpoint. * * @param \WP_REST_Request $request The API request * * @return mixed|\WP_REST_Response */ function handle_request( \WP_REST_Request $request ) { $url = $request['url']; $cache_key = 'spc_external_url_' . $url; $podcast_meta = get_transient( $cache_key ); if ( false === $podcast_meta ) { if ( filter_var( $url, FILTER_VALIDATE_URL ) ) { $podcast_meta = \tenup_podcasting\helpers\get_podcast_meta_from_url( $url ); if ( $podcast_meta ) { $response = array( 'success' => true, 'data' => $podcast_meta, ); set_transient( $cache_key, $podcast_meta, MONTH_IN_SECONDS ); // We add the long expiry so we don't autoload the option in a non-object-cached env. } } else { $response = array( 'success' => false, 'message' => 'Invalid URL parameter passed', ); } } else { $response = array( 'success' => true, 'data' => $podcast_meta, ); } return rest_ensure_response( $response ); } ================================================ FILE: includes/transcripts.php ================================================ term_id ) ) . $post->post_name . '/transcript/'; } /** * Adds