From 7c3d15e31a219f255cc29669cc0ca0996d5f1a92 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 12 Mar 2024 18:53:17 +0800 Subject: [PATCH] Add some scrolling on youtube videos --- .../api/src/jobs/process-youtube-video.ts | 88 +++++++++++++++++++ .../components/templates/article/Article.tsx | 22 +++++ packages/web/styles/articleInnerStyling.css | 28 ++++++ 3 files changed, 138 insertions(+) create mode 100644 packages/api/src/jobs/process-youtube-video.ts diff --git a/packages/api/src/jobs/process-youtube-video.ts b/packages/api/src/jobs/process-youtube-video.ts new file mode 100644 index 000000000..82bf1f946 --- /dev/null +++ b/packages/api/src/jobs/process-youtube-video.ts @@ -0,0 +1,88 @@ +import { logger } from '../utils/logger' +import { authTrx } from '../repository' +import { libraryItemRepository } from '../repository/library_item' +import { LibraryItem, LibraryItemState } from '../entity/library_item' + +import { Video, Client as YouTubeClient } from 'youtubei' + +export interface ProcessYouTubeVideoJobData { + userId: string + libraryItemId: string +} + +export const PROCESS_YOUTUBE_VIDEO_JOB_NAME = 'process-youtube-video' + +const calculateWordCount = (durationInSeconds: number): number => { + // Calculate word count using the formula: word count = read time (in seconds) * words per second + // Assuming average reading speed is 235 words per minute (or about 3.92 words per second) + const wordsPerSecond = 3.92 + const wordCount = Math.round(durationInSeconds * wordsPerSecond) + return wordCount +} + +export const processYouTubeVideo = async ( + jobData: ProcessYouTubeVideoJobData +) => { + try { + const libraryItem = await authTrx( + async (tx) => + tx + .withRepository(libraryItemRepository) + .findById(jobData.libraryItemId), + undefined, + jobData.userId + ) + if (!libraryItem || libraryItem.state !== LibraryItemState.Succeeded) { + logger.info( + `Not ready to get YouTube metadata job state: ${ + libraryItem?.state ?? 'null' + }` + ) + return + } + + const u = new URL(libraryItem.originalUrl) + const videoId = u.searchParams.get('v') + + if (!videoId) { + console.warn('no video id for supplied youtube url', { + url: libraryItem.originalUrl, + }) + return + } + + let needsUpdate = false + const youtube = new YouTubeClient() + const video = await youtube.getVideo(videoId) + if (!video) { + console.warn('no video found for youtube url', { + url: libraryItem.originalUrl, + }) + return + } + + if (video.description && libraryItem.description !== video.description) { + needsUpdate = true + libraryItem.description = video.description + } + + if ('duration' in video && (video as Video).duration > 0) { + needsUpdate = true + libraryItem.wordCount = calculateWordCount((video as Video).duration) + } + + if (needsUpdate) { + const _ = await authTrx( + async (t) => { + return t + .getRepository(LibraryItem) + .update(jobData.libraryItemId, libraryItem) + }, + undefined, + jobData.userId + ) + } + } catch (err) { + console.log('error creating summary: ', err) + } +} diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index 5f7d48ba4..91ef82161 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -115,6 +115,28 @@ export function Article(props: ArticleProps): JSX.Element { } }, 2500) + useEffect(() => { + const youtubePlayer = document.getElementById('_omnivore_youtube_video') + + const updateScroll = () => { + console.log('scroll y: ', window.scrollY, youtubePlayer) + + if (youtubePlayer) { + if (window.scrollY > 200) { + youtubePlayer.classList.add('is-sticky') + } else { + youtubePlayer.classList.remove('is-sticky') + } + } + } + if (youtubePlayer) { + window.addEventListener('scroll', updateScroll) + } + return () => { + window.removeEventListener('scroll', updateScroll) // clean up + } + }, [props]) + // Scroll to initial anchor position useEffect(() => { if (typeof window === 'undefined') { diff --git a/packages/web/styles/articleInnerStyling.css b/packages/web/styles/articleInnerStyling.css index c38f290a7..b0a46dc39 100644 --- a/packages/web/styles/articleInnerStyling.css +++ b/packages/web/styles/articleInnerStyling.css @@ -610,3 +610,31 @@ white-space: pre-wrap; overflow-wrap: break-word; } + +.is-sticky { + position: fixed; + right: 5px; + bottom: 5px; + top: auto; + left: auto; + max-width: 400px; + max-height: 222px; + width: 400px; + height: 222px; + animation-name: fadeInUp; + animation-duration: 0.5s; + animation-fill-mode: both; +} + +@keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} \ No newline at end of file