From db1d28b2d642af977c5f9c73c31f7456a30a6207 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 6 Oct 2025 17:53:44 -0500 Subject: [PATCH 1/2] video - add aria-label parameter --- news/changelog-1.9.md | 1 + .../quarto/video/_tests/test-suite.lua | 41 +++++++++++++++++++ .../extensions/quarto/video/video.lua | 28 +++++++------ 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/news/changelog-1.9.md b/news/changelog-1.9.md index 593a9a5b737..6d73818471c 100644 --- a/news/changelog-1.9.md +++ b/news/changelog-1.9.md @@ -19,6 +19,7 @@ All changes included in 1.9: ### `html` - ([#13413](https://github.com/quarto-dev/quarto-cli/issues/13413)): Fix uncentered play button in `video` shortcodes from cross-reference divs. (author: @bruvellu) +- ([#13508](https://github.com/quarto-dev/quarto-cli/issues/13508)): Add `aria-label` support to `video` shortcode for improved accessibility. ### `typst` diff --git a/src/resources/extensions/quarto/video/_tests/test-suite.lua b/src/resources/extensions/quarto/video/_tests/test-suite.lua index 6436f4d9ef8..485334db829 100644 --- a/src/resources/extensions/quarto/video/_tests/test-suite.lua +++ b/src/resources/extensions/quarto/video/_tests/test-suite.lua @@ -139,6 +139,15 @@ function TestYouTubeBuilder:testHeightWidth() checkYouTubeBuilder(params, expected) end +function TestYouTubeBuilder:testAriaLabel() + local params = { + src = 'https://www.youtube.com/embed/wo9vZccmqwc', + ariaLabel = 'Video demonstration of features', + } + local expected = {snippet = '', type = VIDEO_TYPES.YOUTUBE, src='https://www.youtube.com/embed/wo9vZccmqwc', videoId = 'wo9vZccmqwc'} + checkYouTubeBuilder(params, expected) +end + TestBrightcoveBuilder = {} local checkBrightcoveBuilder = function(params, expected) result = helpers.brightcoveBuilder(params) @@ -206,6 +215,15 @@ function TestBrightcoveBuilder:testHeightWidth() checkBrightcoveBuilder(params, expected) end +function TestBrightcoveBuilder:testAriaLabel() + local params = { + src = 'https://players.brightcove.net/1460825906/default_default/index.html?videoId=5988531335001', + ariaLabel = 'Video demonstration of features', + } + local expected = {snippet = '', type = VIDEO_TYPES.BRIGHTCOVE, src='https://players.brightcove.net/1460825906/default_default/index.html?videoId=5988531335001' } + checkBrightcoveBuilder(params, expected) +end + TestVimeoBuilder = {} local checkVimeoBuilder = function(params, expected) result = helpers.vimeoBuilder(params) @@ -288,6 +306,15 @@ function TestVimeoBuilder:testHeightWidth() checkVimeoBuilder(params, expected) end +function TestVimeoBuilder:testAriaLabel() + local params = { + src = 'https://vimeo.com/548291210', + ariaLabel = 'Video demonstration of features', + } + local expected = {snippet = '', type = VIDEO_TYPES.VIMEO, src='https://player.vimeo.com/video/548291210', videoId = '548291210' } + checkVimeoBuilder(params, expected) +end + TestVideoJSBuilder = {} local checkVideoJSBuilder = function(params, expected) VIDEO_SHORTCODE_NUM_VIDEOJS = 0 -- Reset Counter @@ -368,6 +395,20 @@ function TestVideoJSBuilder:testHeightWidth() checkVideoJSBuilder(params, expected) end +function TestVideoJSBuilder:testAriaLabel() + local params = { + ariaLabel = 'Video demonstration of features', + src = './intro-cern.mp4' + } + local expected = { + snippet="", + type="VIDEOJS", + src='./intro-cern.mp4', + id="video_shortcode_videojs_video1" + } + checkVideoJSBuilder(params, expected) +end + TestVideoResponsive = {} function TestVideoResponsive:testNoResponsive() result = helpers.wrapWithDiv('fake-to-wrap') diff --git a/src/resources/extensions/quarto/video/video.lua b/src/resources/extensions/quarto/video/video.lua index 72c8a2fae4a..f79e0d1fce7 100644 --- a/src/resources/extensions/quarto/video/video.lua +++ b/src/resources/extensions/quarto/video/video.lua @@ -66,6 +66,7 @@ local replaceCommonAttributes = function(snippet, params) height = params.height and ' height="' .. params.height .. '"' or '', width = params.width and ' width="' .. params.width .. '"' or '', title = params.title or '', + ariaLabel = params.ariaLabel and ' aria-label="' .. params.ariaLabel .. '"' or '', } return result end @@ -89,7 +90,7 @@ local youTubeBuilder = function(params) local YOUTUBE_EMBED = 'https://www.youtube.com/embed/' params.src = YOUTUBE_EMBED .. match - local SNIPPET = [[]] + local SNIPPET = [[]] snippet = replaceCommonAttributes(SNIPPET, params) local result = {} @@ -116,7 +117,7 @@ local brightcoveBuilder = function(params) local result = {} - local SNIPPET = [[]] + local SNIPPET = [[]] result.snippet = replaceCommonAttributes(SNIPPET, params) result.type = VIDEO_TYPES.BRIGHTCOVE result.src = params.src @@ -143,7 +144,7 @@ local vimeoBuilder = function(params) end - local SNIPPET = [[]] + local SNIPPET = [[]] local result = {} @@ -160,7 +161,7 @@ local videoJSBuilder = function(params) VIDEO_SHORTCODE_NUM_VIDEOJS = VIDEO_SHORTCODE_NUM_VIDEOJS + 1 local id = "video_shortcode_videojs_video" .. VIDEO_SHORTCODE_NUM_VIDEOJS - local SNIPPET = [[]] + local SNIPPET = [[]] local snippet = params.snippet or SNIPPET snippet = replaceCommonAttributes(snippet, params) snippet = interpolate { @@ -176,14 +177,14 @@ local videoJSBuilder = function(params) result.id = id return result end -local getSnippetFromBuilders = function(src, height, width, title, start) +local getSnippetFromBuilders = function(src, height, width, title, start, ariaLabel) local builderList = { youTubeBuilder, brightcoveBuilder, vimeoBuilder, videoJSBuilder} - local params = { src = src, height = height, width = width, title = title, start = start } + local params = { src = src, height = height, width = width, title = title, start = start, ariaLabel = ariaLabel } for i = 1, #builderList do local builtSnippet = builderList[i](params) @@ -211,12 +212,12 @@ function formatAsciiDocVideo(src, type) return 'video::' .. src .. '[' .. type .. ']' end -local function asciidocVideo(src, height, width, title, start, _aspectRatio) +local function asciidocVideo(src, height, width, title, start, _aspectRatio, ariaLabel) local asciiDocVideoRawBlock = function(src, type) return pandoc.RawBlock("asciidoc", formatAsciiDocVideo(src, type) .. '\n\n') end - local videoSnippetAndType = getSnippetFromBuilders(src, height, width, title, start) + local videoSnippetAndType = getSnippetFromBuilders(src, height, width, title, start, ariaLabel) if videoSnippetAndType.type == VIDEO_TYPES.YOUTUBE then -- Use the video id to form an asciidoc video if videoSnippetAndType.videoId ~= nil then @@ -233,13 +234,13 @@ local function asciidocVideo(src, height, width, title, start, _aspectRatio) end -function htmlVideo(src, height, width, title, start, aspectRatio) +function htmlVideo(src, height, width, title, start, aspectRatio, ariaLabel) -- https://github.com/quarto-dev/quarto-cli/issues/6833 -- handle partially-specified width, height, and aspectRatio if aspectRatio then -- https://github.com/quarto-dev/quarto-cli/issues/11699#issuecomment-2549219533 - -- we remove quotes as a + -- we remove quotes as a -- local workaround for inconsistent shortcode argument parsing on our end. -- -- removing quotes in general is not a good idea, but the @@ -256,7 +257,7 @@ function htmlVideo(src, height, width, title, start, aspectRatio) end end - local videoSnippetAndType = getSnippetFromBuilders(src, height, width, title, start) + local videoSnippetAndType = getSnippetFromBuilders(src, height, width, title, start, ariaLabel) local videoSnippet videoSnippet = videoSnippetAndType.snippet @@ -326,6 +327,7 @@ return { local heightValue = checkArg(kwargs, 'height') local widthValue = checkArg(kwargs, 'width') local aspectRatio = checkArg(kwargs, 'aspectRatio') + local ariaLabelValue = checkArg(kwargs, 'aria-label') if isEmpty(aspectRatio) then aspectRatio = checkArg(kwargs, 'aspect-ratio') @@ -343,9 +345,9 @@ return { end if quarto.doc.is_format("html:js") then - return htmlVideo(srcValue, heightValue, widthValue, titleValue, startValue, aspectRatio) + return htmlVideo(srcValue, heightValue, widthValue, titleValue, startValue, aspectRatio, ariaLabelValue) elseif quarto.doc.is_format("asciidoc") then - return asciidocVideo(srcValue, heightValue, widthValue, titleValue, startValue, aspectRatio) + return asciidocVideo(srcValue, heightValue, widthValue, titleValue, startValue, aspectRatio, ariaLabelValue) elseif quarto.doc.is_format("markdown") then if srcValue:sub(1, 4) == "http" then -- For remote videos, we can emit a link From 10dc33c9e0c20bce1d1df074ac3fc788473417bb Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 6 Oct 2025 17:54:29 -0500 Subject: [PATCH 2/2] test for #13508 --- .../docs/smoke-all/video/video-aria-label.qmd | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/docs/smoke-all/video/video-aria-label.qmd diff --git a/tests/docs/smoke-all/video/video-aria-label.qmd b/tests/docs/smoke-all/video/video-aria-label.qmd new file mode 100644 index 00000000000..e572f6f9b56 --- /dev/null +++ b/tests/docs/smoke-all/video/video-aria-label.qmd @@ -0,0 +1,45 @@ +--- +title: "Video Shortcode aria-label Support (Issue #13508)" +_quarto: + tests: + html: + ensureHtmlElements: + - + - '#youtube-aria-label-test iframe[aria-label="Video demonstration of Positron features"]' + + - '#vimeo-aria-label-test iframe[aria-label="Accessible Vimeo video description"]' + + - '#video-js-aria-label-test video[aria-label="Local video with custom label"]' + + - '#brightcove-aria-label-test iframe[aria-label="Brightcove video accessible label"]' +--- + +Test for aria-label support in video shortcode for improved accessibility. + +::: {#youtube-aria-label-test} + +{{< video https://www.youtube.com/embed/wo9vZccmqwc + aria-label="Video demonstration of Positron features" >}} + +::: + +::: {#vimeo-aria-label-test} + +{{< video https://vimeo.com/548291297 + aria-label="Accessible Vimeo video description" >}} + +::: + +::: {#video-js-aria-label-test} + +{{< video local-video.mp4 + aria-label="Local video with custom label" >}} + +::: + +::: {#brightcove-aria-label-test} + +{{< video https://players.brightcove.net/1460825906/default_default/index.html?videoId=5988531335001 + aria-label="Brightcove video accessible label" >}} + +:::