@lumir-company/editor 0.4.6 → 0.4.8
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/README.md +140 -21
- package/dist/index.d.mts +32 -32
- package/dist/index.d.ts +32 -32
- package/dist/index.js +45 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -35
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +0 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
- [파일명 커스터마이징](#파일명-커스터마이징)
|
|
19
19
|
- [커스텀 업로더](#2-커스텀-업로더)
|
|
20
20
|
- [동영상 업로드 및 임베딩](#동영상-업로드-및-임베딩)
|
|
21
|
-
- [비디오 플레이어 동작 (커스텀 블록)](#비디오-플레이어-동작-커스텀-블록)
|
|
22
21
|
- [이미지·동영상 업로드 상세 가이드](#이미지동영상-업로드-상세-가이드)
|
|
23
22
|
- [이미지·비디오 삭제](#이미지비디오-삭제)
|
|
24
23
|
- [HTML 미리보기](#html-미리보기)
|
|
@@ -146,6 +145,106 @@ production/blog/images/my-photo.png
|
|
|
146
145
|
}
|
|
147
146
|
```
|
|
148
147
|
|
|
148
|
+
클라이언트는 `apiEndpoint?key={파일키}&contentType={MIME}` 형태로 GET 요청을 보내고, 서버는 위 형식으로 JSON을 반환하면 됩니다.
|
|
149
|
+
|
|
150
|
+
#### S3 Presigned URL API 구현 예시
|
|
151
|
+
|
|
152
|
+
**Next.js (App Router)**
|
|
153
|
+
|
|
154
|
+
파일: `app/api/s3/presigned/route.ts`
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
158
|
+
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
159
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
160
|
+
|
|
161
|
+
const s3 = new S3Client({
|
|
162
|
+
region: process.env.AWS_REGION!,
|
|
163
|
+
credentials: {
|
|
164
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
165
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export async function GET(req: NextRequest) {
|
|
170
|
+
const { searchParams } = new URL(req.url);
|
|
171
|
+
const key = searchParams.get("key");
|
|
172
|
+
const contentType = searchParams.get("contentType");
|
|
173
|
+
|
|
174
|
+
if (!key) {
|
|
175
|
+
return NextResponse.json({ error: "key is required" }, { status: 400 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const command = new PutObjectCommand({
|
|
179
|
+
Bucket: process.env.AWS_S3_BUCKET!,
|
|
180
|
+
Key: key,
|
|
181
|
+
ContentType: contentType || "application/octet-stream",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
|
|
185
|
+
const publicUrl = `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
|
|
186
|
+
|
|
187
|
+
return NextResponse.json({ presignedUrl, publicUrl, key });
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
필요한 환경 변수: `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_S3_BUCKET`
|
|
192
|
+
|
|
193
|
+
**Next.js가 아닌 프로젝트에서 사용하기**
|
|
194
|
+
|
|
195
|
+
동일하게 **GET** 요청으로 `key`, `contentType` 쿼리 파라미터를 받아 `presignedUrl`, `publicUrl`을 JSON으로 반환하는 엔드포인트를 구현하면 됩니다.
|
|
196
|
+
|
|
197
|
+
- **Express (Node.js)**
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
const express = require("express");
|
|
201
|
+
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
|
202
|
+
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
|
203
|
+
|
|
204
|
+
const s3 = new S3Client({
|
|
205
|
+
region: process.env.AWS_REGION,
|
|
206
|
+
credentials: {
|
|
207
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
208
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
app.get("/api/s3/presigned", async (req, res) => {
|
|
213
|
+
const key = req.query.key;
|
|
214
|
+
const contentType = req.query.contentType || "application/octet-stream";
|
|
215
|
+
|
|
216
|
+
if (!key) {
|
|
217
|
+
return res.status(400).json({ error: "key is required" });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const command = new PutObjectCommand({
|
|
221
|
+
Bucket: process.env.AWS_S3_BUCKET,
|
|
222
|
+
Key: key,
|
|
223
|
+
ContentType: contentType,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
|
|
227
|
+
const publicUrl = `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
|
|
228
|
+
|
|
229
|
+
res.json({ presignedUrl, publicUrl, key });
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
에디터 사용 시 `apiEndpoint`만 해당 서버 주소로 맞추면 됩니다.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<LumirEditor
|
|
237
|
+
s3Upload={{
|
|
238
|
+
apiEndpoint: "https://api.myapp.com/api/s3/presigned",
|
|
239
|
+
env: "production",
|
|
240
|
+
path: "uploads",
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- **Remix / SvelteKit / 기타 프레임워크**
|
|
246
|
+
GET 라우트에서 `key`, `contentType`을 받아 `@aws-sdk/client-s3`의 `PutObjectCommand`와 `@aws-sdk/s3-request-presigner`의 `getSignedUrl`로 presigned URL을 생성한 뒤, `{ presignedUrl, publicUrl, key }` 형태로 JSON 응답하면 동일하게 사용할 수 있습니다. CORS가 필요한 경우 해당 도메인을 허용해 두세요.
|
|
247
|
+
|
|
149
248
|
---
|
|
150
249
|
|
|
151
250
|
### 파일명 커스터마이징
|
|
@@ -360,24 +459,12 @@ const imageUrl = await s3Uploader(imageFile);
|
|
|
360
459
|
|
|
361
460
|
### 데이터 내부 동영상 임베딩
|
|
362
461
|
|
|
363
|
-
동영상 블록은 `initialContent` / `onContentChange`에 포함됩니다. 저장 시 `{ type: "video", props: { url: "..."
|
|
462
|
+
동영상 블록은 `initialContent` / `onContentChange`에 포함됩니다. 저장 시 `{ type: "video", props: { url: "..." } }` 형태로 블록이 유지됩니다.
|
|
364
463
|
|
|
365
|
-
- **재생**: 화면에서 동영상을 재생하려면 `allowVideoUpload={true}`로 두어야 합니다.
|
|
464
|
+
- **재생**: 화면에서 동영상을 재생하려면 `allowVideoUpload={true}`로 두어야 합니다. 이렇게 해야 video 확장이 활성화되어 BlockNote 기본 플레이어가 렌더링됩니다.
|
|
366
465
|
- `allowVideoUpload={false}`인 상태에서 initialContent에 video 블록만 넣으면 데이터는 보존되지만, 재생 UI는 비활성화된 확장 때문에 표시되지 않을 수 있습니다.
|
|
367
466
|
- **지원 URL**: 비디오 블록의 `url`은 **직접 재생 가능한 비디오 파일 URL**만 지원합니다(예: S3에 업로드된 `.mp4`, `.webm`, `.ogg`). YouTube·Vimeo 등 스트리밍 페이지 URL(`youtube.com/watch?v=...` 등)은 `<video>` 요소의 `src`로 재생되지 않으므로, 해당 링크를 video 블록 URL로 넣으면 재생되지 않습니다. YouTube 임베드가 필요하면 별도 embed 블록 또는 iframe 삽입 방식을 고려해야 합니다.
|
|
368
467
|
|
|
369
|
-
### 비디오 플레이어 동작 (커스텀 블록)
|
|
370
|
-
|
|
371
|
-
LumirEditor의 비디오 블록은 BlockNote 기본 플레이어를 대체한 **커스텀 비디오 블록**입니다.
|
|
372
|
-
|
|
373
|
-
| 항목 | 동작 |
|
|
374
|
-
|------|------|
|
|
375
|
-
| **더보기 / 다운로드** | 미노출 (UI에서 제거됨) |
|
|
376
|
-
| **우클릭 메뉴** | 비디오 영역에서 컨텍스트 메뉴 비활성화 |
|
|
377
|
-
| **사이즈 조절** | 편집 모드에서 좌·우·하단 리사이즈 핸들로 크기 조절 가능. `previewWidth`, `previewHeight`로 블록에 저장됨 (기본 640×360) |
|
|
378
|
-
|
|
379
|
-
블록 데이터에 `previewWidth`(px), `previewHeight`(px)가 없으면 기본값이 적용됩니다.
|
|
380
|
-
|
|
381
468
|
---
|
|
382
469
|
|
|
383
470
|
## 이미지·동영상 업로드 상세 가이드
|
|
@@ -460,7 +547,29 @@ LumirEditor의 비디오 블록은 BlockNote 기본 플레이어를 대체한 **
|
|
|
460
547
|
/>
|
|
461
548
|
```
|
|
462
549
|
|
|
463
|
-
동영상은 같은 `s3Upload`로
|
|
550
|
+
동영상은 같은 `s3Upload`로 업로드됩니다. 서버에서 이미지와 동영상을 다른 경로에 두고 싶다면 아래처럼 `fileNameTransform`으로 prefix를 분리하면 됩니다.
|
|
551
|
+
|
|
552
|
+
**이미지·동영상 업로드 경로 분리 (fileNameTransform)**
|
|
553
|
+
|
|
554
|
+
`fileNameTransform`의 두 번째 인자 `file`로 이미지/동영상을 구분해, 파일명 앞에 폴더 prefix를 붙이면 됩니다. 최종 S3 키는 `{env}/{path}/{filename}` 이므로, `filename`에 `images/...` / `videos/...` 를 넣으면 경로가 나뉩니다.
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
<LumirEditor
|
|
558
|
+
allowVideoUpload={true}
|
|
559
|
+
s3Upload={{
|
|
560
|
+
apiEndpoint: "/api/s3/presigned",
|
|
561
|
+
env: "production",
|
|
562
|
+
path: "uploads", // 공통 상위 경로
|
|
563
|
+
appendUUID: true,
|
|
564
|
+
fileNameTransform: (nameWithoutExt, file) => {
|
|
565
|
+
const isVideo = file.type.startsWith("video/");
|
|
566
|
+
return `${isVideo ? "videos" : "images"}/${nameWithoutExt}`;
|
|
567
|
+
},
|
|
568
|
+
}}
|
|
569
|
+
/>
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
결과 예: 이미지 → `production/uploads/images/photo_abc123.png`, 동영상 → `production/uploads/videos/clip_def456.mp4`
|
|
464
573
|
|
|
465
574
|
**커스텀 업로더로 이미지·동영상 통합**
|
|
466
575
|
|
|
@@ -504,17 +613,14 @@ LumirEditor의 비디오 블록은 BlockNote 기본 플레이어를 대체한 **
|
|
|
504
613
|
{
|
|
505
614
|
"type": "video",
|
|
506
615
|
"props": {
|
|
507
|
-
"url": "https://your-cdn.com/videos/clip_xxx.mp4"
|
|
508
|
-
"previewWidth": 640,
|
|
509
|
-
"previewHeight": 360
|
|
616
|
+
"url": "https://your-cdn.com/videos/clip_xxx.mp4"
|
|
510
617
|
},
|
|
511
618
|
"content": [],
|
|
512
619
|
"children": []
|
|
513
620
|
}
|
|
514
621
|
```
|
|
515
622
|
|
|
516
|
-
|
|
517
|
-
- `previewWidth`, `previewHeight`: 선택. 픽셀 단위. 없으면 기본 640×360이 적용되며, 에디터에서 리사이즈하면 저장됩니다.
|
|
623
|
+
`url`은 반드시 **브라우저에서 직접 재생 가능한 URL**이어야 합니다. 동영상은 YouTube/Vimeo 링크가 아니라, 업로드 후 받은 `.mp4` 등 직접 재생 URL만 지원합니다.
|
|
518
624
|
|
|
519
625
|
### 6. 삭제 시 콜백
|
|
520
626
|
|
|
@@ -1222,6 +1328,19 @@ const url = await uploader(imageFile);
|
|
|
1222
1328
|
|
|
1223
1329
|
## 변경 로그
|
|
1224
1330
|
|
|
1331
|
+
### v0.4.8
|
|
1332
|
+
|
|
1333
|
+
- Redme update (video & image upload)
|
|
1334
|
+
- 버전 배포
|
|
1335
|
+
|
|
1336
|
+
### v0.4.6
|
|
1337
|
+
|
|
1338
|
+
- **README: S3 Presigned URL API**
|
|
1339
|
+
- Next.js App Router용 `app/api/s3/presigned/route.ts` 구현 예시 추가 (PutObjectCommand, getSignedUrl)
|
|
1340
|
+
- Next.js가 아닌 프로젝트: Express 예시 및 Remix/SvelteKit 등 동일 패턴 안내
|
|
1341
|
+
- **README: 이미지·동영상 업로드 경로 분리**
|
|
1342
|
+
- `fileNameTransform`으로 이미지/동영상 prefix 분리 (`images/`, `videos/`) 예시 및 결과 경로 설명 추가
|
|
1343
|
+
|
|
1225
1344
|
### v0.4.5
|
|
1226
1345
|
|
|
1227
1346
|
- **README: 이미지·동영상 업로드**
|
package/dist/index.d.mts
CHANGED
|
@@ -260,38 +260,6 @@ declare const HtmlPreviewBlock: {
|
|
|
260
260
|
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
261
261
|
};
|
|
262
262
|
declare const schema: BlockNoteSchema<_blocknote_core.BlockSchemaFromSpecs<{
|
|
263
|
-
video: {
|
|
264
|
-
config: {
|
|
265
|
-
readonly type: "video";
|
|
266
|
-
readonly propSchema: {
|
|
267
|
-
readonly url: {
|
|
268
|
-
readonly default: "";
|
|
269
|
-
};
|
|
270
|
-
readonly previewWidth: {
|
|
271
|
-
readonly default: 640;
|
|
272
|
-
};
|
|
273
|
-
readonly previewHeight: {
|
|
274
|
-
readonly default: 360;
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
readonly content: "none";
|
|
278
|
-
};
|
|
279
|
-
implementation: _blocknote_core.TiptapBlockImplementation<{
|
|
280
|
-
readonly type: "video";
|
|
281
|
-
readonly propSchema: {
|
|
282
|
-
readonly url: {
|
|
283
|
-
readonly default: "";
|
|
284
|
-
};
|
|
285
|
-
readonly previewWidth: {
|
|
286
|
-
readonly default: 640;
|
|
287
|
-
};
|
|
288
|
-
readonly previewHeight: {
|
|
289
|
-
readonly default: 360;
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
|
-
readonly content: "none";
|
|
293
|
-
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
294
|
-
};
|
|
295
263
|
htmlPreview: {
|
|
296
264
|
config: {
|
|
297
265
|
readonly type: "htmlPreview";
|
|
@@ -380,6 +348,38 @@ declare const schema: BlockNoteSchema<_blocknote_core.BlockSchemaFromSpecs<{
|
|
|
380
348
|
readonly content: "none";
|
|
381
349
|
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
382
350
|
};
|
|
351
|
+
video: {
|
|
352
|
+
config: {
|
|
353
|
+
readonly type: "video";
|
|
354
|
+
readonly propSchema: {
|
|
355
|
+
readonly url: {
|
|
356
|
+
readonly default: "";
|
|
357
|
+
};
|
|
358
|
+
readonly previewWidth: {
|
|
359
|
+
readonly default: 640;
|
|
360
|
+
};
|
|
361
|
+
readonly previewHeight: {
|
|
362
|
+
readonly default: 360;
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
readonly content: "none";
|
|
366
|
+
};
|
|
367
|
+
implementation: _blocknote_core.TiptapBlockImplementation<{
|
|
368
|
+
readonly type: "video";
|
|
369
|
+
readonly propSchema: {
|
|
370
|
+
readonly url: {
|
|
371
|
+
readonly default: "";
|
|
372
|
+
};
|
|
373
|
+
readonly previewWidth: {
|
|
374
|
+
readonly default: 640;
|
|
375
|
+
};
|
|
376
|
+
readonly previewHeight: {
|
|
377
|
+
readonly default: 360;
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
readonly content: "none";
|
|
381
|
+
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
382
|
+
};
|
|
383
383
|
paragraph: {
|
|
384
384
|
config: {
|
|
385
385
|
type: "paragraph";
|
package/dist/index.d.ts
CHANGED
|
@@ -260,38 +260,6 @@ declare const HtmlPreviewBlock: {
|
|
|
260
260
|
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
261
261
|
};
|
|
262
262
|
declare const schema: BlockNoteSchema<_blocknote_core.BlockSchemaFromSpecs<{
|
|
263
|
-
video: {
|
|
264
|
-
config: {
|
|
265
|
-
readonly type: "video";
|
|
266
|
-
readonly propSchema: {
|
|
267
|
-
readonly url: {
|
|
268
|
-
readonly default: "";
|
|
269
|
-
};
|
|
270
|
-
readonly previewWidth: {
|
|
271
|
-
readonly default: 640;
|
|
272
|
-
};
|
|
273
|
-
readonly previewHeight: {
|
|
274
|
-
readonly default: 360;
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
readonly content: "none";
|
|
278
|
-
};
|
|
279
|
-
implementation: _blocknote_core.TiptapBlockImplementation<{
|
|
280
|
-
readonly type: "video";
|
|
281
|
-
readonly propSchema: {
|
|
282
|
-
readonly url: {
|
|
283
|
-
readonly default: "";
|
|
284
|
-
};
|
|
285
|
-
readonly previewWidth: {
|
|
286
|
-
readonly default: 640;
|
|
287
|
-
};
|
|
288
|
-
readonly previewHeight: {
|
|
289
|
-
readonly default: 360;
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
|
-
readonly content: "none";
|
|
293
|
-
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
294
|
-
};
|
|
295
263
|
htmlPreview: {
|
|
296
264
|
config: {
|
|
297
265
|
readonly type: "htmlPreview";
|
|
@@ -380,6 +348,38 @@ declare const schema: BlockNoteSchema<_blocknote_core.BlockSchemaFromSpecs<{
|
|
|
380
348
|
readonly content: "none";
|
|
381
349
|
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
382
350
|
};
|
|
351
|
+
video: {
|
|
352
|
+
config: {
|
|
353
|
+
readonly type: "video";
|
|
354
|
+
readonly propSchema: {
|
|
355
|
+
readonly url: {
|
|
356
|
+
readonly default: "";
|
|
357
|
+
};
|
|
358
|
+
readonly previewWidth: {
|
|
359
|
+
readonly default: 640;
|
|
360
|
+
};
|
|
361
|
+
readonly previewHeight: {
|
|
362
|
+
readonly default: 360;
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
readonly content: "none";
|
|
366
|
+
};
|
|
367
|
+
implementation: _blocknote_core.TiptapBlockImplementation<{
|
|
368
|
+
readonly type: "video";
|
|
369
|
+
readonly propSchema: {
|
|
370
|
+
readonly url: {
|
|
371
|
+
readonly default: "";
|
|
372
|
+
};
|
|
373
|
+
readonly previewWidth: {
|
|
374
|
+
readonly default: 640;
|
|
375
|
+
};
|
|
376
|
+
readonly previewHeight: {
|
|
377
|
+
readonly default: 360;
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
readonly content: "none";
|
|
381
|
+
}, any, _blocknote_core.InlineContentSchema, _blocknote_core.StyleSchema>;
|
|
382
|
+
};
|
|
383
383
|
paragraph: {
|
|
384
384
|
config: {
|
|
385
385
|
type: "paragraph";
|
package/dist/index.js
CHANGED
|
@@ -1018,39 +1018,48 @@ var VideoBlockCard = ({
|
|
|
1018
1018
|
window.removeEventListener("mouseup", onMouseUp);
|
|
1019
1019
|
};
|
|
1020
1020
|
}, [resizeParams, localWidth, localHeight, onWidthChange, onHeightChange]);
|
|
1021
|
-
const handleLeftDown = (0, import_react4.useCallback)(
|
|
1022
|
-
e
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1021
|
+
const handleLeftDown = (0, import_react4.useCallback)(
|
|
1022
|
+
(e) => {
|
|
1023
|
+
e.preventDefault();
|
|
1024
|
+
e.stopPropagation();
|
|
1025
|
+
setResizeParams({
|
|
1026
|
+
handleUsed: "left",
|
|
1027
|
+
initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,
|
|
1028
|
+
initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,
|
|
1029
|
+
initialClientX: e.clientX,
|
|
1030
|
+
initialClientY: e.clientY
|
|
1031
|
+
});
|
|
1032
|
+
},
|
|
1033
|
+
[localHeight]
|
|
1034
|
+
);
|
|
1035
|
+
const handleRightDown = (0, import_react4.useCallback)(
|
|
1036
|
+
(e) => {
|
|
1037
|
+
e.preventDefault();
|
|
1038
|
+
e.stopPropagation();
|
|
1039
|
+
setResizeParams({
|
|
1040
|
+
handleUsed: "right",
|
|
1041
|
+
initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,
|
|
1042
|
+
initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,
|
|
1043
|
+
initialClientX: e.clientX,
|
|
1044
|
+
initialClientY: e.clientY
|
|
1045
|
+
});
|
|
1046
|
+
},
|
|
1047
|
+
[localHeight]
|
|
1048
|
+
);
|
|
1049
|
+
const handleBottomDown = (0, import_react4.useCallback)(
|
|
1050
|
+
(e) => {
|
|
1051
|
+
e.preventDefault();
|
|
1052
|
+
e.stopPropagation();
|
|
1053
|
+
setResizeParams({
|
|
1054
|
+
handleUsed: "bottom",
|
|
1055
|
+
initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,
|
|
1056
|
+
initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,
|
|
1057
|
+
initialClientX: e.clientX,
|
|
1058
|
+
initialClientY: e.clientY
|
|
1059
|
+
});
|
|
1060
|
+
},
|
|
1061
|
+
[localHeight]
|
|
1062
|
+
);
|
|
1054
1063
|
const resizeCursor = resizeParams ? resizeParams.handleUsed === "bottom" ? "ns-resize" : "ew-resize" : "default";
|
|
1055
1064
|
const [hovered, setHovered] = (0, import_react4.useState)(false);
|
|
1056
1065
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -1107,6 +1116,7 @@ var VideoBlockCard = ({
|
|
|
1107
1116
|
{
|
|
1108
1117
|
src: url,
|
|
1109
1118
|
controls: true,
|
|
1119
|
+
controlsList: "nodownload",
|
|
1110
1120
|
playsInline: true,
|
|
1111
1121
|
style: {
|
|
1112
1122
|
width: "100%",
|
|
@@ -1561,9 +1571,9 @@ var HtmlPreviewBlock = (0, import_react5.createReactBlockSpec)(
|
|
|
1561
1571
|
var schema = import_core.BlockNoteSchema.create({
|
|
1562
1572
|
blockSpecs: {
|
|
1563
1573
|
...import_core.defaultBlockSpecs,
|
|
1564
|
-
video: VideoBlock,
|
|
1565
1574
|
htmlPreview: HtmlPreviewBlock,
|
|
1566
|
-
linkPreview: LinkPreviewBlock
|
|
1575
|
+
linkPreview: LinkPreviewBlock,
|
|
1576
|
+
video: VideoBlock
|
|
1567
1577
|
},
|
|
1568
1578
|
inlineContentSpecs: import_core.defaultInlineContentSpecs,
|
|
1569
1579
|
styleSpecs: import_core.defaultStyleSpecs
|