feat: add photoswipe for image zoom (#135)
This commit is contained in:
parent
336290a92f
commit
51025f0ea7
|
@ -1,22 +1,22 @@
|
|||
import sitemap from '@astrojs/sitemap';
|
||||
import svelte from "@astrojs/svelte"
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import swup from '@swup/astro';
|
||||
import Compress from "astro-compress"
|
||||
import icon from "astro-icon"
|
||||
import { defineConfig } from "astro/config"
|
||||
import Color from "colorjs.io"
|
||||
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||
import rehypeComponents from "rehype-components"; /* Render the custom directive content */
|
||||
import rehypeKatex from "rehype-katex"
|
||||
import rehypeSlug from "rehype-slug"
|
||||
import remarkMath from "remark-math"
|
||||
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"
|
||||
import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs"
|
||||
import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs"
|
||||
import remarkDirective from "remark-directive" /* Handle directives */
|
||||
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
|
||||
import rehypeComponents from "rehype-components"; /* Render the custom directive content */
|
||||
import svelte from "@astrojs/svelte"
|
||||
import swup from '@swup/astro';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import remarkMath from "remark-math"
|
||||
import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs"
|
||||
import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs"
|
||||
import {parseDirectiveNode} from "./src/plugins/remark-directive-rehype.js";
|
||||
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"
|
||||
|
||||
const oklchToHex = (str) => {
|
||||
const DEFAULT_HUE = 250
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/sanitize-html": "^2.11.0",
|
||||
"photoswipe": "^5.4.4",
|
||||
"remark-github-admonitions-to-directives": "^1.0.5",
|
||||
"sass": "^1.77.8",
|
||||
"stylus": "^0.63.0"
|
||||
|
@ -67,5 +68,6 @@
|
|||
"vite-imagetools": "^6.2.7",
|
||||
"sharp": "^0.33.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
|
||||
}
|
||||
|
|
|
@ -142,6 +142,9 @@ importers:
|
|||
'@types/sanitize-html':
|
||||
specifier: ^2.11.0
|
||||
version: 2.11.0
|
||||
photoswipe:
|
||||
specifier: ^5.4.4
|
||||
version: 5.4.4
|
||||
remark-github-admonitions-to-directives:
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5
|
||||
|
@ -3514,6 +3517,10 @@ packages:
|
|||
periscopic@3.1.0:
|
||||
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
|
||||
|
||||
photoswipe@5.4.4:
|
||||
resolution: {integrity: sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==}
|
||||
engines: {node: '>= 0.12.0'}
|
||||
|
||||
picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
|
@ -8761,6 +8768,8 @@ snapshots:
|
|||
estree-walker: 3.0.3
|
||||
is-reference: 3.0.2
|
||||
|
||||
photoswipe@5.4.4: {}
|
||||
|
||||
picocolors@1.0.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import type { AstroIntegration } from '@swup/astro'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// type from '@swup/astro' is incorrect
|
||||
swup: AstroIntegration
|
||||
}
|
||||
}
|
|
@ -1,53 +1,58 @@
|
|||
---
|
||||
import GlobalStyles from "@components/GlobalStyles.astro";
|
||||
import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
import ImageWrapper from "@components/misc/ImageWrapper.astro";
|
||||
import GlobalStyles from '@components/GlobalStyles.astro'
|
||||
import '@fontsource/roboto/400.css'
|
||||
import '@fontsource/roboto/500.css'
|
||||
import '@fontsource/roboto/700.css'
|
||||
import ImageWrapper from '@components/misc/ImageWrapper.astro'
|
||||
|
||||
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||
import {profileConfig, siteConfig} from "@/config";
|
||||
import {type Favicon} from "../types/config";
|
||||
import {defaultFavicons} from "../constants/icon";
|
||||
import {LIGHT_MODE, DARK_MODE, AUTO_MODE, DEFAULT_THEME} from "../constants/constants";
|
||||
import {pathsEqual, url} from "../utils/url-utils";
|
||||
import { profileConfig, siteConfig } from '@/config'
|
||||
import ConfigCarrier from '@components/ConfigCarrier.astro'
|
||||
import {
|
||||
AUTO_MODE,
|
||||
DARK_MODE,
|
||||
DEFAULT_THEME,
|
||||
LIGHT_MODE,
|
||||
} from '../constants/constants'
|
||||
import { defaultFavicons } from '../constants/icon'
|
||||
import type { Favicon } from '../types/config'
|
||||
import { url, pathsEqual } from '../utils/url-utils'
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
banner?: string;
|
||||
description?: string;
|
||||
title?: string
|
||||
banner?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
let { title, banner, description } = Astro.props;
|
||||
let { title, banner, description } = Astro.props
|
||||
|
||||
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
||||
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
||||
// so use Swup hooks instead to change the height immediately when a link is clicked
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, url('/'));
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, url('/'))
|
||||
|
||||
// defines global css variables
|
||||
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
||||
const configHue = siteConfig.themeColor.hue;
|
||||
const configHue = siteConfig.themeColor.hue
|
||||
if (!banner || typeof banner !== 'string' || banner.trim() === '') {
|
||||
banner = siteConfig.banner.src;
|
||||
banner = siteConfig.banner.src
|
||||
}
|
||||
|
||||
// TODO don't use post cover as banner for now
|
||||
banner = siteConfig.banner.src;
|
||||
banner = siteConfig.banner.src
|
||||
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
const enableBanner = siteConfig.banner.enable
|
||||
|
||||
let pageTitle;
|
||||
let pageTitle: string
|
||||
if (title) {
|
||||
pageTitle = `${title} - ${siteConfig.title}`;
|
||||
pageTitle = `${title} - ${siteConfig.title}`
|
||||
} else {
|
||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
|
||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`
|
||||
}
|
||||
|
||||
const favicons: Favicon[] = siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons
|
||||
const favicons: Favicon[] =
|
||||
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons
|
||||
|
||||
const siteLang = siteConfig.lang.replace('_', '-')
|
||||
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
---
|
||||
import Layout from "./Layout.astro";
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
import SideBar from "@components/widget/SideBar.astro";
|
||||
import Footer from "@components/Footer.astro";
|
||||
import BackToTop from "@components/control/BackToTop.astro";
|
||||
import Footer from '@components/Footer.astro'
|
||||
import Navbar from '@components/Navbar.astro'
|
||||
import BackToTop from '@components/control/BackToTop.astro'
|
||||
import SideBar from '@components/widget/SideBar.astro'
|
||||
import Layout from './Layout.astro'
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { siteConfig } from "../config";
|
||||
import { siteConfig } from '../config';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
banner?: string;
|
||||
description?: string;
|
||||
title?: string
|
||||
banner?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
const { title, banner, description } = Astro.props
|
||||
|
||||
const hasBannerCredit = siteConfig.banner.enable && siteConfig.banner.credit.enable
|
||||
const hasBannerLink = !!(siteConfig.banner.credit.url)
|
||||
|
||||
---
|
||||
|
||||
<Layout title={title} banner={banner} description={description}>
|
||||
|
@ -46,7 +44,8 @@ const hasBannerLink = !!(siteConfig.banner.credit.url)
|
|||
|
||||
<div id="content-wrapper" class="row-start-2 row-end-3 col-span-2 lg:col-span-1 overflow-hidden onload-animation">
|
||||
<!-- the overflow-hidden here prevent long text break the layout-->
|
||||
<main id="swup" class="transition-swup-fade">
|
||||
<!-- make id different from windows.swup global property -->
|
||||
<main id="swup-container" class="transition-swup-fade">
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
---
|
||||
import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||
import Pagination from "../components/control/Pagination.astro";
|
||||
import {getSortedPosts} from "../utils/content-utils";
|
||||
import {PAGE_SIZE} from "../constants/constants";
|
||||
import PostPage from "../components/PostPage.astro";
|
||||
import {type GetStaticPaths} from "astro";
|
||||
import type { GetStaticPaths } from 'astro'
|
||||
import PostPage from '../components/PostPage.astro'
|
||||
import Pagination from '../components/control/Pagination.astro'
|
||||
import { PAGE_SIZE } from '../constants/constants'
|
||||
import MainGridLayout from '../layouts/MainGridLayout.astro'
|
||||
import { getSortedPosts } from '../utils/content-utils'
|
||||
|
||||
export const getStaticPaths = (async ({ paginate }) => {
|
||||
const allBlogPosts = await getSortedPosts();
|
||||
return paginate(allBlogPosts, { pageSize: PAGE_SIZE });
|
||||
const allBlogPosts = await getSortedPosts()
|
||||
return paginate(allBlogPosts, { pageSize: PAGE_SIZE })
|
||||
}) satisfies GetStaticPaths
|
||||
// https://github.com/withastro/astro/issues/6507#issuecomment-1489916992
|
||||
|
||||
const {page} = Astro.props;
|
||||
|
||||
const len = page.data.length;
|
||||
const { page } = Astro.props
|
||||
|
||||
const len = page.data.length
|
||||
---
|
||||
|
||||
<MainGridLayout>
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import PostMetadata from "../../components/PostMeta.astro";
|
||||
import {i18n} from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import {getDir, getPostUrlBySlug} from "@utils/url-utils";
|
||||
import License from "@components/misc/License.astro";
|
||||
import {licenseConfig} from "src/config";
|
||||
import Markdown from "@components/misc/Markdown.astro";
|
||||
import path from "path";
|
||||
import {profileConfig} from "../../config";
|
||||
import {formatDateToYYYYMMDD} from "../../utils/date-utils";
|
||||
import path from 'node:path'
|
||||
import { getCollection } from 'astro:content'
|
||||
import License from '@components/misc/License.astro'
|
||||
import Markdown from '@components/misc/Markdown.astro'
|
||||
import I18nKey from '@i18n/i18nKey'
|
||||
import { i18n } from '@i18n/translation'
|
||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
||||
import { getDir, getPostUrlBySlug } from '@utils/url-utils'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { licenseConfig } from 'src/config'
|
||||
import PostMetadata from '../../components/PostMeta.astro'
|
||||
import ImageWrapper from '../../components/misc/ImageWrapper.astro'
|
||||
import { profileConfig } from '../../config'
|
||||
import { formatDateToYYYYMMDD } from '../../utils/date-utils'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
return blogEntries.map(entry => ({
|
||||
params: { slug: entry.slug }, props: { entry },
|
||||
}));
|
||||
const blogEntries = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true
|
||||
})
|
||||
return blogEntries.map(entry => ({
|
||||
params: { slug: entry.slug },
|
||||
props: { entry },
|
||||
}))
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
const { Content } = await entry.render();
|
||||
const { entry } = Astro.props
|
||||
const { Content } = await entry.render()
|
||||
|
||||
const { remarkPluginFrontmatter } = await entry.render();
|
||||
const { remarkPluginFrontmatter } = await entry.render()
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": entry.data.title,
|
||||
"description": entry.data.description || entry.data.title,
|
||||
"keywords": entry.data.tags,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": profileConfig.name,
|
||||
"url": Astro.site
|
||||
},
|
||||
"datePublished": formatDateToYYYYMMDD(entry.data.published),
|
||||
// TODO include cover image here
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: entry.data.title,
|
||||
description: entry.data.description || entry.data.title,
|
||||
keywords: entry.data.tags,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: profileConfig.name,
|
||||
url: Astro.site,
|
||||
},
|
||||
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
||||
// TODO include cover image here
|
||||
}
|
||||
|
||||
---
|
||||
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description}>
|
||||
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
|
||||
|
@ -139,3 +139,59 @@ const jsonLd = {
|
|||
#post-container :nth-child(5) { animation-delay: calc(var(--content-delay) + 250ms) }
|
||||
#post-container :nth-child(6) { animation-delay: calc(var(--content-delay) + 325ms) }
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import PhotoSwipeLightbox from "photoswipe/lightbox"
|
||||
import "photoswipe/style.css"
|
||||
|
||||
let lightbox: PhotoSwipeLightbox
|
||||
|
||||
function createPhotoSwipe() {
|
||||
lightbox = new PhotoSwipeLightbox({
|
||||
gallery: "#post-container img",
|
||||
pswpModule: () => import("photoswipe"),
|
||||
})
|
||||
|
||||
lightbox.addFilter("domItemData", (itemData, element, linkEl) => {
|
||||
if (element instanceof HTMLImageElement) {
|
||||
itemData.src = element.src
|
||||
|
||||
itemData.w = Number(element.getAttribute("width"))
|
||||
itemData.h = Number(element.getAttribute("height"))
|
||||
|
||||
itemData.msrc = element.src
|
||||
}
|
||||
|
||||
return itemData
|
||||
})
|
||||
|
||||
lightbox.init()
|
||||
}
|
||||
|
||||
const setup = () => {
|
||||
if (!lightbox) {
|
||||
createPhotoSwipe()
|
||||
}
|
||||
window.swup.hooks.on("page:view", () => {
|
||||
createPhotoSwipe()
|
||||
})
|
||||
|
||||
window.swup.hooks.on(
|
||||
"content:replace",
|
||||
() => {
|
||||
console.log("content:replace")
|
||||
lightbox?.destroy?.()
|
||||
},
|
||||
{ before: true },
|
||||
)
|
||||
}
|
||||
|
||||
if (window.swup) {
|
||||
setup()
|
||||
} else {
|
||||
document.addEventListener("swup:enable", setup)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue