Skip to content

Commit

Permalink
feat: auto-generate file slug from episode-post-title
Browse files Browse the repository at this point in the history
Implements #1436
  • Loading branch information
eteubert authored Feb 13, 2024
1 parent e074bc2 commit efe7e3a
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 47 deletions.
3 changes: 3 additions & 0 deletions client/src/modules/mediafiles/components/MediaSlug.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { mapState, injectStore } from 'redux-vuex'
import { selectors } from '@store'
import { update as updateEpisode } from '@store/episode.store'
import { disableSlugAutogen } from '@store/mediafiles.store'
export default defineComponent({
setup() {
Expand All @@ -47,6 +48,8 @@ export default defineComponent({
this.dispatch(
updateEpisode({ prop: 'slug', value: (event.target as HTMLInputElement).value })
)
// disable slug generation on any manual input
this.dispatch(disableSlugAutogen())
},
},
Expand Down
9 changes: 8 additions & 1 deletion client/src/sagas/episode.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { debounce, fork, put, select, takeEvery } from 'redux-saga/effects'
import { PodloveEpisode } from '../types/episode.types'
import * as auphonic from '../store/auphonic.store'
import * as episode from '../store/episode.store'
import * as mediafiles from '../store/mediafiles.store'
import * as wordpress from '../store/wordpress.store'
import { createApi } from './api'
import { WebhookConfig } from './auphonic.sagas'
Expand Down Expand Up @@ -34,9 +35,15 @@ function* updateAuphonicWebhookConfig() {

function* initialize(api: PodloveApiClient) {
const episodeId: string = yield select(selectors.episode.id)
const { result: episodesResult }: { result: PodloveEpisode } = yield api.get(`episodes/${episodeId}`)
const { result: episodesResult }: { result: PodloveEpisode } = yield api.get(
`episodes/${episodeId}`
)

if (episodesResult) {
if (episodesResult.slug === null) {
yield put(mediafiles.enableSlugAutogen())
}

yield put(episode.set(episodesResult))
}
}
Expand Down
27 changes: 25 additions & 2 deletions client/src/sagas/mediafiles.sagas.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { PodloveApiClient } from '@lib/api'
import { selectors } from '@store'
import { all, call, delay, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'
import { all, call, debounce, fork, put, select, takeEvery, throttle } from 'redux-saga/effects'
import * as mediafiles from '@store/mediafiles.store'
import * as episode from '@store/episode.store'
import * as wordpress from '@store/wordpress.store'
import { MediaFile } from '@store/mediafiles.store'
import { takeFirst } from './helper'
import { takeFirst, channel } from './helper'
import { createApi } from './api'
import { Action } from 'redux'
import { get } from 'lodash'
Expand All @@ -17,6 +17,7 @@ function* mediafilesSaga(): any {

function* initialize(api: PodloveApiClient) {
const episodeId: string = yield select(selectors.episode.id)
const episodeSlug: string = yield select(selectors.episode.slug)
const {
result: { results: files },
}: { result: { results: MediaFile[] } } = yield api.get(`episodes/${episodeId}/media`)
Expand All @@ -29,6 +30,8 @@ function* initialize(api: PodloveApiClient) {
yield takeEvery(mediafiles.DISABLE, handleDisable, api)
yield takeEvery(mediafiles.VERIFY, handleVerify, api)
yield takeEvery(episode.SAVED, maybeReverify, api)
yield debounce(2000, wordpress.UPDATE, maybeUpdateSlug, api)

yield throttle(
2000,
[mediafiles.ENABLE, mediafiles.DISABLE, mediafiles.UPDATE],
Expand Down Expand Up @@ -101,6 +104,26 @@ function* maybeReverify(api: PodloveApiClient, action: { type: string; payload:
yield all(mediaFiles.map((file) => call(verifyEpisodeAsset, api, episodeId, file.asset_id)))
}

function* maybeUpdateSlug(
api: PodloveApiClient,
action: { type: string; payload: { prop: string; value: any } }
) {
const episodeId: boolean = yield select(selectors.episode.id)
const oldSlug: boolean = yield select(selectors.episode.slug)
const enabled: boolean = yield select(selectors.mediafiles.slugAutogenerationEnabled)

if (enabled && action.payload.prop == 'title' && action.payload.value) {
const newTitle = action.payload.value

const { result } = yield api.get(`episodes/${episodeId}/build_slug`, {
query: { title: newTitle },
})
if (oldSlug != result.slug) {
yield put(episode.update({ prop: 'slug', value: result.slug }))
}
}
}

function* verifyEpisodeAsset(api: PodloveApiClient, episodeId: number, assetId: number) {
const mediaFiles: MediaFile[] = yield select(selectors.mediafiles.files)
const prevMediaFile: MediaFile | undefined = mediaFiles.find((mf) => mf.asset_id == assetId)
Expand Down
8 changes: 7 additions & 1 deletion client/src/sagas/wordpress.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,17 @@ function* updatePostTitle() {
const seasonNumber: string = ''
const padding: number = yield select(selectors.settings.episodeNumberPadding)

wordpress.postTitleInput.value = template
const newTitle = template
.replace('%mnemonic%', mnemonic || '')
.replace('%episode_number%', (episodeNumber || '').padStart(padding || 0, '0'))
.replace('%season_number%', seasonNumber || '')
.replace('%episode_title%', title || '')

if (wordpress.postTitleInput.value != newTitle) {
wordpress.postTitleInput.value = newTitle

yield postTitleUpdate(newTitle)
}
}

function* selectMediaFromLibrary(action: { payload: { onSuccess: Action } }) {
Expand Down
15 changes: 15 additions & 0 deletions client/src/store/mediafiles.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export type MediaFile = {

export type State = {
is_initializing: boolean
slug_autogeneration_enabled: boolean
files: MediaFile[]
}

export const initialState: State = {
is_initializing: true,
slug_autogeneration_enabled: false,
files: [],
}

Expand All @@ -28,6 +30,8 @@ export const DISABLE = 'podlove/publisher/mediafiles/DISABLE'
export const VERIFY = 'podlove/publisher/mediafiles/VERIFY'
export const UPLOAD_INTENT = 'podlove/publisher/mediafiles/UPLOAD_INTENT'
export const SET_UPLOAD_URL = 'podlove/publisher/mediafiles/SET_UPLOAD_URL'
export const ENABLE_SLUG_AUTOGEN = 'podlove/publisher/mediafiles/ENABLE_SLUG_AUTOGEN'
export const DISABLE_SLUG_AUTOGEN = 'podlove/publisher/mediafiles/DISABLE_SLUG_AUTOGEN'

export const init = createAction<void>(INIT)
export const initDone = createAction<void>(INIT_DONE)
Expand All @@ -38,6 +42,8 @@ export const disable = createAction<number>(DISABLE)
export const verify = createAction<number>(VERIFY)
export const uploadIntent = createAction<void>(UPLOAD_INTENT)
export const setUploadUrl = createAction<string>(SET_UPLOAD_URL)
export const enableSlugAutogen = createAction<void>(ENABLE_SLUG_AUTOGEN)
export const disableSlugAutogen = createAction<void>(DISABLE_SLUG_AUTOGEN)

// TODO: enable revalidates I think?
export const reducer = handleActions(
Expand Down Expand Up @@ -80,11 +86,20 @@ export const reducer = handleActions(
[]
),
}),
[ENABLE_SLUG_AUTOGEN]: (state: State): State => ({
...state,
slug_autogeneration_enabled: true,
}),
[DISABLE_SLUG_AUTOGEN]: (state: State): State => ({
...state,
slug_autogeneration_enabled: false,
}),
},
initialState
)

export const selectors = {
isInitializing: (state: State) => state.is_initializing,
slugAutogenerationEnabled: (state: State) => state.slug_autogeneration_enabled,
files: (state: State) => state.files,
}
4 changes: 4 additions & 0 deletions client/src/store/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ const episode = {
const mediafiles = {
isInitializing: createSelector(root.mediafiles, mediafilesStore.selectors.isInitializing),
files: createSelector(root.mediafiles, mediafilesStore.selectors.files),
slugAutogenerationEnabled: createSelector(
root.mediafiles,
mediafilesStore.selectors.slugAutogenerationEnabled
),
}

const runtime = {
Expand Down
37 changes: 37 additions & 0 deletions includes/api/episodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,24 @@ public function register_routes()
'permission_callback' => [$this, 'create_item_permissions_check'],
]
]);

register_rest_route($this->namespace, '/'.$this->rest_base.'/(?P<id>[\d]+)/build_slug', [
'args' => [
'id' => [
'description' => __('Unique identifier for the episode.', 'podlove-podcasting-plugin-for-wordpress'),
'type' => 'integer',
],
'title' => [
'type' => 'string'
]
],
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'build_slug'],
'permission_callback' => [$this, 'create_item_permissions_check'],
]
]);

register_rest_route($this->namespace, '/'.$this->rest_base.'/(?P<id>[\d]+)', [
'args' => [
'id' => [
Expand Down Expand Up @@ -665,6 +683,25 @@ public function create_item($request)
return new \WP_REST_Response(null, 500);
}

public function build_slug($request)
{
$id = $request->get_param('id');
if (!$id) {
return;
}

$episode = Episode::find_by_id($id);
if (!$episode) {
return new \Podlove\Api\Error\NotFound();
}

$title = $request->get_param('title') ?? get_the_title($episode->post_id);

$slug = sanitize_title($title);

return new \Podlove\Api\Response\CreateResponse(['slug' => $slug]);
}

public function update_item_permissions_check($request)
{
if (!current_user_can('edit_posts')) {
Expand Down
35 changes: 0 additions & 35 deletions js/src/admin/episode.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,6 @@ var PODLOVE = PODLOVE || {}
PODLOVE.Episode = function (container) {
var o = {}

// private

function maybe_update_episode_slug(title) {
if (o.slug_field.data('auto-update')) {
update_episode_slug(title)
}
}

// current ajax object to ensure only the latest one is active
var update_episode_slug_xhr

function update_episode_slug(title) {
if (update_episode_slug_xhr) update_episode_slug_xhr.abort()

update_episode_slug_xhr = $.ajax({
url: ajaxurl,
data: {
action: 'podlove-episode-slug',
title: title,
},
context: o.slug_field,
}).done(function (slug) {
$(this).val(slug).blur()
})
}

o.slug_field = container.find('[name*=slug]')

$('#_podlove_meta_subtitle').count_characters({
Expand Down Expand Up @@ -63,12 +37,6 @@ var PODLOVE = PODLOVE || {}
}
})

o.slug_field
.data('auto-update', !Boolean(o.slug_field.val())) // only auto-update if it is empty
.on('keyup', function () {
o.slug_field.data('auto-update', false) // stop autoupdate on manual change
})

var typewatch = (function () {
var timer = 0
return function (callback, ms) {
Expand Down Expand Up @@ -97,9 +65,6 @@ var PODLOVE = PODLOVE || {}

// update episode title
$('#_podlove_meta_title').attr('placeholder', title)

// maybe update episode slug
maybe_update_episode_slug(title)
})
.trigger('titleHasChanged')

Expand Down
8 changes: 0 additions & 8 deletions lib/ajax/ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public function __construct()
'analytics-global-total-downloads',
'analytics-global-total-downloads-by-show',
'analytics-csv-episodes-table',
'episode-slug',
'admin-news',
'job-create',
'job-get',
Expand Down Expand Up @@ -915,13 +914,6 @@ public function get_license_parameters_from_url()
self::respond_with_json(\Podlove\Model\License::get_license_from_url($_REQUEST['url']));
}

public function episode_slug()
{
echo sanitize_title($_REQUEST['title']);

exit;
}

private static function analytics_date_condition()
{
$from = filter_input(INPUT_GET, 'date_from');
Expand Down

0 comments on commit efe7e3a

Please sign in to comment.