@asd20/ui-next 2.18.0 → 2.20.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,19 @@
1
1
  # Changelog
2
2
 
3
+ # [2.20.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.19.0...ui-next-v2.20.0) (2026-08-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * adjust video descriptions rendering for tiptap messages ([fbe3171](https://github.com/academydistrict20/asd20-ui-next/commit/fbe3171d365aeffeb7f66add00756ab9689f40a2))
9
+
10
+ # [2.19.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.18.0...ui-next-v2.19.0) (2026-08-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * add azure blob mp4 and myvrspot video urls for tiptap editor ([b7bc2e9](https://github.com/academydistrict20/asd20-ui-next/commit/b7bc2e94bf8502bb990bfb063c14b6631f981f80))
16
+
3
17
  # [2.18.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.17.0...ui-next-v2.18.0) (2026-08-06)
4
18
 
5
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.18.0",
3
+ "version": "2.20.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -59,6 +59,10 @@ const MARK_TYPES = new Set([
59
59
  const TABLE_HEADER_SCOPES = new Set(['col', 'row', 'colgroup', 'rowgroup'])
60
60
  const VIDEO_TITLE_MAX_LENGTH = 200
61
61
  const VIDEO_DESCRIPTION_MAX_LENGTH = 1000
62
+ const MYVRSPOT_VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]{16,256}$/
63
+ const MYVRSPOT_HOST = 'live.myvrspot.com'
64
+ const ASD20_VIDEO_HOST = 'asd20websitestorage.blob.core.windows.net'
65
+ const ASD20_VIDEO_PATH_PREFIX = '/asd20-images/videos/'
62
66
  const VIDEO_PROVIDER_RULES = Object.freeze([
63
67
  {
64
68
  host: 'www.youtube.com',
@@ -236,14 +240,36 @@ function isCanonicalVideoEmbedSource(value) {
236
240
  url.username ||
237
241
  url.password ||
238
242
  url.port ||
239
- url.search ||
240
243
  url.hash
241
244
  ) {
242
245
  return false
243
246
  }
244
247
 
248
+ const host = url.hostname.toLowerCase()
249
+ if (host === MYVRSPOT_HOST) {
250
+ const videoId = url.searchParams.get('v') || ''
251
+ return (
252
+ url.pathname === '/iframe' &&
253
+ MYVRSPOT_VIDEO_ID_PATTERN.test(videoId) &&
254
+ url.searchParams.get('share') === '1' &&
255
+ [...url.searchParams].length === 2 &&
256
+ value === `https://${MYVRSPOT_HOST}/iframe?v=${videoId}&share=1`
257
+ )
258
+ }
259
+
260
+ if (host === ASD20_VIDEO_HOST) {
261
+ return (
262
+ !url.search &&
263
+ url.pathname.startsWith(ASD20_VIDEO_PATH_PREFIX) &&
264
+ url.pathname.toLowerCase().endsWith('.mp4') &&
265
+ value === `https://${ASD20_VIDEO_HOST}${url.pathname}`
266
+ )
267
+ }
268
+
269
+ if (url.search) return false
270
+
245
271
  const rule = VIDEO_PROVIDER_RULES.find(
246
- candidate => candidate.host === url.hostname.toLowerCase()
272
+ candidate => candidate.host === host
247
273
  )
248
274
  return Boolean(
249
275
  rule &&
@@ -265,6 +291,16 @@ function validateRequiredCanonicalText(node, name, path, maximumLength) {
265
291
  : null
266
292
  }
267
293
 
294
+ function validateOptionalCanonicalText(node, name, path, maximumLength) {
295
+ const value = node.attrs?.[name]
296
+ if (value === undefined) return null
297
+ return typeof value !== 'string' ||
298
+ value !== value.trim() ||
299
+ value.length > maximumLength
300
+ ? issue('malformed-attribute', path, name)
301
+ : null
302
+ }
303
+
268
304
  function validateMarks(marks, path) {
269
305
  if (marks === undefined) return null
270
306
  if (!Array.isArray(marks)) return issue('malformed-marks', path)
@@ -505,7 +541,7 @@ function validateNode(node, path) {
505
541
  VIDEO_TITLE_MAX_LENGTH
506
542
  )
507
543
  if (titleIssue) return titleIssue
508
- const descriptionIssue = validateRequiredCanonicalText(
544
+ const descriptionIssue = validateOptionalCanonicalText(
509
545
  node,
510
546
  'descriptiveText',
511
547
  path,
@@ -783,15 +819,14 @@ function renderStandardNode(node) {
783
819
  .join('')}</div></figure>`
784
820
  }
785
821
  if (node.type === 'videoEmbed') {
822
+ const description = node.attrs?.descriptiveText || ''
786
823
  return `<div class="asd20-message-video" data-cc-node="video"${attribute(
787
824
  'data-src',
788
825
  node.attrs.src
789
826
  )}${attribute('data-title', node.attrs.title)}${attribute(
790
827
  'data-descriptive-text',
791
- node.attrs.descriptiveText
792
- )}><p class="asd20-message-video__description">${escapeHtml(
793
- node.attrs.descriptiveText
794
- )}</p><div class="asd20-message-video__frame"><iframe${attribute(
828
+ description
829
+ )}><div class="asd20-message-video__frame"><iframe${attribute(
795
830
  'src',
796
831
  node.attrs.src
797
832
  )}${attribute(
@@ -801,7 +836,13 @@ function renderStandardNode(node) {
801
836
  'allowfullscreen',
802
837
  '',
803
838
  true
804
- )}></iframe></div></div>`
839
+ )}></iframe></div>${
840
+ description
841
+ ? `<p class="asd20-message-video__description">${escapeHtml(
842
+ description
843
+ )}</p>`
844
+ : ''
845
+ }</div>`
805
846
  }
806
847
  if (node.type === 'table') return renderTable(node)
807
848
  if (node.type === 'tableRow') return `<tr>${renderChildren(node)}</tr>`