From 2a6df70f40c78d63745128e1ed312e262751e5fc Mon Sep 17 00:00:00 2001 From: Christiaan Scheermeijer Date: Wed, 16 Jun 2021 11:26:28 +0200 Subject: [PATCH] feat(project): add structured data for movie and series screens --- src/components/Layout/Layout.tsx | 1 - src/screens/Movie/Movie.tsx | 5 +++- src/screens/Series/Series.tsx | 3 ++ src/utils/datetime.ts | 24 ++++++++++++++++ src/utils/structuredData.ts | 49 ++++++++++++++++++++++++++++++++ types/playlist.d.ts | 2 ++ 6 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/utils/datetime.ts create mode 100644 src/utils/structuredData.ts diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 0eea79c1a..50e9e33f3 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -36,7 +36,6 @@ const Layout: FC = ({ children }) => { - {banner && } {banner && } diff --git a/src/screens/Movie/Movie.tsx b/src/screens/Movie/Movie.tsx index b88a98c52..061d3c71f 100644 --- a/src/screens/Movie/Movie.tsx +++ b/src/screens/Movie/Movie.tsx @@ -6,11 +6,12 @@ import { Helmet } from 'react-helmet'; import { useFavorites } from '../../stores/FavoritesStore'; import { ConfigContext } from '../../providers/ConfigProvider'; import useBlurImageUpdater from '../../hooks/useBlurImageUpdater'; -import { cardUrl, videoUrl } from '../../utils/formatting'; +import { cardUrl, movieURL, videoUrl } from '../../utils/formatting'; import type { PlaylistItem } from '../../../types/playlist'; import VideoComponent from '../../components/Video/Video'; import Shelf from '../../containers/Shelf/Shelf'; import useMedia from '../../hooks/useMedia'; +import { generateMovieJSONLD } from '../../utils/structuredData'; type MovieRouteParams = { id: string; @@ -49,6 +50,7 @@ const Movie = ( {item.title} - {config.siteName} + {item ? : null} @@ -66,6 +68,7 @@ const Movie = ( {item.tags.split(',').map(tag => )} + {item ? : null} {item.title} - {config.siteName} + {seriesPlaylist && item ? : null} @@ -77,6 +79,7 @@ const Series = ( {item.tags.split(',').map(tag => )} + {seriesPlaylist && item ? : null} 0) isoString += hours + 'H'; + if (minutes > 0) isoString += minutes + 'M'; + if (seconds > 0) isoString += seconds + 'S'; + + return isoString; +} diff --git a/src/utils/structuredData.ts b/src/utils/structuredData.ts new file mode 100644 index 000000000..79af7c326 --- /dev/null +++ b/src/utils/structuredData.ts @@ -0,0 +1,49 @@ +import type { Playlist, PlaylistItem } from '../../types/playlist'; + +import { episodeURL, movieURL } from './formatting'; +import { secondsToISO8601 } from './datetime'; + +export const generateSeriesMetadata = (seriesPlaylist: Playlist) => { + const seriesCanonical = `${window.location.origin}/${episodeURL(seriesPlaylist)}`; + + return { + '@type': 'TVSeries', + '@id': seriesCanonical, + name: seriesPlaylist.title, + numberOfEpisodes: seriesPlaylist.playlist.length, + numberOfSeasons: seriesPlaylist.playlist.reduce(function (list, playlistItem) { + return !playlistItem.seasonNumber || list.includes(playlistItem.seasonNumber) ? list : list.concat(playlistItem.seasonNumber); + }, [] as string[]).length + }; +} + +export const generateEpisodeJSONLD = (seriesPlaylist: Playlist, episode: PlaylistItem) => { + const episodeCanonical = `${window.location.origin}/${episodeURL(seriesPlaylist, episode.mediaid)}`; + const seriesMetadata = generateSeriesMetadata(seriesPlaylist); + + return JSON.stringify({ + '@context': 'http://schema.org/', + '@type': 'TVEpisode', + '@id': episodeCanonical, + episodeNumber: episode.episodeNumber, + seasonNumber: episode.seasonNumber, + name: episode.title, + uploadDate: secondsToISO8601(episode.pubdate), + partOfSeries: seriesMetadata, + }); +} + +export const generateMovieJSONLD = (item: PlaylistItem) => { + const movieCanonical = `${window.location.origin}/${movieURL(item)}`; + + return JSON.stringify({ + '@context': 'http://schema.org/', + '@type': 'VideoObject', + '@id': movieCanonical, + name: item.title, + description: item.description, + duration: secondsToISO8601(item.duration, true), + thumbnailUrl: item.image, + uploadDate: secondsToISO8601(item.pubdate) + }); +} diff --git a/types/playlist.d.ts b/types/playlist.d.ts index e82e38f08..362281b5c 100644 --- a/types/playlist.d.ts +++ b/types/playlist.d.ts @@ -28,6 +28,8 @@ export type PlaylistItem = { rating: string; sources: Source[]; seriesId?: string; + episodeNumber?: string; + seasonNumber?: string; tags: string; title: string; tracks: Track[];