|
| 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