Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
41 changes: 41 additions & 0 deletions src/resources/extensions/quarto/video/_tests/test-suite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<iframe data-external="1" src="https://www.youtube.com/embed/wo9vZccmqwc" title="" aria-label="Video demonstration of features" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>', 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)
Expand Down Expand Up @@ -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 = '<iframe data-external="1" src="https://players.brightcove.net/1460825906/default_default/index.html?videoId=5988531335001" allowfullscreen="" title="" aria-label="Video demonstration of features" allow="encrypted-media"></iframe>', 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)
Expand Down Expand Up @@ -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 = '<iframe data-external="1" src="https://player.vimeo.com/video/548291210" frameborder="0" title="" aria-label="Video demonstration of features" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>', 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
Expand Down Expand Up @@ -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="<video id=\"video_shortcode_videojs_video1\" class=\"video-js vjs-default-skin vjs-big-play-centered vjs-fluid\" controls preload=\"auto\" data-setup='{}' title=\"\" aria-label=\"Video demonstration of features\"><source src=\"./intro-cern.mp4\"></video>",
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')
Expand Down
28 changes: 15 additions & 13 deletions src/resources/extensions/quarto/video/video.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -89,7 +90,7 @@ local youTubeBuilder = function(params)
local YOUTUBE_EMBED = 'https://www.youtube.com/embed/'
params.src = YOUTUBE_EMBED .. match

local SNIPPET = [[<iframe data-external="1" src="{src}{start}"{width}{height} title="{title}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>]]
local SNIPPET = [[<iframe data-external="1" src="{src}{start}"{width}{height} title="{title}"{ariaLabel} frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>]]
snippet = replaceCommonAttributes(SNIPPET, params)

local result = {}
Expand All @@ -116,7 +117,7 @@ local brightcoveBuilder = function(params)

local result = {}

local SNIPPET = [[<iframe data-external="1" src="{src}"{width}{height} allowfullscreen="" title="{title}" allow="encrypted-media"></iframe>]]
local SNIPPET = [[<iframe data-external="1" src="{src}"{width}{height} allowfullscreen="" title="{title}"{ariaLabel} allow="encrypted-media"></iframe>]]
result.snippet = replaceCommonAttributes(SNIPPET, params)
result.type = VIDEO_TYPES.BRIGHTCOVE
result.src = params.src
Expand All @@ -143,7 +144,7 @@ local vimeoBuilder = function(params)
end


local SNIPPET = [[<iframe data-external="1" src="{src}"{width}{height} frameborder="0" title="{title}" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>]]
local SNIPPET = [[<iframe data-external="1" src="{src}"{width}{height} frameborder="0" title="{title}"{ariaLabel} allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>]]

local result = {}

Expand All @@ -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 = [[<video id="{id}"{width}{height} class="video-js vjs-default-skin vjs-big-play-centered {fluid}" controls preload="auto" data-setup='{}' title="{title}"><source src="{src}"></video>]]
local SNIPPET = [[<video id="{id}"{width}{height} class="video-js vjs-default-skin vjs-big-play-centered {fluid}" controls preload="auto" data-setup='{}' title="{title}"{ariaLabel}><source src="{src}"></video>]]
local snippet = params.snippet or SNIPPET
snippet = replaceCommonAttributes(snippet, params)
snippet = interpolate {
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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')
Expand All @@ -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
Expand Down
45 changes: 45 additions & 0 deletions tests/docs/smoke-all/video/video-aria-label.qmd
Original file line number Diff line number Diff line change
@@ -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" >}}

:::
Loading