Skip to content

Commit

Permalink
refactor: markdown copy linked files
Browse files Browse the repository at this point in the history
  • Loading branch information
zce committed Nov 30, 2023
1 parent dbb409b commit 5f7d661
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 85 deletions.
92 changes: 92 additions & 0 deletions src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { createHash } from 'node:crypto'
import { copyFile, readFile } from 'node:fs/promises'
import { basename, extname, join, resolve } from 'node:path'
import sharp from 'sharp'
import { visit } from 'unist-util-visit'

import { getConfig } from './config'

import type { Element, Root as Hast } from 'hast'
import type { Root as Mdast, Node } from 'mdast'
import type { Plugin } from 'unified'

/**
* Image object with metadata & blur image
*/
Expand Down Expand Up @@ -147,3 +152,90 @@ export const outputImage = async <T extends string | undefined>(ref: T, fromPath
if (ref == null) return ref
return output(ref, fromPath, true) as Promise<Image | T>
}

/**
* rehype plugin to copy linked files to public path and replace their urls with public urls
*/
export const rehypeCopyLinkedFiles: Plugin<[], Hast> = () => async (tree, file) => {
const links = new Map<string, Element[]>()
const linkedPropertyNames = ['href', 'src', 'poster']

visit(tree, 'element', node => {
linkedPropertyNames.forEach(name => {
const value = node.properties[name]
if (typeof value === 'string' && isValidatedStaticPath(value)) {
const elements = links.get(value) ?? []
elements.push(node)
links.set(value, elements)
}
})
})

await Promise.all(
Array.from(links.entries()).map(async ([url, elements]) => {
const publicUrl = await outputFile(url, file.path)
if (publicUrl == null || publicUrl === url) return
elements.forEach(node => {
linkedPropertyNames.forEach(name => {
if (name in node.properties) {
node.properties[name] = publicUrl
}
})
})
})
)
}

/**
* remark plugin to copy linked files to public path and replace their urls with public urls
*/
export const remarkCopyLinkedFiles: Plugin<[], Mdast> = () => async (tree, file) => {
const links = new Map<string, Node[]>()

visit(tree, ['link', 'image', 'definition'], (node: any) => {
if (isValidatedStaticPath(node.url)) {
const nodes = links.get(node.url) || []
nodes.push(node)
links.set(node.url, nodes)
}
})

visit(tree, 'mdxJsxFlowElement', node => {
node.attributes.forEach((attr: any) => {
if (['href', 'src', 'poster'].includes(attr.name) && typeof attr.value === 'string' && isValidatedStaticPath(attr.value)) {
const nodes = links.get(attr.value) || []
nodes.push(node)
links.set(attr.value, nodes)
}
})
})

await Promise.all(
Array.from(links.entries()).map(async ([url, nodes]) => {
const publicUrl = await outputFile(url, file.path)
if (publicUrl == null || publicUrl === url) return
nodes.forEach((node: any) => {
if ('url' in node && node.url === url) {
node.url = publicUrl
return
}
if ('href' in node && node.href === url) {
node.href = publicUrl
return
}

node.attributes.forEach((attr: any) => {
if (attr.name === 'src' && attr.value === url) {
attr.value = publicUrl
}
if (attr.name === 'href' && attr.value === url) {
attr.value = publicUrl
}
if (attr.name === 'poster' && attr.value === url) {
attr.value = publicUrl
}
})
})
})
)
}
34 changes: 2 additions & 32 deletions src/schemas/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { unified } from 'unified'
import { visit } from 'unist-util-visit'
import { z } from 'zod'

import { isValidatedStaticPath, outputFile } from '../assets'
import { rehypeCopyLinkedFiles } from '../assets'
import { getConfig } from '../config'

import type { Element, Root } from 'hast'
import type { Root } from 'hast'
import type { PluggableList, Plugin } from 'unified'

/**
Expand Down Expand Up @@ -52,36 +52,6 @@ const remarkRemoveComments: Plugin<[], Root> = () => tree => {
})
}

const rehypeCopyLinkedFiles: Plugin<[], Root> = () => async (tree, file) => {
const links = new Map<string, Element[]>()
const linkedPropertyNames = ['href', 'src', 'poster']

visit(tree, 'element', node => {
linkedPropertyNames.forEach(name => {
const value = node.properties[name]
if (typeof value === 'string' && isValidatedStaticPath(value)) {
const elements = links.get(value) ?? []
elements.push(node)
links.set(value, elements)
}
})
})

await Promise.all(
Array.from(links.entries()).map(async ([url, elements]) => {
const publicUrl = await outputFile(url, file.path)
if (publicUrl == null || publicUrl === url) return
elements.forEach(node => {
linkedPropertyNames.forEach(name => {
if (name in node.properties) {
node.properties[name] = publicUrl
}
})
})
})
)
}

export const markdown = (options: MarkdownOptions = {}) =>
z.string().transform(async (value, ctx) => {
const { markdown = {} } = getConfig()
Expand Down
55 changes: 2 additions & 53 deletions src/schemas/mdx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import remarkGfm from 'remark-gfm'
import { visit } from 'unist-util-visit'
import { z } from 'zod'

import { isValidatedStaticPath, outputFile } from '../assets'
import { remarkCopyLinkedFiles } from '../assets'
import { getConfig } from '../config'

import type { CompileOptions } from '@mdx-js/mdx'
import type { Node, Root } from 'mdast'
import type { Root } from 'mdast'
import type { Plugin } from 'unified'

/**
Expand Down Expand Up @@ -45,57 +45,6 @@ const remarkRemoveComments: Plugin<[], Root> = () => tree => {
})
}

const remarkCopyLinkedFiles: Plugin<[], Root> = () => async (tree, file) => {
const links = new Map<string, Node[]>()

visit(tree, ['link', 'image', 'definition'], (node: any) => {
if (isValidatedStaticPath(node.url)) {
const nodes = links.get(node.url) || []
nodes.push(node)
links.set(node.url, nodes)
}
})

visit(tree, 'mdxJsxFlowElement', node => {
node.attributes.forEach((attr: any) => {
if (['href', 'src', 'poster'].includes(attr.name) && typeof attr.value === 'string' && isValidatedStaticPath(attr.value)) {
const nodes = links.get(attr.value) || []
nodes.push(node)
links.set(attr.value, nodes)
}
})
})

await Promise.all(
Array.from(links.entries()).map(async ([url, nodes]) => {
const publicUrl = await outputFile(url, file.path)
if (publicUrl == null || publicUrl === url) return
nodes.forEach((node: any) => {
if ('url' in node && node.url === url) {
node.url = publicUrl
return
}
if ('href' in node && node.href === url) {
node.href = publicUrl
return
}

node.attributes.forEach((attr: any) => {
if (attr.name === 'src' && attr.value === url) {
attr.value = publicUrl
}
if (attr.name === 'href' && attr.value === url) {
attr.value = publicUrl
}
if (attr.name === 'poster' && attr.value === url) {
attr.value = publicUrl
}
})
})
})
)
}

export const mdx = (options: MdxOptions = {}) =>
z.string().transform(async (value, ctx) => {
const { mdx = {} } = getConfig()
Expand Down

0 comments on commit 5f7d661

Please sign in to comment.