Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: anchor link doesn't scroll properly #53

Merged
merged 1 commit into from
Dec 12, 2024
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
8 changes: 8 additions & 0 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import process from 'node:process';
import { URL, fileURLToPath } from 'node:url';
import { defineConfig } from 'vitepress';
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
import { markdownFitMedia } from './plugins/markdown-fit-media';

const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));

Expand Down Expand Up @@ -122,6 +123,13 @@ export default defineConfig({
markdown: {
config(md) {
md.use(tabsMarkdownPlugin);
md.use(markdownFitMedia, {
imgDir: './public',
lazyLoad: true,
decoding: 'auto',
aspectRatio: true,
imgSizeHint: true,
});
},
},
vite: {
Expand Down
192 changes: 192 additions & 0 deletions .vitepress/plugins/markdown-fit-media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Modified from https://github.com/ulfschneider/markdown-it-fitmedia
import * as cheerio from 'cheerio';
import sizeOf from 'image-size';
import type MarkdownIt from 'markdown-it';

interface FitMediaOptions {
imgDir?: string;
imgLazyLoad?: boolean;
imgDecoding?: string;
imgSizeHint?: boolean;
lazyLoad?: boolean;
decoding?: string;
sizeHint?: boolean;
fitElements?: string[];
}

interface Dimensions {
width: number;
height: number;
}

interface Token {
content: string;
attrIndex: (key: string) => number;
attrs: [string, string][];
attrPush: (attr: [string, string]) => void;
}

function getDimensions(src: string, fitMediaOptions: FitMediaOptions): Dimensions {
if (fitMediaOptions.imgDir) {
return sizeOf(`${fitMediaOptions.imgDir}${src}`);
}
return sizeOf(src);
}

function styleAspectRatio(style: string | undefined, width: number, height: number): string {
if (style && !/aspect-ratio/i.test(style)) {
if (!/;\s*$/.test(style)) {
style += '; ';
}
style += `aspect-ratio:${width}/${height};`;
}
else {
style = `aspect-ratio:${width}/${height};`;
}
return style;
}

function fitHtmlElements(md: MarkdownIt, fitMediaOptions: FitMediaOptions): void {
const blockRenderer = md.renderer.rules.html_block;
const elementRenderer = (tokens: Token[], idx: number): string | undefined => {
try {
const token = tokens[idx];
const $ = cheerio.load(token.content);
const elements = $(fitMediaOptions.fitElements?.toString() || '');

if (elements.length > 0) {
elements.each(function () {
const width = parseInt($(this).attr('width') || '0');
const height = parseInt($(this).attr('height') || '0');
if (width > 0 && height > 0) {
let style = $(this).attr('style');
style = styleAspectRatio(style, width, height);
style += ' width:100%; max-width:100%; height:auto;';
$(this).attr('style', style);
}
});
return $('body').html() || undefined;
}
}
catch (error) {
console.error(`Failure when fitting media element ${error}`);
}
};

md.renderer.rules.html_block = (tokens: Token[], idx: number, options: any, env: any, self: any): string => {
const html = elementRenderer(tokens, idx);
return html || blockRenderer(tokens, idx, options, env, self);
};
}

function fitHtmlImgs(md: MarkdownIt, fitMediaOptions: FitMediaOptions): void {
const inlineRenderer = md.renderer.rules.html_inline;
const blockRenderer = md.renderer.rules.html_block;
const imgRenderer = (tokens: Token[], idx: number): string | undefined => {
try {
const token = tokens[idx];
const $ = cheerio.load(token.content);
const imgs = $('img');
if (imgs.length > 0) {
imgs.each(function () {
if (fitMediaOptions.imgLazyLoad) {
$(this).attr('loading', 'lazy');
}
if (fitMediaOptions.imgDecoding && fitMediaOptions.imgDecoding !== 'auto') {
$(this).attr('decoding', fitMediaOptions.imgDecoding);
}
const src = $(this).attr('src');
if (src) {
const dimensions = getDimensions(src, fitMediaOptions);
const height = dimensions.height;
const width = dimensions.width;
if (height > 0 && width > 0) {
let style = $(this).attr('style');
style = styleAspectRatio(style, width, height);
$(this).attr('style', style);
$(this).attr('width', width.toString());
$(this).attr('height', height.toString());
}
}
});
return $('body').html() || undefined;
}
}
catch (error) {
console.error(`Failure when adjusting img ${error}`);
}
};

md.renderer.rules.html_inline = (tokens: Token[], idx: number, options: any, env: any, self: any): string => {
const html = imgRenderer(tokens, idx);
return html || inlineRenderer(tokens, idx, options, env, self);
};

md.renderer.rules.html_block = (tokens: Token[], idx: number, options: any, env: any, self: any): string => {
const html = imgRenderer(tokens, idx);
return html || blockRenderer(tokens, idx, options, env, self);
};
}

function fitMarkdownImgs(md: MarkdownIt, fitMediaOptions: FitMediaOptions): void {
const attr = (token: Token, key: string, value?: string): string | null => {
const idx = token.attrIndex(key);
if (value === undefined) {
return idx >= 0 ? token.attrs[idx][1] : null;
}
else {
if (idx < 0) {
token.attrPush([key, value]);
}
else {
token.attrs[idx][1] = value;
}
return value;
}
};

const defaultRender = md.renderer.rules.image;
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: any, self: any): string => {
try {
const img = tokens[idx];

if (fitMediaOptions.imgLazyLoad) {
attr(img, 'loading', 'lazy');
}
if (fitMediaOptions.imgDecoding && fitMediaOptions.imgDecoding !== 'auto') {
attr(img, 'decoding', fitMediaOptions.imgDecoding);
}

const src = attr(img, 'src');
if (src) {
const dimensions = getDimensions(src, fitMediaOptions);
const height = dimensions.height;
const width = dimensions.width;
if (height > 0 && width > 0) {
let style = attr(img, 'style');
style = styleAspectRatio(style || '', width, height);
attr(img, 'style', style);
attr(img, 'width', width.toString());
attr(img, 'height', height.toString());
}
}
}
catch (error) {
console.error(`Failure when adjusting img ${error}`);
}

return defaultRender(tokens, idx, options, env, self);
};
}

function fitImgs(md: MarkdownIt, fitMediaOptions: FitMediaOptions): void {
fitHtmlImgs(md, fitMediaOptions);
fitMarkdownImgs(md, fitMediaOptions);
}

function markdownFitMedia(md: MarkdownIt, options: FitMediaOptions): void {
fitImgs(md, options);
fitHtmlElements(md, options);
}

export { markdownFitMedia };
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@
"@types/node": "20.17.9",
"@vue/compiler-sfc": "3.5.13",
"@vue/runtime-dom": "3.5.13",
"cheerio": "1.0.0",
"eslint": "9.16.0",
"husky": "9.1.7",
"image-size": "1.1.1",
"lint-staged": "15.2.10",
"markdown-it": "14.1.0",
"papaparse": "5.4.1",
"url": "0.11.4",
"vitepress": "1.5.0",
Expand Down
Loading
Loading