├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── codeql.yml │ ├── docs.yml │ ├── lint.yml │ ├── stale.yml │ └── test.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── .gitignore ├── .vitepress │ ├── config.ts │ ├── escape-code.ts │ ├── md │ │ ├── index.ts │ │ ├── md-link.ts │ │ └── md-replacer.ts │ └── theme │ │ ├── index.ts │ │ └── style.css ├── guide │ ├── asset-import.md │ ├── getting-started.md │ ├── gpu-acceleration.md │ ├── index.md │ ├── integrations │ │ ├── astro.md │ │ ├── nuxt.md │ │ ├── remix.md │ │ ├── solid.md │ │ ├── svelte.md │ │ ├── vite.md │ │ └── vitepress.md │ └── plugins.md ├── index.md ├── package.json ├── public │ ├── favicon.svg │ ├── fonts │ │ ├── inter.woff2 │ │ └── jb.woff2 │ ├── img │ │ ├── hero.blend │ │ ├── hero.webp │ │ └── og.jpg │ ├── imgit │ │ ├── covers.json │ │ ├── encoded │ │ │ ├── i.gyazo.com-1bd7705bf63b4fa179e45a7b4445d34e.png@cover.avif │ │ │ ├── i.gyazo.com-1bd7705bf63b4fa179e45a7b4445d34e.png@main.avif │ │ │ ├── i.gyazo.com-2f5c124d0bd7c96a91a3bc19a4365850.mp4@cover.avif │ │ │ ├── i.gyazo.com-2f5c124d0bd7c96a91a3bc19a4365850.mp4@main.mp4 │ │ │ ├── i.gyazo.com-4122593cabf787177589cec34e94263c.png@cover.avif │ │ │ ├── i.gyazo.com-4122593cabf787177589cec34e94263c.png@main.avif │ │ │ ├── i.gyazo.com-57489480e03593abb47d78c5e1374aa7.png@cover.avif │ │ │ ├── i.gyazo.com-57489480e03593abb47d78c5e1374aa7.png@main.avif │ │ │ ├── i.gyazo.com-845ed7b1a635187b00f93a3e7f2730ae.mp4@cover.avif │ │ │ ├── i.gyazo.com-845ed7b1a635187b00f93a3e7f2730ae.mp4@main.mp4 │ │ │ ├── i.gyazo.com-b2f45680247820c398682d7150fca566.mp4@cover.avif │ │ │ ├── i.gyazo.com-b2f45680247820c398682d7150fca566.mp4@main.mp4 │ │ │ ├── i.gyazo.com-d1f5eb93e57799d1a3cd2abdf438c040.png@cover.avif │ │ │ ├── i.gyazo.com-d1f5eb93e57799d1a3cd2abdf438c040.png@main.avif │ │ │ ├── i.ytimg.com-vi_webp-arbuYnJoLtU-maxresdefault.webp@cover.avif │ │ │ ├── i.ytimg.com-vi_webp-arbuYnJoLtU-maxresdefault.webp@main.avif │ │ │ ├── svg-imgit-nutshell-dark.png@cover.avif │ │ │ ├── svg-imgit-nutshell-dark.png@dense.avif │ │ │ ├── svg-imgit-nutshell-dark.png@main.avif │ │ │ ├── svg-imgit-nutshell-light.png@cover.avif │ │ │ ├── svg-imgit-nutshell-light.png@dense.avif │ │ │ └── svg-imgit-nutshell-light.png@main.avif │ │ ├── fetched │ │ │ ├── i.gyazo.com-1bd7705bf63b4fa179e45a7b4445d34e.png │ │ │ ├── i.gyazo.com-2f5c124d0bd7c96a91a3bc19a4365850.mp4 │ │ │ ├── i.gyazo.com-4122593cabf787177589cec34e94263c.png │ │ │ ├── i.gyazo.com-57489480e03593abb47d78c5e1374aa7.png │ │ │ ├── i.gyazo.com-845ed7b1a635187b00f93a3e7f2730ae.mp4 │ │ │ ├── i.gyazo.com-b2f45680247820c398682d7150fca566.mp4 │ │ │ ├── i.gyazo.com-d1f5eb93e57799d1a3cd2abdf438c040.png │ │ │ └── i.ytimg.com-vi_webp-arbuYnJoLtU-maxresdefault.webp │ │ ├── probes.json │ │ ├── sizes.json │ │ ├── specs.json │ │ └── youtube.json │ └── svg │ │ ├── imgit-nutshell-dark.png │ │ ├── imgit-nutshell-light.png │ │ └── imgit-nutshell.excalidraw ├── scripts │ └── api.sh └── tsconfig.json ├── package.json ├── samples ├── .gitignore ├── assets │ ├── README.md │ ├── apng.apng │ ├── art-square.jpg │ ├── art-wide.jpg │ ├── avi.avi │ ├── avif.avif │ ├── bmp.bmp │ ├── gif.gif │ ├── jpg.jpg │ ├── mkv.mkv │ ├── mov.mov │ ├── mp4.mp4 │ ├── png.png │ ├── psd.psd │ ├── svg.svg │ ├── tga.tga │ ├── tif.tif │ ├── tiff.tiff │ ├── webm.webm │ └── webp.webp ├── astro │ ├── README.md │ ├── astro.config.mts │ ├── package.json │ ├── src │ │ ├── env.d.ts │ │ └── pages │ │ │ ├── import.astro │ │ │ └── index.astro │ └── tsconfig.json ├── minimal │ ├── README.md │ ├── build.ts │ └── index.html ├── nuxt │ ├── README.md │ ├── app.vue │ ├── nuxt.config.ts │ ├── package.json │ └── tsconfig.json ├── remix │ ├── README.md │ ├── app │ │ ├── app.css │ │ └── root.tsx │ ├── package.json │ ├── tsconfig.json │ └── vite.config.ts ├── solid │ ├── README.md │ ├── package.json │ ├── src │ │ ├── app.css │ │ ├── app.tsx │ │ ├── entry-client.tsx │ │ └── entry-server.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── svelte │ ├── README.md │ ├── package.json │ ├── src │ │ ├── app.html │ │ └── routes │ │ │ └── +page.svelte │ ├── tsconfig.json │ └── vite.config.ts ├── vite │ ├── README.md │ ├── index.html │ ├── package.json │ ├── tsconfig.json │ └── vite.config.ts └── vitepress │ ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── index.mts │ │ └── styles.css │ ├── README.md │ ├── index.md │ ├── package.json │ └── tsconfig.json ├── scripts └── build.sh ├── src ├── client.d.ts ├── client │ ├── index.ts │ ├── intersection.ts │ ├── mutation.ts │ └── styles.css ├── plugin │ ├── astro.ts │ ├── svg.ts │ ├── vite.ts │ └── youtube │ │ ├── client.ts │ │ ├── index.ts │ │ ├── server.ts │ │ └── styles.css ├── server │ ├── asset.ts │ ├── cache.ts │ ├── common.ts │ ├── config │ │ ├── defaults.ts │ │ ├── index.ts │ │ ├── options.ts │ │ └── plugin.ts │ ├── context.ts │ ├── ffmpeg │ │ ├── ffmpeg.ts │ │ ├── ffprobe.ts │ │ └── index.ts │ ├── import.ts │ ├── index.ts │ ├── platform │ │ ├── bun.ts │ │ ├── deno.ts │ │ ├── index.ts │ │ ├── node.ts │ │ └── platform.ts │ └── transform │ │ ├── 1-capture.ts │ │ ├── 2-resolve.ts │ │ ├── 3-fetch.ts │ │ ├── 4-probe.ts │ │ ├── 5-encode.ts │ │ ├── 6-build.ts │ │ ├── 7-rewrite.ts │ │ └── index.ts └── tsconfig.json └── test ├── client ├── client.spec.ts ├── common.ts ├── intersection.spec.ts ├── mutation.spec.ts └── youtube.spec.ts ├── server ├── 1-capture.spec.ts ├── 2-resolve.spec.ts ├── 3-fetch.spec.ts ├── 4-probe.spec.ts ├── 5-encode.spec.ts ├── 6-build.spec.ts ├── 7-rewrite.spec.ts ├── astro.spec.ts ├── common.ts ├── import.spec.ts ├── server.spec.ts ├── svg.spec.ts ├── vite.spec.ts └── youtube.spec.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: elringus 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: codeql 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | analyze: 11 | name: analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'javascript' ] 21 | steps: 22 | - name: checkout 23 | uses: actions/checkout@v4 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: 20 27 | - name: initialize codeql 28 | uses: github/codeql-action/init@v2 29 | with: 30 | languages: ${{ matrix.language }} 31 | - name: build 32 | run: | 33 | npm install 34 | bash scripts/build.sh 35 | - name: analyze 36 | uses: github/codeql-action/analyze@v2 37 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | workflow_dispatch: { } 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pages: write 14 | id-token: write 15 | environment: 16 | name: github-pages 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: fetch tags 20 | run: git fetch --prune --unshallow --tags 21 | - uses: actions/setup-node@v4 22 | - name: build 23 | run: | 24 | cd docs 25 | npm install 26 | npm run docs:api 27 | npm run docs:build 28 | - uses: actions/configure-pages@v5 29 | - uses: actions/upload-pages-artifact@v3 30 | with: 31 | path: docs/.vitepress/dist 32 | - uses: actions/deploy-pages@v4 33 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | editor: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: editorconfig 15 | run: | 16 | docker run --rm --volume=$PWD:/check mstruebing/editorconfig-checker ec --exclude "\.git|[\\/]public[\\/]|[\\/]assets[\\/]" 17 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: stale 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '45 3 * * *' 7 | 8 | jobs: 9 | scan: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v9 13 | id: stale 14 | with: 15 | stale-issue-label: stale 16 | stale-pr-label: stale 17 | stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. It will be automatically closed in 7 days.' 18 | stale-pr-message: 'This pull request is stale because it has been open 14 days with no activity. It will be automatically closed in 7 days.' 19 | days-before-stale: 14 20 | days-before-close: 7 21 | exempt-issue-labels: 'bug,enhancement' 22 | exempt-pr-labels: 'bug,enhancement' 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | cover: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 20 17 | - name: cover 18 | run: | 19 | npm install 20 | npm run cover 21 | - name: upload report 22 | uses: codecov/codecov-action@v3 23 | with: 24 | fail_ci_if_error: true 25 | env: 26 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .idea 3 | 4 | dist 5 | coverage 6 | node_modules 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Artyom Sovetnikov (Elringus) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | imgit logo 4 | 5 |

6 |
7 |

8 | npm 9 | codefactor 10 | codecov 11 | codeql 12 |

13 |
14 | 15 | # Convert media links to optimized HTML 16 | 17 | Images, video and YouTube: fetch, encode, scale, lazyload – for best UX and [Web Vitals](https://web.dev/vitals) — imgit is a JavaScript package and set of plugins for popular web frameworks to enhance user experience when interacting with media-heavy websites, such as blogs, landings, portfolios and documentation sites. 18 | 19 | ## Features 20 | 21 | ✨ Builds optimized HTML for media URLs, markdown or JSX tags 22 | 23 | ⚡ Encodes to AV1/AVIF utilizing GPU acceleration 24 | 25 | ♻️ Supports JPG, A/PNG, GIF, WEBM/P, AVI, MKV, TIFF, PSD and more 26 | 27 | 🌊 Generates tiny blurred covers cross-faded into HD source on lazy-load 28 | 29 | 📐 Scales the content while preserving HD original for high-DPI displays 30 | 31 | 🌐 Fetches sources from remote URLs, uploads optimized versions to CDN 32 | 33 | 🗺️ Plugs into Astro, Svelte, VitePress and more; runs on Node, Deno and Bun 34 | 35 | ## 🎬 Get Started 36 | 37 | http://imgit.dev/guide 38 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | api 3 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, DefaultTheme } from "vitepress"; 2 | import md from "./md"; 3 | import escapeCode from "./escape-code"; 4 | import imgit from "imgit/vite"; 5 | import svg from "imgit/svg"; 6 | import youtube from "imgit/youtube"; 7 | 8 | // https://vitepress.dev/reference/site-config 9 | export default defineConfig({ 10 | title: "imgit", 11 | titleTemplate: ":title • imgit", 12 | description: "JavaScript utility to optimize web media content.", 13 | appearance: "dark", 14 | cleanUrls: true, 15 | lastUpdated: true, 16 | markdown: md, 17 | vite: { plugins: [imgit({ width: 688, plugins: [svg(), youtube(), escapeCode] })] }, 18 | head: [ 19 | ["link", { rel: "icon", href: "/favicon.svg" }], 20 | ["link", { rel: "preload", href: "/fonts/inter.woff2", as: "font", type: "font/woff2", crossorigin: "" }], 21 | ["link", { rel: "preload", href: "/fonts/jb.woff2", as: "font", type: "font/woff2", crossorigin: "" }], 22 | ["meta", { name: "theme-color", content: "#ee3248" }], 23 | ["meta", { name: "og:image", content: "/img/og.jpg" }], 24 | ["meta", { name: "twitter:card", content: "summary_large_image" }] 25 | ], 26 | themeConfig: { 27 | logo: { src: "/favicon.svg" }, 28 | logoLink: "/", 29 | socialLinks: [{ icon: "github", link: "https://github.com/elringus/imgit" }], 30 | search: { provider: "local", options: { detailedView: true } }, 31 | lastUpdated: { text: "Updated", formatOptions: { dateStyle: "medium" } }, 32 | sidebarMenuLabel: "Menu", 33 | darkModeSwitchLabel: "Appearance", 34 | returnToTopLabel: "Return to top", 35 | outline: { label: "On this page", level: [2, 3] }, 36 | docFooter: { prev: "Previous page", next: "Next page" }, 37 | nav: [ 38 | { text: "Guide", link: "/guide/", activeMatch: "/guide/" }, 39 | { text: "Reference", link: "/api/", activeMatch: "/api/" }, 40 | { 41 | text: `v${(await import("./../../package.json")).version}`, items: [ 42 | { text: "Changes", link: "https://github.com/elringus/imgit/releases/latest" }, 43 | { text: "Contribute", link: "https://github.com/elringus/imgit/labels/help%20wanted" } 44 | ] 45 | } 46 | ], 47 | editLink: { 48 | pattern: "https://github.com/elringus/imgit/edit/main/docs/:path", 49 | text: "Edit this page on GitHub" 50 | }, 51 | sidebar: { 52 | "/guide/": [ 53 | { 54 | text: "Guide", 55 | items: [ 56 | { text: "Introduction", link: "/guide/" }, 57 | { text: "Getting Started", link: "/guide/getting-started" }, 58 | { text: "Asset Import", link: "/guide/asset-import" }, 59 | { text: "GPU Acceleration", link: "/guide/gpu-acceleration" }, 60 | { text: "Plugins", link: "/guide/plugins" } 61 | ] 62 | }, 63 | { 64 | text: "Integrations", 65 | items: [ 66 | { text: "Vite", link: "/guide/integrations/vite" }, 67 | { text: "Astro", link: "/guide/integrations/astro" }, 68 | { text: "Nuxt", link: "/guide/integrations/nuxt" }, 69 | { text: "Remix", link: "/guide/integrations/remix" }, 70 | { text: "SolidStart", link: "/guide/integrations/solid" }, 71 | { text: "SvelteKit", link: "/guide/integrations/svelte" }, 72 | { text: "VitePress", link: "/guide/integrations/vitepress" } 73 | ] 74 | } 75 | ], 76 | "/api/": await getApiSidebar() 77 | } 78 | }, 79 | sitemap: { hostname: "https://imgit.dev" } 80 | }); 81 | 82 | async function getApiSidebar(): Promise { 83 | const items = (await import("./../api/typedoc-sidebar.json")).default; 84 | const server = items.find(i => i.text === "server"); 85 | const client = items.find(i => i.text === "client"); 86 | const other = items.filter(i => i !== server && i !== client); 87 | return [{ text: "Reference", items: [server, client, ...other] }]; 88 | } 89 | -------------------------------------------------------------------------------- /docs/.vitepress/escape-code.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, CapturedAsset, stages } from "imgit/server"; 2 | 3 | type Range = { start: number; end: number; }; 4 | 5 | export default { capture } satisfies Plugin; 6 | 7 | // Remove captures inside Markdown code blocks (```code```). 8 | function capture(content: string, assets: CapturedAsset[]): boolean { 9 | stages.capture.assets(content, assets); 10 | if (assets.length === 0) return true; 11 | const ranges = findCodeRanges(content); 12 | if (ranges.length > 0) 13 | assets.splice(0, assets.length, ...assets.filter(a => !isInCodeBlock(a, ranges))); 14 | return true; 15 | } 16 | 17 | function findCodeRanges(content: string): Range[] { 18 | const ranges = new Array(); 19 | let tickCount = 0; 20 | let openIndex = -1; 21 | for (let i = 0; i < content.length; i++) 22 | if (content[i] === "`") handleTick(i); 23 | else tickCount = 0; 24 | return ranges; 25 | 26 | function handleTick(index: number): void { 27 | if (++tickCount < 3) return; 28 | if (openIndex === -1) openRange(index); 29 | else closeRange(index); 30 | } 31 | 32 | function openRange(index: number): void { 33 | openIndex = index; 34 | tickCount = 0; 35 | } 36 | 37 | function closeRange(index: number): void { 38 | ranges.push({ start: openIndex, end: index }); 39 | openIndex = -1; 40 | tickCount = 0; 41 | } 42 | } 43 | 44 | function isInCodeBlock(asset: CapturedAsset, ranges: Range[]): boolean { 45 | for (const range of ranges) 46 | if (asset.syntax.index >= range.start && 47 | (asset.syntax.index + asset.syntax.text.length) <= range.end) 48 | return true; 49 | return false; 50 | } 51 | -------------------------------------------------------------------------------- /docs/.vitepress/md/index.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownOptions, MarkdownRenderer } from "vitepress"; 2 | import { AppendIconToExternalLinks } from "./md-link"; 3 | 4 | export default { 5 | config: installPlugins, 6 | attrs: { disable: true } // https://github.com/vuejs/vitepress/issues/2440 7 | } satisfies MarkdownOptions; 8 | 9 | function installPlugins(md: MarkdownRenderer) { 10 | md.use(AppendIconToExternalLinks); 11 | } 12 | -------------------------------------------------------------------------------- /docs/.vitepress/md/md-link.ts: -------------------------------------------------------------------------------- 1 | import type { MarkdownRenderer } from "vitepress"; 2 | import type { RenderRule } from "markdown-it/lib/renderer"; 3 | 4 | export function AppendIconToExternalLinks(md: MarkdownRenderer) { 5 | const renderToken: RenderRule = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options); 6 | const defaultLinkOpenRenderer = md.renderer.rules.link_open || renderToken; 7 | const defaultLinkCloseRenderer = md.renderer.rules.link_close || renderToken; 8 | let externalLinkOpen = false; 9 | 10 | md.renderer.rules.link_open = (tokens, idx, options, env, self) => { 11 | const token = tokens[idx]; 12 | const href = token.attrGet("href"); 13 | 14 | if (href) { 15 | token.attrJoin("class", "vp-link"); 16 | if (/^((ht|f)tps?):\/\/?/.test(href)) 17 | externalLinkOpen = true; 18 | } 19 | 20 | return defaultLinkOpenRenderer(tokens, idx, options, env, self); 21 | }; 22 | 23 | md.renderer.rules.link_close = (tokens, idx, options, env, self) => { 24 | if (externalLinkOpen) { 25 | externalLinkOpen = false; 26 | return ` ↗${self.renderToken(tokens, idx, options)}`; 27 | } 28 | return defaultLinkCloseRenderer(tokens, idx, options, env, self); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /docs/.vitepress/md/md-replacer.ts: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/rlidwka/markdown-it-regexp. 2 | 3 | import { inherits } from "node:util"; 4 | import { MarkdownEnv, MarkdownRenderer } from "vitepress"; 5 | 6 | let instanceId = 0; 7 | 8 | export function Replacer(regexp: RegExp, replace: (match: string[], env: MarkdownEnv) => string) { 9 | let self: any = (md: any) => self.init(md); 10 | self.__proto__ = Replacer.prototype; 11 | self.regexp = new RegExp("^" + regexp.source, regexp.flags); 12 | self.replace = replace; 13 | self.id = `md-replacer-${instanceId}`; 14 | instanceId++; 15 | return self; 16 | } 17 | 18 | inherits(Replacer, Function); 19 | 20 | Replacer.prototype.init = function (md: MarkdownRenderer) { 21 | md.inline.ruler.push(this.id, this.parse.bind(this)); 22 | md.renderer.rules[this.id] = this.render.bind(this); 23 | }; 24 | 25 | Replacer.prototype.parse = function (state: any, silent: any) { 26 | let match = this.regexp.exec(state.src.slice(state.pos)); 27 | if (!match) return false; 28 | 29 | state.pos += match[0].length; 30 | if (silent) return true; 31 | 32 | let token = state.push(this.id, "", 0); 33 | token.meta = { match: match }; 34 | return true; 35 | }; 36 | 37 | Replacer.prototype.render = function (tokens: any, id: any, options: any, env: MarkdownEnv) { 38 | return this.replace(tokens[id].meta.match, env); 39 | }; 40 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme-without-fonts"; 2 | import "./style.css"; 3 | 4 | // Have to import client assets manually due to vitepress 5 | // bug: https://github.com/vuejs/vitepress/issues/3314 6 | import "imgit/styles"; 7 | import "imgit/styles/youtube"; 8 | import "imgit/client"; 9 | import "imgit/client/youtube"; 10 | 11 | // https://vitepress.dev/guide/extending-default-theme 12 | export default { extends: { Layout: DefaultTheme.Layout } }; 13 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/style.css: -------------------------------------------------------------------------------- 1 | /** https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */ 2 | 3 | @font-face { 4 | font-family: "Inter"; 5 | font-style: normal; 6 | font-weight: 100 900; 7 | font-display: swap; 8 | /* noinspection CssUnknownTarget */ 9 | src: url(/fonts/inter.woff2) format("woff2"); 10 | } 11 | 12 | @font-face { 13 | font-family: "JetBrains"; 14 | font-style: normal; 15 | font-weight: 100 900; 16 | font-display: swap; 17 | /* noinspection CssUnknownTarget */ 18 | src: url(/fonts/jb.woff2) format("woff2"); 19 | } 20 | 21 | :root { 22 | --vp-font-family-base: "Inter", sans-serif; 23 | --vp-font-family-mono: "JetBrains", monospace; 24 | font-optical-sizing: auto; 25 | 26 | /* Main highlight/link color (light mode). */ 27 | --vp-c-brand-1: #d22c40; 28 | /* Link hover color (light mode). */ 29 | --vp-c-brand-2: #be2033; 30 | } 31 | 32 | .dark { 33 | /* Main highlight/link color (dark mode). */ 34 | --vp-c-brand-1: #ee3248; 35 | /* Link hover color (dark mode). */ 36 | --vp-c-brand-2: #f35d44; 37 | } 38 | 39 | .dark .dark-only, 40 | .light-only { 41 | display: flex !important; 42 | } 43 | 44 | .dark .light-only, 45 | .dark-only { 46 | display: none !important; 47 | } 48 | 49 | .attr { 50 | display: block; 51 | text-align: right; 52 | font-size: 11px; 53 | opacity: 0.65; 54 | transition: opacity 0.25s; 55 | font-family: "JetBrains", monospace; 56 | user-select: none; 57 | } 58 | 59 | .attr:hover { 60 | opacity: 1; 61 | } 62 | 63 | pre { 64 | font-variant-ligatures: none; 65 | } 66 | 67 | .vp-doc a { 68 | text-underline-position: from-font; 69 | } 70 | 71 | .external-link-icon { 72 | margin-left: -1px; 73 | margin-right: 2px; 74 | } 75 | 76 | details summary { 77 | font-weight: 500; 78 | color: var(--vp-c-brand-1); 79 | text-decoration: underline; 80 | text-underline-offset: 2px; 81 | transition: color 0.25s, opacity 0.25s; 82 | text-underline-position: from-font; 83 | cursor: pointer; 84 | } 85 | 86 | details summary:hover { 87 | color: var(--vp-c-brand-2); 88 | } 89 | 90 | .vp-doc th, .vp-doc td, .vp-doc tr { 91 | border: inherit !important; 92 | } 93 | 94 | table { 95 | --border: 1px solid var(--vp-c-divider); 96 | width: fit-content; 97 | border-radius: 8px; 98 | border-spacing: 0; 99 | border-collapse: separate !important; 100 | border: var(--border) !important; 101 | overflow: hidden; 102 | } 103 | 104 | table th:not(:last-child), 105 | table td:not(:last-child) { 106 | border-right: var(--border) !important; 107 | } 108 | 109 | table > thead > tr:not(:last-child) > th, 110 | table > thead > tr:not(:last-child) > td, 111 | table > tbody > tr:not(:last-child) > th, 112 | table > tbody > tr:not(:last-child) > td, 113 | table > tfoot > tr:not(:last-child) > th, 114 | table > tfoot > tr:not(:last-child) > td, 115 | table > tr:not(:last-child) > td, 116 | table > tr:not(:last-child) > th, 117 | table > thead:not(:last-child), 118 | table > tbody:not(:last-child), 119 | table > tfoot:not(:last-child) { 120 | border-bottom: var(--border) !important; 121 | } 122 | 123 | /* Stretch YouTube embeds with small thumbnails. */ 124 | .imgit-youtube { 125 | width: 100% !important; 126 | } 127 | 128 | [data-imgit-container], 129 | [data-imgit-container] img, 130 | [data-imgit-container] video { 131 | border-radius: 8px; 132 | } 133 | 134 | /** 135 | * Navbar blur effect 136 | * -------------------------------------------------------------------------- */ 137 | 138 | /* remove default opaque bg from navbar */ 139 | .content-body { 140 | background: none !important; 141 | } 142 | 143 | /* remove opacity gradient under navbar */ 144 | .curtain, 145 | .aside-curtain { 146 | display: none !important; 147 | } 148 | 149 | /* a hack for logo title to keep sidebar bg color */ 150 | @media (min-width: 960px) { 151 | .VPNavBar.has-sidebar div.title { 152 | background: var(--vp-sidebar-bg-color) !important; 153 | } 154 | } 155 | 156 | /* a bit of tint to make navbar not as transparent */ 157 | .VPNavBar:not(.top) { 158 | background: rgba(255, 255, 255, 0.75) !important; 159 | } 160 | 161 | .dark .VPNavBar:not(.top) { 162 | background: rgba(30, 30, 32, 0.5) !important; 163 | } 164 | 165 | /* the blur effect */ 166 | .VPNavBar:not(.top)::after { 167 | content: ""; 168 | position: absolute; 169 | top: 0; 170 | left: 0; 171 | right: 0; 172 | bottom: 0; 173 | backdrop-filter: saturate(180%) blur(5px); 174 | z-index: -1; 175 | } 176 | 177 | .VPNavBar.top::after { 178 | opacity: 0; 179 | } 180 | 181 | .VPNavBar.top { 182 | background-color: transparent; 183 | } 184 | 185 | .VPNavBar { 186 | transition: border-bottom-color 0.25s, background-color 0.25s, opacity 0.25s; 187 | } 188 | -------------------------------------------------------------------------------- /docs/guide/asset-import.md: -------------------------------------------------------------------------------- 1 | # Asset Import 2 | 3 | By default, imgit is set up to detect and transform Markdown syntax in the source content. This works best for simple documentation and blog websites, but may not be flexible enough for more complex apps authored with frameworks like React. 4 | 5 | To better fit component-based apps, imgit allows importing media assets with `import` statement to manually author the desired HTML. 6 | 7 | Use `imgit:` namespace when importing a media asset to make imgit optimize it and return sources of the generated assets. For example, consider following [Astro](https://astro.build) page: 8 | 9 | ```astro 10 | --- 11 | import psd from "imgit:https://example.com/photo.psd"; 12 | import mkv from "imgit:/assets/video.mkv"; 13 | --- 14 | 15 | 18 | 19 |