|
| 1 | +import * as Path from "path"; |
1 | 2 | import { VideoBlockObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
| 3 | +import { ListBlockChildrenResponseResult } from "notion-to-md/build/types"; |
2 | 4 | import { IDocuNotionContext, IPlugin } from "./pluginTypes";
|
3 | 5 | import { warning } from "../log";
|
4 | 6 | import { NotionBlock } from "../types";
|
| 7 | +import { writeAsset } from "../assets"; |
5 | 8 |
|
6 | 9 | export const standardVideoTransformer: IPlugin = {
|
7 | 10 | name: "video",
|
8 | 11 | notionToMarkdownTransforms: [
|
9 | 12 | {
|
10 | 13 | type: "video",
|
11 |
| - getStringFromBlock: ( |
12 |
| - context: IDocuNotionContext, |
13 |
| - block: NotionBlock |
14 |
| - ): string => { |
15 |
| - const video = (block as VideoBlockObjectResponse).video; |
16 |
| - let url = ""; |
17 |
| - switch (video.type) { |
18 |
| - case "external": |
19 |
| - url = video.external.url; |
20 |
| - break; |
21 |
| - case "file": |
22 |
| - url = video.file.url; |
23 |
| - break; |
24 |
| - default: |
25 |
| - // video.type can only be "external" or "file" as of the writing of this code, so typescript |
26 |
| - // isn't happy trying to turn video.type into a string. But this default in our switch is |
27 |
| - // just attempting some future-proofing. Thus the strange typing/stringifying below. |
28 |
| - warning( |
29 |
| - `[standardVideoTransformer] Found Notion "video" block with type ${JSON.stringify( |
30 |
| - (video as any).type |
31 |
| - )}. The best docu-notion can do for now is ignore it.` |
32 |
| - ); |
33 |
| - return ""; |
34 |
| - break; |
35 |
| - } |
36 |
| - |
37 |
| - context.imports.push(`import ReactPlayer from "react-player";`); |
38 |
| - return `<ReactPlayer controls url="${url}" />`; |
39 |
| - }, |
| 14 | + getStringFromBlock: (context: IDocuNotionContext, block: NotionBlock) => |
| 15 | + markdownToMDVideoTransformer(block, context), |
40 | 16 | },
|
41 | 17 | ],
|
42 | 18 | };
|
| 19 | + |
| 20 | +async function markdownToMDVideoTransformer( |
| 21 | + block: ListBlockChildrenResponseResult, |
| 22 | + context: IDocuNotionContext |
| 23 | +): Promise<string> { |
| 24 | + const videoBlock = block as VideoBlockObjectResponse; |
| 25 | + const video = videoBlock.video; |
| 26 | + let url = ""; |
| 27 | + switch (video.type) { |
| 28 | + case "external": |
| 29 | + url = `"${video.external.url}"`; |
| 30 | + break; |
| 31 | + case "file": |
| 32 | + // The url we get for a Notion-hosted asset expires after an hour, so we have to download it locally. |
| 33 | + url = await downloadVideoAndConvertUrl( |
| 34 | + context, |
| 35 | + video.file.url, |
| 36 | + videoBlock.id |
| 37 | + ); |
| 38 | + break; |
| 39 | + default: |
| 40 | + // video.type can only be "external" or "file" as of the writing of this code, so typescript |
| 41 | + // isn't happy trying to turn video.type into a string. But this default in our switch is |
| 42 | + // just attempting some future-proofing. Thus the strange typing/stringifying below. |
| 43 | + warning( |
| 44 | + `[standardVideoTransformer] Found Notion "video" block with type ${JSON.stringify( |
| 45 | + (video as any).type |
| 46 | + )}. The best docu-notion can do for now is ignore it.` |
| 47 | + ); |
| 48 | + return ""; |
| 49 | + } |
| 50 | + |
| 51 | + context.imports.push(`import ReactPlayer from "react-player";`); |
| 52 | + return `<ReactPlayer controls url=${url} />`; |
| 53 | +} |
| 54 | + |
| 55 | +// ENHANCE: One day, we may want to allow for options of where to place the files, how |
| 56 | +// to name them, etc. Or we could at least follow the image options. |
| 57 | +// But for now, I'm just trying to fix the bug that Notion-hosted videos don't work at all. |
| 58 | +async function downloadVideoAndConvertUrl( |
| 59 | + context: IDocuNotionContext, |
| 60 | + notionVideoUrl: string, |
| 61 | + blockId: string |
| 62 | +): Promise<string> { |
| 63 | + // Get the file name from the url. Ignore query parameters and fragments. |
| 64 | + let newFileName = notionVideoUrl.split("?")[0].split("#")[0].split("/").pop(); |
| 65 | + |
| 66 | + if (!newFileName) { |
| 67 | + // If something went wrong, fall back to the block ID. |
| 68 | + // But at least try to get the extension from the url. |
| 69 | + const extension = notionVideoUrl |
| 70 | + .split("?")[0] |
| 71 | + .split("#")[0] |
| 72 | + .split(".") |
| 73 | + .pop(); |
| 74 | + newFileName = blockId + (extension ? "." + extension : ""); |
| 75 | + } |
| 76 | + |
| 77 | + const newPath = Path.posix.join( |
| 78 | + context.pageInfo.directoryContainingMarkdown, |
| 79 | + newFileName |
| 80 | + ); |
| 81 | + |
| 82 | + const response = await fetch(notionVideoUrl); |
| 83 | + const arrayBuffer = await response.arrayBuffer(); |
| 84 | + const buffer = Buffer.from(arrayBuffer); |
| 85 | + writeAsset(newPath, buffer); |
| 86 | + |
| 87 | + // Add an import statement for the video file. |
| 88 | + // Otherwise, the docusaurus build won't include the video file in the build. |
| 89 | + const countVideoImports = context.imports.filter(i => { |
| 90 | + return /import video\d+/.exec(i); |
| 91 | + }).length; |
| 92 | + const importName = `video${countVideoImports + 1}`; |
| 93 | + context.imports.push(`import ${importName} from "./${newFileName}";`); |
| 94 | + |
| 95 | + return `{${importName}}`; |
| 96 | +} |
0 commit comments