@teriusu/rich-editor 0.11.3 → 0.11.5
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/dist/extensions/MbusVideo.d.ts.map +1 -1
- package/dist/extensions/MbusVideo.js +39 -1
- package/dist/extensions/VideoEmbed.d.ts.map +1 -1
- package/dist/extensions/VideoEmbed.js +5 -22
- package/dist/utils/embed.d.ts +37 -0
- package/dist/utils/embed.d.ts.map +1 -0
- package/dist/utils/embed.js +45 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MbusVideo.d.ts","sourceRoot":"","sources":["../../src/extensions/MbusVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"MbusVideo.d.ts","sourceRoot":"","sources":["../../src/extensions/MbusVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAWrD,MAAM,WAAW,gBAAgB;IAChC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,SAAS,EAAE;YACV,YAAY,EAAE,CAAC,KAAK,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAC;SACrE,CAAC;KACF;CACD;AAED,eAAO,MAAM,SAAS,6BA0NpB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
2
|
import { attachResize } from "../utils/resize";
|
|
3
|
+
import { applyCrossOriginEmbedAttrs, canEmbedCrossOrigin } from "../utils/embed";
|
|
3
4
|
import { getEditorTranslator } from "../i18n";
|
|
4
5
|
import { mediaRatioCss, normalizeMediaAlign, normalizeMediaHeight, normalizeMediaRatio } from "../utils/media-size";
|
|
5
6
|
export const MbusVideo = Node.create({
|
|
@@ -99,16 +100,53 @@ export const MbusVideo = Node.create({
|
|
|
99
100
|
dom.style.marginRight = align === "center" ? "auto" : "";
|
|
100
101
|
};
|
|
101
102
|
applyLayout(node.attrs);
|
|
102
|
-
|
|
103
|
+
/*
|
|
104
|
+
* ⚠️ **`credentialless` 를 반드시 붙인다** — `VideoEmbed` 와 같은 이유·같은
|
|
105
|
+
* 레시피다(`utils/embed.ts`). 정올은 전 문서가 `COEP: require-corp` 라, 이
|
|
106
|
+
* 속성이 없으면 mbus iframe 이 **크롬에서도 통째로 막히고** 그 자리에 크롬의
|
|
107
|
+
* 오류 화면(`play.mbus.tv 에서 연결을 거부했습니다`)이 뜬다.
|
|
108
|
+
*
|
|
109
|
+
* 이 노드만 그 처리가 빠져 있었다. 저장한 뒤 읽는 화면은 호스트의 정적 렌더
|
|
110
|
+
* (`trinity …/tiptap/static-enhance.ts` 의 `buildVideoBox`)가 같은 상자를 다시
|
|
111
|
+
* 지으면서 속성을 붙이므로 **에디터에서만** 거부로 보였다 — "영상을 올리면
|
|
112
|
+
* 연결이 거부된다"는 신고의 실체가 이 비대칭이다.
|
|
113
|
+
* (브라우저 실측, 격리된 문서: 속성 없는 mbus iframe = COEP 위반 보고 1건,
|
|
114
|
+
* 속성 있는 쪽 = 0건.)
|
|
115
|
+
*/
|
|
116
|
+
if (node.attrs.src && canEmbedCrossOrigin()) {
|
|
103
117
|
const iframe = document.createElement("iframe");
|
|
104
118
|
iframe.src = node.attrs.src;
|
|
105
119
|
iframe.allow = "autoplay; fullscreen; encrypted-media; picture-in-picture";
|
|
106
120
|
iframe.setAttribute("allowfullscreen", "");
|
|
107
121
|
iframe.setAttribute("loading", "lazy");
|
|
122
|
+
applyCrossOriginEmbedAttrs(iframe);
|
|
108
123
|
iframe.style.cssText =
|
|
109
124
|
"position:absolute;inset:0;width:100%;height:100%;border:0;display:block;";
|
|
110
125
|
aspect.appendChild(iframe);
|
|
111
126
|
}
|
|
127
|
+
else if (node.attrs.src) {
|
|
128
|
+
/*
|
|
129
|
+
* 실을 수 없는 브라우저(파폭·사파리 + 격리) — **빈 박스로 두지 않는다.**
|
|
130
|
+
* 그러면 영상이 깨진 줄 알지 이유를 모른다. `VideoEmbed`·호스트 정적 렌더와
|
|
131
|
+
* **같은 문구·같은 모양**의 포스터를 세운다.
|
|
132
|
+
*/
|
|
133
|
+
const poster = document.createElement("div");
|
|
134
|
+
poster.style.cssText =
|
|
135
|
+
"position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:16px;text-align:center;color:#c7cbd4;";
|
|
136
|
+
const label = document.createElement("span");
|
|
137
|
+
label.textContent = t("videoCannotEmbed");
|
|
138
|
+
label.style.cssText = "font-size:13px;line-height:1.5;";
|
|
139
|
+
poster.appendChild(label);
|
|
140
|
+
const open = document.createElement("a");
|
|
141
|
+
open.href = node.attrs.src;
|
|
142
|
+
open.target = "_blank";
|
|
143
|
+
open.rel = "noopener noreferrer";
|
|
144
|
+
open.textContent = t("openInNewTab");
|
|
145
|
+
open.style.cssText =
|
|
146
|
+
"font-size:13px;font-weight:600;color:#fff;background:rgba(255,255,255,0.16);border-radius:6px;padding:6px 14px;text-decoration:none;";
|
|
147
|
+
poster.appendChild(open);
|
|
148
|
+
aspect.appendChild(poster);
|
|
149
|
+
}
|
|
112
150
|
if (editor.isEditable) {
|
|
113
151
|
const del = document.createElement("button");
|
|
114
152
|
del.type = "button";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoEmbed.d.ts","sourceRoot":"","sources":["../../src/extensions/VideoEmbed.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoEmbed.d.ts","sourceRoot":"","sources":["../../src/extensions/VideoEmbed.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAYrD,MAAM,WAAW,iBAAiB;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,UAAU,EAAE;YACX,aAAa,EAAE,CAAC,KAAK,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAC;SACtE,CAAC;KACF;CACD;AAyBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuC1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2B9C;AAOD,eAAO,MAAM,UAAU,8BA+RrB,CAAC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
10
10
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
11
11
|
import { attachResize } from "../utils/resize";
|
|
12
|
+
import { applyCrossOriginEmbedAttrs, canEmbedCrossOrigin } from "../utils/embed";
|
|
12
13
|
import { getEditorTranslator } from "../i18n";
|
|
13
14
|
import { mediaRatioCss, normalizeMediaAlign, normalizeMediaHeight, normalizeMediaRatio } from "../utils/media-size";
|
|
14
15
|
/** `1m30s` · `90` 처럼 적히는 유튜브 타임스탬프를 초로 바꾼다. */
|
|
@@ -126,24 +127,10 @@ export function toEmbedUrl(raw) {
|
|
|
126
127
|
}
|
|
127
128
|
return trimmed;
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* ⚠️ 정올은 전 브라우저에 `COEP: require-corp`(교차 출처 격리)를 건다. 격리된 문서에
|
|
133
|
-
* 자기 COEP 를 안 보내는 제3자 iframe(유튜브·Vimeo·mbus 전부 해당)을 넣는 방법은
|
|
134
|
-
* `credentialless` 속성뿐인데 **크로미움 전용**이다(파이어폭스 미구현·사파리 거부).
|
|
135
|
-
* 그래서 정올 파폭·사파리에서는 mbus 영상이 지금 **빈 회색 박스**로 남아 있다.
|
|
136
|
-
*
|
|
137
|
-
* 격리되지 않은 문서(코드패스의 비-크로미움 등)에서는 평범한 크로스오리진 iframe 이라
|
|
138
|
-
* 아무 문제가 없다 — 그래서 격리 여부와 지원 여부를 **함께** 본다.
|
|
130
|
+
/*
|
|
131
|
+
* 판정은 `utils/embed.ts` 로 옮겼다 — `MbusVideo` 가 같은 규칙을 써야 하는데 여기
|
|
132
|
+
* 모듈 안에만 있어서 그쪽이 통째로 빠뜨리고 있었다(그 노드 주석 참조).
|
|
139
133
|
*/
|
|
140
|
-
function canEmbedCrossOrigin() {
|
|
141
|
-
if (typeof window === "undefined")
|
|
142
|
-
return true;
|
|
143
|
-
if (!window.crossOriginIsolated)
|
|
144
|
-
return true;
|
|
145
|
-
return "credentialless" in HTMLIFrameElement.prototype;
|
|
146
|
-
}
|
|
147
134
|
export const VideoEmbed = Node.create({
|
|
148
135
|
name: "videoEmbed",
|
|
149
136
|
group: "block",
|
|
@@ -261,11 +248,7 @@ export const VideoEmbed = Node.create({
|
|
|
261
248
|
iframe.allow = "autoplay; fullscreen; encrypted-media; picture-in-picture";
|
|
262
249
|
iframe.setAttribute("allowfullscreen", "");
|
|
263
250
|
iframe.setAttribute("loading", "lazy");
|
|
264
|
-
|
|
265
|
-
* 격리된 문서에서 COEP 없는 영상을 싣는 유일한 통로. 크로미움이 아니면 이
|
|
266
|
-
* 속성 자체가 무시되므로 조건 없이 붙여도 해가 없다.
|
|
267
|
-
*/
|
|
268
|
-
iframe.setAttribute("credentialless", "");
|
|
251
|
+
applyCrossOriginEmbedAttrs(iframe);
|
|
269
252
|
iframe.style.cssText =
|
|
270
253
|
"position:absolute;inset:0;width:100%;height:100%;border:0;display:block;";
|
|
271
254
|
aspect.appendChild(iframe);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「이 문서에서 **COEP 를 안 보내는 남의 iframe** 을 실을 수 있는가」.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 정올은 전 문서에 `Cross-Origin-Embedder-Policy: require-corp`(교차 출처 격리)를
|
|
5
|
+
* 건다. 격리된 문서에 자기 COEP 를 안 보내는 제3자 iframe(유튜브·Vimeo·mbus 전부
|
|
6
|
+
* 해당)을 넣는 방법은 **`credentialless` 속성뿐**인데 크로미움 전용이다
|
|
7
|
+
* (파이어폭스 미구현·사파리 거부).
|
|
8
|
+
*
|
|
9
|
+
* 격리되지 않은 문서(코드패스의 비-크로미움 등)에서는 평범한 크로스오리진 iframe 이라
|
|
10
|
+
* 아무 문제가 없다 — 그래서 격리 여부와 지원 여부를 **함께** 본다.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ 이 판정과 `applyCrossOriginEmbedAttrs` 는 **항상 짝으로** 쓴다. 한쪽만 쓰면
|
|
13
|
+
* 격리된 문서에서 브라우저가 iframe 을 막고 크롬이 제 오류 화면
|
|
14
|
+
* (`…에서 연결을 거부했습니다`)을 그 자리에 그린다 — `MbusVideo` 가 실제로 그랬다.
|
|
15
|
+
*/
|
|
16
|
+
export declare function canEmbedCrossOrigin(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 제3자 임베드 iframe 이 **실제로 로드되게** 하는 속성 묶음. 둘은 막는 층이 다르므로
|
|
19
|
+
* 항상 같이 붙인다.
|
|
20
|
+
*
|
|
21
|
+
* 1. `credentialless` — 격리된 문서(COEP `require-corp`)에서 COEP 없는 남의 iframe 을
|
|
22
|
+
* 싣는 유일한 통로. 크로미움이 아니면 무시되므로 조건 없이 붙여도 해가 없다.
|
|
23
|
+
*
|
|
24
|
+
* 2. `referrerpolicy` — ⚠️ **호스트가 Referer 를 끄고 있을 수 있다.** 정올은
|
|
25
|
+
* `app.html` 에 `<meta name="referrer" content="same-origin">` 을 두어 교차 출처로는
|
|
26
|
+
* Referer 를 **아예 안 보낸다.** 그런데 mbus 채널 중에는 Referer 로 출처를 거르는
|
|
27
|
+
* 것이 있어서, 그런 영상은 CDN 청크가 403 으로 막히고 플레이어가
|
|
28
|
+
* `인코딩에 실패했거나 접근 제한되어 재생할 수 없습니다` 를 띄운다 — 영상 자체는
|
|
29
|
+
* 멀쩡한데(최상위 playlist 는 200) 안 나오는, 설명이 안 되는 실패로 보인다.
|
|
30
|
+
* 실측(`ch_17827535`): Referer 없음 → 403 · `https://jungol.co.kr/` → 200 ·
|
|
31
|
+
* 엉뚱한 출처 → 403. 요소에 건 정책이 문서 메타를 이기므로 여기서 되돌린다.
|
|
32
|
+
* ⚠️ 전체 URL 이 아니라 **출처만** 보낸다(`strict-origin-…`) — 어느 글에서 봤는지까지
|
|
33
|
+
* 남의 서버에 알릴 이유는 없다. 앱의 다른 mbus 자리(`MidibusInner`·`legacy-iframe`)가
|
|
34
|
+
* 쓰는 값과 같다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyCrossOriginEmbedAttrs(iframe: HTMLIFrameElement): void;
|
|
37
|
+
//# sourceMappingURL=embed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/utils/embed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAI7C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAG1E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「이 문서에서 **COEP 를 안 보내는 남의 iframe** 을 실을 수 있는가」.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 정올은 전 문서에 `Cross-Origin-Embedder-Policy: require-corp`(교차 출처 격리)를
|
|
5
|
+
* 건다. 격리된 문서에 자기 COEP 를 안 보내는 제3자 iframe(유튜브·Vimeo·mbus 전부
|
|
6
|
+
* 해당)을 넣는 방법은 **`credentialless` 속성뿐**인데 크로미움 전용이다
|
|
7
|
+
* (파이어폭스 미구현·사파리 거부).
|
|
8
|
+
*
|
|
9
|
+
* 격리되지 않은 문서(코드패스의 비-크로미움 등)에서는 평범한 크로스오리진 iframe 이라
|
|
10
|
+
* 아무 문제가 없다 — 그래서 격리 여부와 지원 여부를 **함께** 본다.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ 이 판정과 `applyCrossOriginEmbedAttrs` 는 **항상 짝으로** 쓴다. 한쪽만 쓰면
|
|
13
|
+
* 격리된 문서에서 브라우저가 iframe 을 막고 크롬이 제 오류 화면
|
|
14
|
+
* (`…에서 연결을 거부했습니다`)을 그 자리에 그린다 — `MbusVideo` 가 실제로 그랬다.
|
|
15
|
+
*/
|
|
16
|
+
export function canEmbedCrossOrigin() {
|
|
17
|
+
if (typeof window === "undefined")
|
|
18
|
+
return true;
|
|
19
|
+
if (!window.crossOriginIsolated)
|
|
20
|
+
return true;
|
|
21
|
+
return "credentialless" in HTMLIFrameElement.prototype;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 제3자 임베드 iframe 이 **실제로 로드되게** 하는 속성 묶음. 둘은 막는 층이 다르므로
|
|
25
|
+
* 항상 같이 붙인다.
|
|
26
|
+
*
|
|
27
|
+
* 1. `credentialless` — 격리된 문서(COEP `require-corp`)에서 COEP 없는 남의 iframe 을
|
|
28
|
+
* 싣는 유일한 통로. 크로미움이 아니면 무시되므로 조건 없이 붙여도 해가 없다.
|
|
29
|
+
*
|
|
30
|
+
* 2. `referrerpolicy` — ⚠️ **호스트가 Referer 를 끄고 있을 수 있다.** 정올은
|
|
31
|
+
* `app.html` 에 `<meta name="referrer" content="same-origin">` 을 두어 교차 출처로는
|
|
32
|
+
* Referer 를 **아예 안 보낸다.** 그런데 mbus 채널 중에는 Referer 로 출처를 거르는
|
|
33
|
+
* 것이 있어서, 그런 영상은 CDN 청크가 403 으로 막히고 플레이어가
|
|
34
|
+
* `인코딩에 실패했거나 접근 제한되어 재생할 수 없습니다` 를 띄운다 — 영상 자체는
|
|
35
|
+
* 멀쩡한데(최상위 playlist 는 200) 안 나오는, 설명이 안 되는 실패로 보인다.
|
|
36
|
+
* 실측(`ch_17827535`): Referer 없음 → 403 · `https://jungol.co.kr/` → 200 ·
|
|
37
|
+
* 엉뚱한 출처 → 403. 요소에 건 정책이 문서 메타를 이기므로 여기서 되돌린다.
|
|
38
|
+
* ⚠️ 전체 URL 이 아니라 **출처만** 보낸다(`strict-origin-…`) — 어느 글에서 봤는지까지
|
|
39
|
+
* 남의 서버에 알릴 이유는 없다. 앱의 다른 mbus 자리(`MidibusInner`·`legacy-iframe`)가
|
|
40
|
+
* 쓰는 값과 같다.
|
|
41
|
+
*/
|
|
42
|
+
export function applyCrossOriginEmbedAttrs(iframe) {
|
|
43
|
+
iframe.setAttribute("credentialless", "");
|
|
44
|
+
iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
45
|
+
}
|