Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import remarkNpm2Yarn from '@docusaurus/remark-plugin-npm2yarn';
import rehypeCodeblockMeta from './src/plugins/rehype-codeblock-meta.mjs';
import rehypeStaticToDynamic from './src/plugins/rehype-static-to-dynamic.mjs';
import rehypeVideoAspectRatio from './src/plugins/rehype-video-aspect-ratio.mjs';

export default {
future: {
Expand Down Expand Up @@ -149,6 +150,7 @@ export default {
rehypeCodeblockMeta,
{ match: { snack: true, lang: true, tabs: true } },
],
[rehypeVideoAspectRatio, { staticDir: 'static' }],
rehypeStaticToDynamic,
],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"devDependencies": {
"@babel/types": "^7.28.5",
"@ffprobe-installer/ffprobe": "^2.1.2",
"markdownlint": "^0.36.1",
"markdownlint-cli2": "^0.14.0",
"prettier": "^3.6.2",
Expand Down
170 changes: 170 additions & 0 deletions src/plugins/rehype-video-aspect-ratio.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import ffprobe from '@ffprobe-installer/ffprobe';
import { exec } from 'child_process';
import fs from 'fs';
import path from 'path';
import { visit } from 'unist-util-visit';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
* Rehype plugin to add aspect ratio preservation to video tags
*/
export default function rehypeVideoAspectRatio({ staticDir }) {
return async (tree, file) => {
const promises = [];

visit(tree, 'mdxJsxFlowElement', (node) => {
if (node.name === 'video') {
// Find video source - check src attribute or source children
let videoSrc = null;

// Look for src in attributes
if (node.attributes) {
const srcAttr = node.attributes.find(
(attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'src'
);

if (srcAttr) {
videoSrc = srcAttr.value;
}
}

// If no src attribute, look for source children
if (!videoSrc && node.children) {
const sourceNode = node.children.find(
(child) =>
child.type === 'mdxJsxFlowElement' && child.name === 'source'
);

if (sourceNode?.attributes) {
const srcAttr = sourceNode.attributes.find(
(attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'src'
);

if (srcAttr) {
videoSrc = srcAttr.value;
}
}
}

const isLocalFile =
videoSrc &&
!videoSrc.startsWith('http://') &&
!videoSrc.startsWith('https://') &&
!videoSrc.startsWith('//');

if (isLocalFile) {
const videoPath = path.join(
videoSrc.startsWith('/') ? file.cwd : file.dirname,
staticDir,
videoSrc
);

if (fs.existsSync(videoPath)) {
const promise = getVideoDimensions(videoPath).then((dimensions) => {
if (dimensions.width && dimensions.height) {
applyAspectRatio(node, dimensions.width, dimensions.height);
}
});

promises.push(promise);
} else {
throw new Error(`Video file does not exist (got ${videoPath})`);
}
}
}
});

await Promise.all(promises);
};
}

/**
* Apply aspect ratio styles to a video node
*/
function applyAspectRatio(node, width, height) {
const data = {
estree: {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ObjectExpression',
properties: [
{
type: 'Property',
key: { type: 'Identifier', name: 'aspectRatio' },
value: { type: 'Literal', value: width / height },
kind: 'init',
},
],
},
},
],
},
};

node.attributes = node.attributes || [];

let styleAttr = node.attributes?.find(
(attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'style'
);

if (styleAttr) {
const properties =
styleAttr.value?.data?.estree?.body?.[0]?.expression?.properties ?? [];

data.estree.body[0].expression.properties.push(...properties);
}

styleAttr = {
type: 'mdxJsxAttribute',
name: 'style',
value: {
type: 'mdxJsxAttributeValueExpression',
data,
},
};

const existingIndex = node.attributes.findIndex(
(attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'style'
);

if (existingIndex !== -1) {
node.attributes[existingIndex] = styleAttr;
} else {
node.attributes.push(styleAttr);
}
}

/**
* Get video dimensions using ffprobe
*/
async function getVideoDimensions(filePath) {
const { stdout } = await execAsync(
`${ffprobe.path} -v error -of flat=s=_ -select_streams v:0 -show_entries stream=height,width "${filePath}"`
);

const lines = stdout.trim().split('\n');
const dimensions = {};

for (const line of lines) {
if (line.includes('width')) {
const width = Number(line.split('=')[1]);

if (Number.isFinite(width) && width > 0) {
dimensions.width = width;
}
} else if (line.includes('height')) {
const height = Number(line.split('=')[1]);

if (Number.isFinite(height) && height > 0) {
dimensions.height = height;
}
}
}

return dimensions;
}
1 change: 0 additions & 1 deletion versioned_docs/version-7.x/use-prevent-remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,3 @@ Doing so has several benefits:

- This approach still works if the app is closed or crashes unexpectedly.
- It's less intrusive to the user as they can still navigate away from the screen to check something and return without losing the data.
```
90 changes: 90 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,95 @@ __metadata:
languageName: node
linkType: hard

"@ffprobe-installer/darwin-arm64@npm:5.0.1":
version: 5.0.1
resolution: "@ffprobe-installer/darwin-arm64@npm:5.0.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard

"@ffprobe-installer/darwin-x64@npm:5.1.0":
version: 5.1.0
resolution: "@ffprobe-installer/darwin-x64@npm:5.1.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard

"@ffprobe-installer/ffprobe@npm:^2.1.2":
version: 2.1.2
resolution: "@ffprobe-installer/ffprobe@npm:2.1.2"
dependencies:
"@ffprobe-installer/darwin-arm64": "npm:5.0.1"
"@ffprobe-installer/darwin-x64": "npm:5.1.0"
"@ffprobe-installer/linux-arm": "npm:5.2.0"
"@ffprobe-installer/linux-arm64": "npm:5.2.0"
"@ffprobe-installer/linux-ia32": "npm:5.2.0"
"@ffprobe-installer/linux-x64": "npm:5.2.0"
"@ffprobe-installer/win32-ia32": "npm:5.1.0"
"@ffprobe-installer/win32-x64": "npm:5.1.0"
dependenciesMeta:
"@ffprobe-installer/darwin-arm64":
optional: true
"@ffprobe-installer/darwin-x64":
optional: true
"@ffprobe-installer/linux-arm":
optional: true
"@ffprobe-installer/linux-arm64":
optional: true
"@ffprobe-installer/linux-ia32":
optional: true
"@ffprobe-installer/linux-x64":
optional: true
"@ffprobe-installer/win32-ia32":
optional: true
"@ffprobe-installer/win32-x64":
optional: true
checksum: d3a0e472c9a31bffe10facf6ee0ce4520b4df13d1cf3fdcf7e6ead367f895a348633481c6010c0d9f30a950fe89f791aade9755360047c8dd48d098dccd8414b
languageName: node
linkType: hard

"@ffprobe-installer/linux-arm64@npm:5.2.0":
version: 5.2.0
resolution: "@ffprobe-installer/linux-arm64@npm:5.2.0"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard

"@ffprobe-installer/linux-arm@npm:5.2.0":
version: 5.2.0
resolution: "@ffprobe-installer/linux-arm@npm:5.2.0"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard

"@ffprobe-installer/linux-ia32@npm:5.2.0":
version: 5.2.0
resolution: "@ffprobe-installer/linux-ia32@npm:5.2.0"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard

"@ffprobe-installer/linux-x64@npm:5.2.0":
version: 5.2.0
resolution: "@ffprobe-installer/linux-x64@npm:5.2.0"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard

"@ffprobe-installer/win32-ia32@npm:5.1.0":
version: 5.1.0
resolution: "@ffprobe-installer/win32-ia32@npm:5.1.0"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard

"@ffprobe-installer/win32-x64@npm:5.1.0":
version: 5.1.0
resolution: "@ffprobe-installer/win32-x64@npm:5.1.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard

"@hapi/hoek@npm:^9.0.0":
version: 9.3.0
resolution: "@hapi/hoek@npm:9.3.0"
Expand Down Expand Up @@ -10517,6 +10606,7 @@ __metadata:
"@docusaurus/plugin-google-analytics": "npm:3.6.1"
"@docusaurus/preset-classic": "npm:3.6.1"
"@docusaurus/remark-plugin-npm2yarn": "npm:3.6.1"
"@ffprobe-installer/ffprobe": "npm:^2.1.2"
"@octokit/graphql": "npm:^7.1.0"
"@react-navigation/core": "npm:^7.0.4"
escape-html: "npm:^1.0.3"
Expand Down