@asd20/ui-next 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# [2.17.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.16.0...ui-next-v2.17.0) (2026-08-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add canonical video rendering ([295fbfd](https://github.com/academydistrict20/asd20-ui-next/commit/295fbfd38d3d66c2bc1e0070fb98a15ba6e51d02))
|
|
9
|
+
|
|
3
10
|
# [2.16.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.15.1...ui-next-v2.16.0) (2026-08-04)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -157,5 +157,30 @@ export default {
|
|
|
157
157
|
:deep(.asd20-message-table td > :last-child) {
|
|
158
158
|
margin-bottom: 0;
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
:deep(.asd20-message-video) {
|
|
162
|
+
max-width: 100%;
|
|
163
|
+
margin: 0.75rem 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:deep(.asd20-message-video__description) {
|
|
167
|
+
margin: 0 0 0.5rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:deep(.asd20-message-video__frame) {
|
|
171
|
+
position: relative;
|
|
172
|
+
width: 100%;
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
aspect-ratio: 16 / 9;
|
|
175
|
+
background: #000;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
:deep(.asd20-message-video__frame iframe) {
|
|
179
|
+
position: absolute;
|
|
180
|
+
inset: 0;
|
|
181
|
+
width: 100%;
|
|
182
|
+
height: 100%;
|
|
183
|
+
border: 0;
|
|
184
|
+
}
|
|
160
185
|
}
|
|
161
186
|
</style>
|
|
@@ -56,6 +56,22 @@ const MARK_TYPES = new Set([
|
|
|
56
56
|
'superscript',
|
|
57
57
|
])
|
|
58
58
|
const TABLE_HEADER_SCOPES = new Set(['col', 'row', 'colgroup', 'rowgroup'])
|
|
59
|
+
const VIDEO_TITLE_MAX_LENGTH = 200
|
|
60
|
+
const VIDEO_DESCRIPTION_MAX_LENGTH = 1000
|
|
61
|
+
const VIDEO_PROVIDER_RULES = Object.freeze([
|
|
62
|
+
{
|
|
63
|
+
host: 'www.youtube.com',
|
|
64
|
+
pathPattern: /^\/embed\/([A-Za-z0-9_-]{11})$/,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
host: 'www.youtube-nocookie.com',
|
|
68
|
+
pathPattern: /^\/embed\/([A-Za-z0-9_-]{11})$/,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
host: 'player.vimeo.com',
|
|
72
|
+
pathPattern: /^\/video\/([0-9]+)$/,
|
|
73
|
+
},
|
|
74
|
+
])
|
|
59
75
|
const MARK_RENDER_ORDER = Object.freeze({
|
|
60
76
|
link: 0,
|
|
61
77
|
bold: 1,
|
|
@@ -196,6 +212,45 @@ function isSafeResource(value, allowHash = false) {
|
|
|
196
212
|
}
|
|
197
213
|
}
|
|
198
214
|
|
|
215
|
+
function isCanonicalVideoEmbedSource(value) {
|
|
216
|
+
if (typeof value !== 'string' || !value) return false
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const url = new URL(value)
|
|
220
|
+
if (
|
|
221
|
+
url.protocol !== 'https:' ||
|
|
222
|
+
url.username ||
|
|
223
|
+
url.password ||
|
|
224
|
+
url.port ||
|
|
225
|
+
url.search ||
|
|
226
|
+
url.hash
|
|
227
|
+
) {
|
|
228
|
+
return false
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const rule = VIDEO_PROVIDER_RULES.find(
|
|
232
|
+
candidate => candidate.host === url.hostname.toLowerCase()
|
|
233
|
+
)
|
|
234
|
+
return Boolean(
|
|
235
|
+
rule &&
|
|
236
|
+
rule.pathPattern.test(url.pathname) &&
|
|
237
|
+
value === `https://${rule.host}${url.pathname}`
|
|
238
|
+
)
|
|
239
|
+
} catch (_error) {
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function validateRequiredCanonicalText(node, name, path, maximumLength) {
|
|
245
|
+
const attrIssue = validateStringAttribute(node, name, path, true)
|
|
246
|
+
if (attrIssue) return attrIssue
|
|
247
|
+
|
|
248
|
+
const value = node.attrs[name]
|
|
249
|
+
return !value || value !== value.trim() || value.length > maximumLength
|
|
250
|
+
? issue('malformed-attribute', path, name)
|
|
251
|
+
: null
|
|
252
|
+
}
|
|
253
|
+
|
|
199
254
|
function validateMarks(marks, path) {
|
|
200
255
|
if (marks === undefined) return null
|
|
201
256
|
if (!Array.isArray(marks)) return issue('malformed-marks', path)
|
|
@@ -396,14 +451,24 @@ function validateNode(node, path) {
|
|
|
396
451
|
return validateAtom(node, path)
|
|
397
452
|
}
|
|
398
453
|
if (node.type === 'videoEmbed') {
|
|
399
|
-
const srcIssue = validateStringAttribute(node, 'src', path)
|
|
400
|
-
if (srcIssue || (node.attrs?.src
|
|
454
|
+
const srcIssue = validateStringAttribute(node, 'src', path, true)
|
|
455
|
+
if (srcIssue || !isCanonicalVideoEmbedSource(node.attrs?.src)) {
|
|
401
456
|
return srcIssue || issue('unsafe-resource', path, 'src')
|
|
402
457
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
458
|
+
const titleIssue = validateRequiredCanonicalText(
|
|
459
|
+
node,
|
|
460
|
+
'title',
|
|
461
|
+
path,
|
|
462
|
+
VIDEO_TITLE_MAX_LENGTH
|
|
463
|
+
)
|
|
464
|
+
if (titleIssue) return titleIssue
|
|
465
|
+
const descriptionIssue = validateRequiredCanonicalText(
|
|
466
|
+
node,
|
|
467
|
+
'descriptiveText',
|
|
468
|
+
path,
|
|
469
|
+
VIDEO_DESCRIPTION_MAX_LENGTH
|
|
470
|
+
)
|
|
471
|
+
if (descriptionIssue) return descriptionIssue
|
|
407
472
|
return validateAtom(node, path)
|
|
408
473
|
}
|
|
409
474
|
if (node.type === 'table') {
|
|
@@ -666,16 +731,25 @@ function renderStandardNode(node) {
|
|
|
666
731
|
)}${attribute('height', node.attrs.height)}>`
|
|
667
732
|
}
|
|
668
733
|
if (node.type === 'videoEmbed') {
|
|
669
|
-
return `<div data-cc-node="video"${attribute(
|
|
734
|
+
return `<div class="asd20-message-video" data-cc-node="video"${attribute(
|
|
670
735
|
'data-src',
|
|
671
736
|
node.attrs.src
|
|
672
737
|
)}${attribute('data-title', node.attrs.title)}${attribute(
|
|
673
738
|
'data-descriptive-text',
|
|
674
739
|
node.attrs.descriptiveText
|
|
675
|
-
)}><
|
|
740
|
+
)}><p class="asd20-message-video__description">${escapeHtml(
|
|
741
|
+
node.attrs.descriptiveText
|
|
742
|
+
)}</p><div class="asd20-message-video__frame"><iframe${attribute(
|
|
743
|
+
'src',
|
|
744
|
+
node.attrs.src
|
|
745
|
+
)}${attribute(
|
|
676
746
|
'title',
|
|
677
|
-
node.attrs.title
|
|
678
|
-
)}
|
|
747
|
+
node.attrs.title
|
|
748
|
+
)} loading="lazy" referrerpolicy="strict-origin-when-cross-origin"${attribute(
|
|
749
|
+
'allowfullscreen',
|
|
750
|
+
'',
|
|
751
|
+
true
|
|
752
|
+
)}></iframe></div></div>`
|
|
679
753
|
}
|
|
680
754
|
if (node.type === 'table') return renderTable(node)
|
|
681
755
|
if (node.type === 'tableRow') return `<tr>${renderChildren(node)}</tr>`
|