Skip to content

Commit 71451f6

Browse files
fix: video parsing and export for markdown (#1955)
1 parent a790d16 commit 71451f6

File tree

21 files changed

+262
-21
lines changed

21 files changed

+262
-21
lines changed

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"remark-rehype": "^11.1.2",
114114
"remark-stringify": "^11.0.0",
115115
"unified": "^11.0.5",
116+
"unist-util-visit": "^5.0.0",
116117
"uuid": "^8.3.2",
117118
"y-prosemirror": "^1.3.7",
118119
"y-protocols": "^1.0.6",

packages/core/src/api/exporters/markdown/markdownExporter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {
1313
StyleSchema,
1414
} from "../../../schema/index.js";
1515
import { createExternalHTMLExporter } from "../html/externalHTMLExporter.js";
16-
import { removeUnderlines } from "./removeUnderlinesRehypePlugin.js";
16+
import { removeUnderlines } from "./util/removeUnderlinesRehypePlugin.js";
1717
import { addSpacesToCheckboxes } from "./util/addSpacesToCheckboxesRehypePlugin.js";
18+
import { convertVideoToMarkdown } from "./util/convertVideoToMarkdownRehypePlugin.js";
1819

1920
// Needs to be sync because it's used in drag handler event (SideMenuPlugin)
2021
export function cleanHTMLToMarkdown(cleanHTMLString: string) {
2122
const markdownString = unified()
2223
.use(rehypeParse, { fragment: true })
24+
.use(convertVideoToMarkdown)
2325
.use(removeUnderlines)
2426
.use(addSpacesToCheckboxes)
2527
.use(rehypeRemark)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Parent as HASTParent } from "hast";
2+
import { visit } from "unist-util-visit";
3+
4+
// Originally, rehypeParse parses videos as links, which is incorrect.
5+
export function convertVideoToMarkdown() {
6+
return (tree: HASTParent) => {
7+
visit(tree, "element", (node, index, parent) => {
8+
if (parent && node.tagName === "video") {
9+
const src = node.properties?.src || node.properties?.["data-url"] || "";
10+
const name =
11+
node.properties?.title || node.properties?.["data-name"] || "";
12+
parent.children[index!] = {
13+
type: "text",
14+
value: `![${name}](${src})`,
15+
};
16+
}
17+
});
18+
};
19+
}
File renamed without changes.

packages/core/src/api/parsers/markdown/parseMarkdown.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
StyleSchema,
1515
} from "../../../schema/index.js";
1616
import { HTMLToBlocks } from "../html/parseHTML.js";
17+
import { isVideoUrl } from "../../../util/string.js";
1718

1819
// modified version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js
1920
// that outputs a data-language attribute instead of a CSS class (e.g.: language-typescript)
@@ -54,13 +55,43 @@ function code(state: any, node: any) {
5455
return result;
5556
}
5657

58+
function video(state: any, node: any) {
59+
const url = String(node?.url || "");
60+
const title = node?.title ? String(node.title) : undefined;
61+
62+
let result: any = {
63+
type: "element",
64+
tagName: "video",
65+
properties: {
66+
src: url,
67+
"data-name": title,
68+
"data-url": url,
69+
controls: true,
70+
},
71+
children: [],
72+
};
73+
state.patch?.(node, result);
74+
result = state.applyData ? state.applyData(node, result) : result;
75+
76+
return result;
77+
}
78+
5779
export function markdownToHTML(markdown: string): string {
5880
const htmlString = unified()
5981
.use(remarkParse)
6082
.use(remarkGfm)
6183
.use(remarkRehype, {
6284
handlers: {
6385
...(remarkRehypeDefaultHandlers as any),
86+
image: (state: any, node: any) => {
87+
const url = String(node?.url || "");
88+
89+
if (isVideoUrl(url)) {
90+
return video(state, node);
91+
} else {
92+
return remarkRehypeDefaultHandlers.image(state, node);
93+
}
94+
},
6495
code,
6596
},
6697
})

packages/core/src/util/string.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,24 @@ export function filenameFromURL(url: string): string {
1313
}
1414
return parts[parts.length - 1];
1515
}
16+
17+
export function isVideoUrl(url: string) {
18+
const videoExtensions = [
19+
"mp4",
20+
"webm",
21+
"ogg",
22+
"mov",
23+
"mkv",
24+
"flv",
25+
"avi",
26+
"wmv",
27+
"m4v",
28+
];
29+
try {
30+
const pathname = new URL(url).pathname;
31+
const ext = pathname.split(".").pop()?.toLowerCase() || "";
32+
return videoExtensions.includes(ext);
33+
} catch (_) {
34+
return false;
35+
}
36+
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<div class="bn-block-group" data-node-type="blockGroup">
2+
<div class="bn-block-outer" data-node-type="blockOuter" data-id="1">
3+
<div class="bn-block" data-node-type="blockContainer" data-id="1">
4+
<div
5+
class="bn-block-content"
6+
data-content-type="image"
7+
data-url="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
8+
data-file-block=""
9+
>
10+
<div class="bn-file-block-content-wrapper" style="position: relative;">
11+
<div class="bn-visual-media-wrapper">
12+
<img
13+
class="bn-visual-media"
14+
src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
15+
alt="BlockNote image"
16+
draggable="false"
17+
/>
18+
</div>
19+
</div>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<div class="bn-block-group" data-node-type="blockGroup">
2+
<div class="bn-block-outer" data-node-type="blockOuter" data-id="1">
3+
<div class="bn-block" data-node-type="blockContainer" data-id="1">
4+
<div
5+
class="bn-block-content"
6+
data-content-type="video"
7+
data-url="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
8+
data-file-block=""
9+
>
10+
<div class="bn-file-block-content-wrapper" style="position: relative;">
11+
<div class="bn-visual-media-wrapper">
12+
<video
13+
class="bn-visual-media"
14+
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
15+
controls=""
16+
draggable="false"
17+
width="0"
18+
></video>
19+
</div>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img
2+
src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
3+
alt="BlockNote image"
4+
data-url="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
5+
/>

0 commit comments

Comments
 (0)