From 749734d23bb9f43ac4f77cab9ea69d449f59d6c5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 21 Jun 2023 11:20:28 +0200 Subject: [PATCH] Do not allow YouTube video URLs when using the URLPicker --- .../components/ContentSelector.tsx | 1 + .../frontend_apps/components/URLPicker.tsx | 14 ++++++++- .../components/test/URLPicker-test.js | 31 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lms/static/scripts/frontend_apps/components/ContentSelector.tsx b/lms/static/scripts/frontend_apps/components/ContentSelector.tsx index b5df3cc143..5c6c1436d4 100644 --- a/lms/static/scripts/frontend_apps/components/ContentSelector.tsx +++ b/lms/static/scripts/frontend_apps/components/ContentSelector.tsx @@ -200,6 +200,7 @@ export default function ContentSelector({ defaultURL={getDefaultValue('url')} onCancel={cancelDialog} onSelectURL={selectURL} + youtubeEnabled={youtubeEnabled} /> ); break; diff --git a/lms/static/scripts/frontend_apps/components/URLPicker.tsx b/lms/static/scripts/frontend_apps/components/URLPicker.tsx index 354cbf5330..1358b32338 100644 --- a/lms/static/scripts/frontend_apps/components/URLPicker.tsx +++ b/lms/static/scripts/frontend_apps/components/URLPicker.tsx @@ -1,6 +1,7 @@ import { Button, ModalDialog, Input } from '@hypothesis/frontend-shared'; import { useRef, useState } from 'preact/hooks'; +import { isYouTubeURL } from '../utils/youtube'; import UIMessage from './UIMessage'; export type URLPickerProps = { @@ -10,6 +11,8 @@ export type URLPickerProps = { onCancel: () => void; /** Callback invoked with the entered URL when the user accepts the dialog */ onSelectURL: (url: string) => void; + /** Indicates if YouTube transcript annotations are enabled */ + youtubeEnabled?: boolean; }; /** @@ -20,6 +23,7 @@ export default function URLPicker({ defaultURL = '', onCancel, onSelectURL, + youtubeEnabled = false, }: URLPickerProps) { const input = useRef(null); const form = useRef(null); @@ -31,7 +35,9 @@ export default function URLPicker({ const submit = (event: Event) => { event.preventDefault(); try { - const url = new URL(input.current!.value); + const rawInputValue = input.current!.value; + const url = new URL(rawInputValue); + if (!url.protocol.startsWith('http')) { if (url.protocol.startsWith('file')) { setError( @@ -40,6 +46,12 @@ export default function URLPicker({ } else { setError('Please use a URL that starts with "http" or "https"'); } + } else if (isYouTubeURL(rawInputValue)) { + setError( + youtubeEnabled + ? 'To annotate a video, go back and choose the YouTube option.' + : 'Annotating YouTube videos is not yet supported. This feature is coming soon.' + ); } else { onSelectURL(input.current!.value); } diff --git a/lms/static/scripts/frontend_apps/components/test/URLPicker-test.js b/lms/static/scripts/frontend_apps/components/test/URLPicker-test.js index 6b98441fbc..755568804c 100644 --- a/lms/static/scripts/frontend_apps/components/test/URLPicker-test.js +++ b/lms/static/scripts/frontend_apps/components/test/URLPicker-test.js @@ -90,6 +90,37 @@ describe('URLPicker', () => { ); }); + [ + { + youtubeEnabled: true, + expectedError: + 'To annotate a video, go back and choose the YouTube option.', + }, + { + youtubeEnabled: false, + expectedError: + 'Annotating YouTube videos is not yet supported. This feature is coming soon.', + }, + ].forEach(({ youtubeEnabled, expectedError }) => { + it('does not invoke `onSelectURL` if URL is for a YouTube video', () => { + const onSelectURL = sinon.stub(); + + const wrapper = renderUrlPicker({ onSelectURL, youtubeEnabled }); + wrapper.find('input').getDOMNode().value = 'https://youtu.be/EU6TDnV5osM'; + + wrapper + .find('button[data-testid="submit-button"]') + .props() + .onClick(new Event('click')); + wrapper.update(); + + assert.notCalled(onSelectURL); + const errorMessage = wrapper.find('UIMessage[status="error"]'); + assert.isTrue(errorMessage.exists()); + assert.include(errorMessage.text(), expectedError); + }); + }); + it( 'should pass a11y checks', checkAccessibility({