feat: implement tags system and optimize blog structure
This commit is contained in:
parent
dcf1f74a27
commit
b2f3ec355a
22 changed files with 151 additions and 71 deletions
|
|
@ -28,6 +28,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/dom-view-transitions": "^1.0.5",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@typescript-eslint/parser": "^8.18.0",
|
||||
|
|
|
|||
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
|
|
@ -54,6 +54,9 @@ importers:
|
|||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.15
|
||||
version: 0.5.15(tailwindcss@3.4.16)
|
||||
'@types/dom-view-transitions':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5
|
||||
'@types/node':
|
||||
specifier: ^22.10.2
|
||||
version: 22.10.2
|
||||
|
|
@ -630,67 +633,79 @@ packages:
|
|||
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.0.5':
|
||||
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.0.4':
|
||||
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.33.5':
|
||||
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.33.5':
|
||||
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.33.5':
|
||||
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
||||
|
|
@ -817,46 +832,55 @@ packages:
|
|||
resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.27.4':
|
||||
resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.27.4':
|
||||
resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.27.4':
|
||||
resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
|
||||
resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.27.4':
|
||||
resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.27.4':
|
||||
resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.27.4':
|
||||
resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.27.4':
|
||||
resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.27.4':
|
||||
resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
|
||||
|
|
@ -911,6 +935,9 @@ packages:
|
|||
'@types/debug@4.1.12':
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
|
||||
'@types/dom-view-transitions@1.0.5':
|
||||
resolution: {integrity: sha512-N2sILR7fxSMnaFaAPwGj4DtHCXjIyQTHt+xJyf9jATpzUsTkMNM0DWtqZB6W7f501B/Y0tq3uqat/VlbjuTpMA==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
|
|
@ -4279,6 +4306,8 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/ms': 0.7.34
|
||||
|
||||
'@types/dom-view-transitions@1.0.5': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
---
|
||||
import { config } from "~/config"
|
||||
import { type Post } from "~/types/post"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
|
||||
interface PostProps {
|
||||
slug: string
|
||||
data: {
|
||||
title: string
|
||||
pubDate: Date
|
||||
}
|
||||
}
|
||||
|
||||
const posts = (await getPosts()).slice(0, config.latestPosts ?? 8)
|
||||
---
|
||||
|
||||
<div class="mb-4 text-xl font-medium md:my-8">近期文章</div>
|
||||
{
|
||||
posts.map((post: PostProps) => (
|
||||
posts.map((post: Post) => (
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
import { config } from "~/config"
|
||||
---
|
||||
|
||||
<div class="my-10">{config.intro}</div>
|
||||
<div class="mb-10">{config.intro}</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
import { config } from "~/config"
|
||||
|
||||
const { home, archive, category, tags, custom, about } = config.navigation
|
||||
const { home, archive, tags, custom, about } = config.navigation
|
||||
---
|
||||
|
||||
<div class="flex gap-4 text-lg">
|
||||
<div class="mb-8 flex gap-4 text-lg">
|
||||
{
|
||||
home && (
|
||||
<a href="/" class="hover:underline hover:underline-offset-4">
|
||||
|
|
@ -19,13 +19,6 @@ const { home, archive, category, tags, custom, about } = config.navigation
|
|||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
category && (
|
||||
<a href="/category" class="hover:underline hover:underline-offset-4">
|
||||
<p>分类</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
tags && (
|
||||
<a href="/tags" class="hover:underline hover:underline-offset-4">
|
||||
|
|
|
|||
|
|
@ -8,27 +8,6 @@ import { Moon, Sun } from "lucide-react"
|
|||
</button>
|
||||
|
||||
<script>
|
||||
// check current theme
|
||||
const theme = (() => {
|
||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
||||
return localStorage.getItem("theme")
|
||||
}
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark"
|
||||
}
|
||||
return "light"
|
||||
})()
|
||||
|
||||
// set theme
|
||||
if (theme === "light") {
|
||||
document.documentElement.classList.remove("dark")
|
||||
} else {
|
||||
document.documentElement.classList.add("dark")
|
||||
}
|
||||
|
||||
// save theme
|
||||
window.localStorage.setItem("theme", theme!)
|
||||
|
||||
// update theme
|
||||
const updateTheme = () => {
|
||||
const isDark = document.documentElement.classList.contains("dark")
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ export const config = {
|
|||
navigation: {
|
||||
home: true,
|
||||
archive: true,
|
||||
category: false,
|
||||
tags: false,
|
||||
tags: true,
|
||||
about: true,
|
||||
custom: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const posts = defineCollection({
|
|||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: "我的第一篇博客文章"
|
||||
pubDate: 2024-07-01
|
||||
pubDate: 2022-07-01
|
||||
description: "这是我 Astro 博客的第一篇文章。"
|
||||
author: "Astro 学习者"
|
||||
image:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: "学习了一些 Astro 后,我根本停不下来!"
|
|||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "The Astro logo on a dark background with a purple gradient arc."
|
||||
pubDate: 2024-07-08
|
||||
pubDate: 2023-07-08
|
||||
tags: ["astro", "blogging", "learning in public", "successes"]
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: "我遇到了一些问题,但是在社区里面提问真的很有
|
|||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "The Astro logo on a dark background with rainbow rays."
|
||||
pubDate: 2024-07-15
|
||||
pubDate: 2021-07-15
|
||||
tags: ["astro", "learning in public", "setbacks", "community"]
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const { title, description, pubDate, updatedDate } = Astro.props
|
|||
---
|
||||
|
||||
<HomeLayout>
|
||||
<article class="prose mt-10 dark:prose-invert">
|
||||
<article class="prose my-10 dark:prose-invert">
|
||||
<slot />
|
||||
</article>
|
||||
</HomeLayout>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
layout: ../layouts/post.astro
|
||||
layout: ../../layouts/post.astro
|
||||
title: "关于我"
|
||||
description: "前端开发者 | 摄影爱好者 | 骑行爱好者"
|
||||
---
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div>archive</div>
|
||||
</HomeLayout>
|
||||
45
src/pages/archive/index.astro
Normal file
45
src/pages/archive/index.astro
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { type Post } from "~/types/post"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
|
||||
const posts = await getPosts()
|
||||
|
||||
const postsByYear = posts.reduce(
|
||||
(acc: Record<string, Post[]>, post: Post) => {
|
||||
const year = new Date(post.data.pubDate).getFullYear().toString()
|
||||
if (!acc[year]) {
|
||||
acc[year] = []
|
||||
}
|
||||
acc[year].push(post)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Post[]>,
|
||||
)
|
||||
|
||||
const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a))
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div class="space-y-8">
|
||||
{
|
||||
years.map((year) => (
|
||||
<div class="year-group">
|
||||
<h2 class="mb-4 text-2xl font-bold">{year}</h2>
|
||||
<div class="space-y-2">
|
||||
{postsByYear[year].map((post: Post) => (
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
</p>
|
||||
<p class="line-clamp-3">{post.data.title}</p>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</HomeLayout>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div>category</div>
|
||||
</HomeLayout>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection } from "astro:content"
|
||||
import Post from "~/layouts/post.astro"
|
||||
import PostLayout from "~/layouts/post.astro"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("posts")
|
||||
|
|
@ -15,6 +15,6 @@ const post = Astro.props
|
|||
const { Content } = await post.render()
|
||||
---
|
||||
|
||||
<Post {...post.data}>
|
||||
<PostLayout {...post.data}>
|
||||
<Content />
|
||||
</Post>
|
||||
</PostLayout>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div>tag</div>
|
||||
</HomeLayout>
|
||||
35
src/pages/tags/[tag].astro
Normal file
35
src/pages/tags/[tag].astro
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPosts()
|
||||
const uniqueTags = [...new Set(posts.flatMap((post) => post.data.tags || []))]
|
||||
|
||||
return uniqueTags.map((tag) => ({
|
||||
params: { tag },
|
||||
props: { posts },
|
||||
}))
|
||||
}
|
||||
|
||||
const { tag } = Astro.params
|
||||
const { posts } = Astro.props
|
||||
const filteredPosts = posts.filter((post) => post.data.tags?.includes(tag))
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<ul>
|
||||
{
|
||||
filteredPosts.map((post) => (
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
</p>
|
||||
<p class="line-clamp-3">{post.data.title}</p>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</HomeLayout>
|
||||
19
src/pages/tags/index.astro
Normal file
19
src/pages/tags/index.astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { getPosts } from "~/utils"
|
||||
|
||||
const posts = await getPosts()
|
||||
const tags = [...new Set(posts.map((post) => post.data.tags).flat())]
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div>
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<span class="mx-4">
|
||||
<a href={`/tags/${tag}`}>{tag}</a>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</HomeLayout>
|
||||
7
src/types/post.ts
Normal file
7
src/types/post.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface Post {
|
||||
slug: string
|
||||
data: {
|
||||
title: string
|
||||
pubDate: Date
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue