feat: add photoswipe for image zoom (#135)

This commit is contained in:
dabuside 2024-07-28 01:41:11 +08:00 committed by saicaca
parent 336290a92f
commit 51025f0ea7
8 changed files with 173 additions and 95 deletions

View File

@ -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

View File

@ -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"
}

View File

@ -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: {}

8
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import type { AstroIntegration } from '@swup/astro'
declare global {
interface Window {
// type from '@swup/astro' is incorrect
swup: AstroIntegration
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>