@commonpub/layer 0.21.11 → 0.21.13
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.
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const props = defineProps<{ content: Record<string, unknown> }>();
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
// so authors who paste a YouTube/Vimeo URL into the generic Embed block
|
|
6
|
-
// don't end up with an iframe that the provider refuses to render
|
|
7
|
-
// (X-Frame-Options / CSP frame-ancestors). Mirrors BlockVideoView.
|
|
8
|
-
const embedUrl = computed(() => {
|
|
9
|
-
const raw = (props.content.url as string) || '';
|
|
10
|
-
if (!raw) return '';
|
|
11
|
-
const yt = raw.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{6,})/);
|
|
12
|
-
if (yt) return `https://www.youtube-nocookie.com/embed/${yt[1]}`;
|
|
13
|
-
const vimeo = raw.match(/vimeo\.com\/(\d+)/);
|
|
14
|
-
if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
|
|
15
|
-
// Anything else: allow http(s) only (block javascript:, data:, etc.).
|
|
16
|
-
if (raw.startsWith('https://') || raw.startsWith('http://')) return raw;
|
|
17
|
-
return '';
|
|
18
|
-
});
|
|
4
|
+
const embedUrl = computed(() => toEmbedUrl(props.content.url as string));
|
|
19
5
|
</script>
|
|
20
6
|
|
|
21
7
|
<template>
|
|
@@ -2,23 +2,7 @@
|
|
|
2
2
|
const props = defineProps<{ content: Record<string, unknown> }>();
|
|
3
3
|
|
|
4
4
|
const url = computed(() => (props.content.url as string) || '');
|
|
5
|
-
|
|
6
|
-
const embedUrl = computed(() => {
|
|
7
|
-
const u = url.value;
|
|
8
|
-
if (!u) return '';
|
|
9
|
-
|
|
10
|
-
// YouTube — handle watch + youtu.be + /embed/ + /v/ + /shorts/.
|
|
11
|
-
const ytMatch = u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{6,})/);
|
|
12
|
-
if (ytMatch) return `https://www.youtube-nocookie.com/embed/${ytMatch[1]}`;
|
|
13
|
-
|
|
14
|
-
// Vimeo — extract video ID and construct safe embed URL
|
|
15
|
-
const vimeoMatch = u.match(/vimeo\.com\/(\d+)/);
|
|
16
|
-
if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
|
|
17
|
-
|
|
18
|
-
// Only allow http/https URLs for unknown platforms — block javascript:, data:, etc.
|
|
19
|
-
if (u.startsWith('https://') || u.startsWith('http://')) return u;
|
|
20
|
-
return '';
|
|
21
|
-
});
|
|
5
|
+
const embedUrl = computed(() => toEmbedUrl(url.value));
|
|
22
6
|
|
|
23
7
|
const platform = computed(() => {
|
|
24
8
|
const u = url.value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"vue": "^3.4.0",
|
|
51
51
|
"vue-router": "^4.3.0",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
-
"@commonpub/
|
|
53
|
+
"@commonpub/config": "0.13.0",
|
|
54
54
|
"@commonpub/docs": "0.6.3",
|
|
55
|
-
"@commonpub/
|
|
56
|
-
"@commonpub/schema": "0.16.0",
|
|
55
|
+
"@commonpub/auth": "0.6.0",
|
|
57
56
|
"@commonpub/learning": "0.5.2",
|
|
58
|
-
"@commonpub/
|
|
57
|
+
"@commonpub/schema": "0.16.0",
|
|
59
58
|
"@commonpub/explainer": "0.7.15",
|
|
60
|
-
"@commonpub/
|
|
59
|
+
"@commonpub/ui": "0.8.5",
|
|
60
|
+
"@commonpub/editor": "0.7.10",
|
|
61
61
|
"@commonpub/server": "2.54.2",
|
|
62
|
-
"@commonpub/
|
|
62
|
+
"@commonpub/protocol": "0.10.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate a pasted video / embed URL into an iframe-embeddable form.
|
|
3
|
+
*
|
|
4
|
+
* Authors commonly paste the watch-page URL (which provider iframes
|
|
5
|
+
* refuse to render via X-Frame-Options). This helper detects YouTube
|
|
6
|
+
* and Vimeo URLs and rewrites them to the canonical embed form,
|
|
7
|
+
* preserving start-time (`?t=`) and Vimeo private-video hashes.
|
|
8
|
+
*
|
|
9
|
+
* Unknown providers pass through unchanged if they use http(s),
|
|
10
|
+
* otherwise return empty string (blocks `javascript:`, `data:` etc).
|
|
11
|
+
*
|
|
12
|
+
* Returns `''` when the input is empty or rejected.
|
|
13
|
+
*/
|
|
14
|
+
export function toEmbedUrl(raw: string | null | undefined): string {
|
|
15
|
+
if (!raw) return '';
|
|
16
|
+
|
|
17
|
+
// YouTube — watch / embed / v / shorts / youtu.be. The capture group is
|
|
18
|
+
// the 11-char video ID; we accept 6+ to tolerate future ID-length shifts.
|
|
19
|
+
const yt = raw.match(
|
|
20
|
+
/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{6,})/,
|
|
21
|
+
);
|
|
22
|
+
if (yt) {
|
|
23
|
+
const id = yt[1];
|
|
24
|
+
const start = extractStartSeconds(raw);
|
|
25
|
+
return start > 0
|
|
26
|
+
? `https://www.youtube-nocookie.com/embed/${id}?start=${start}`
|
|
27
|
+
: `https://www.youtube-nocookie.com/embed/${id}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Vimeo — public (vimeo.com/ID) and private (vimeo.com/ID/HASH). The
|
|
31
|
+
// hash is required for unlisted/private videos; without it the iframe
|
|
32
|
+
// 403s.
|
|
33
|
+
const vimeo = raw.match(/vimeo\.com\/(\d+)(?:\/([a-zA-Z0-9]+))?/);
|
|
34
|
+
if (vimeo) {
|
|
35
|
+
const id = vimeo[1];
|
|
36
|
+
const hash = vimeo[2];
|
|
37
|
+
return hash
|
|
38
|
+
? `https://player.vimeo.com/video/${id}?h=${hash}`
|
|
39
|
+
: `https://player.vimeo.com/video/${id}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (raw.startsWith('https://') || raw.startsWith('http://')) return raw;
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract a `t=` / `start=` query parameter from a URL and convert it
|
|
48
|
+
* to integer seconds. Supports YouTube's mixed formats:
|
|
49
|
+
* - 120
|
|
50
|
+
* - 120s
|
|
51
|
+
* - 2m30s
|
|
52
|
+
* - 1h30m45s
|
|
53
|
+
* Returns 0 if no start-time is present or the format is unrecognized.
|
|
54
|
+
*/
|
|
55
|
+
export function extractStartSeconds(raw: string): number {
|
|
56
|
+
const m = raw.match(/[?&#](?:t|start)=([^&#]+)/);
|
|
57
|
+
if (!m) return 0;
|
|
58
|
+
const t = m[1];
|
|
59
|
+
if (/^\d+$/.test(t)) return parseInt(t, 10);
|
|
60
|
+
const parts = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/);
|
|
61
|
+
if (!parts) return 0;
|
|
62
|
+
const h = parseInt(parts[1] || '0', 10);
|
|
63
|
+
const mm = parseInt(parts[2] || '0', 10);
|
|
64
|
+
const s = parseInt(parts[3] || '0', 10);
|
|
65
|
+
return h * 3600 + mm * 60 + s;
|
|
66
|
+
}
|