diff --git a/docs/plugins/AliasRedirects.md b/docs/plugins/AliasRedirects.md index 8c0365377..caf656cee 100644 --- a/docs/plugins/AliasRedirects.md +++ b/docs/plugins/AliasRedirects.md @@ -2,6 +2,10 @@ title: AliasRedirects tags: - plugin/emitter +permalink: "permalink" +alias: + - "alias" + - "aliases" --- This plugin emits HTML redirect pages for aliases and permalinks defined in the frontmatter of content files. @@ -11,14 +15,15 @@ For example, A `foo.md` has the following frontmatter ```md title="foo.md" --- title: "Foo" +permalink: "bar" alias: - - "bar" + - "baz" --- ``` -The target `host.me/bar` will be redirected to `host.me/foo` +The target `host.me/bar` and `host.me/baz` will be redirected to `host.me/foo` -Note that these are permanent redirect. +Note that these are permanent redirects. The emitter supports the following aliases: diff --git a/quartz/plugins/emitters/aliases.ts b/quartz/plugins/emitters/aliases.ts index 9cb9bd576..58bd1c24f 100644 --- a/quartz/plugins/emitters/aliases.ts +++ b/quartz/plugins/emitters/aliases.ts @@ -1,4 +1,4 @@ -import { FullSlug, isRelativeURL, resolveRelative, simplifySlug } from "../../util/path" +import { FullSlug, FilePath, joinSegments, isRelativeURL, resolveRelative, simplifySlug } from "../../util/path" import { QuartzEmitterPlugin } from "../types" import { write } from "./helpers" import { BuildCtx } from "../../util/ctx" @@ -38,17 +38,45 @@ async function* processFile(ctx: BuildCtx, file: VFile) { export const AliasRedirects: QuartzEmitterPlugin = () => ({ name: "AliasRedirects", - async *emit(ctx, content) { - for (const [_tree, file] of content) { - yield* processFile(ctx, file) - } - }, async *partialEmit(ctx, _content, _resources, changeEvents) { for (const changeEvent of changeEvents) { if (!changeEvent.file) continue if (changeEvent.type === "add" || changeEvent.type === "change") { // add new ones if this file still exists yield* processFile(ctx, changeEvent.file) + } + }, + async emit(ctx, content, _resources): Promise { + const { argv } = ctx + const fps: FilePath[] = [] + + for (const [_tree, file] of content) { + const ogSlug = simplifySlug(file.data.slug!) + + const slugs = [file.data.frontmatter?.permalink || [], ...(file.data.aliases || [])] + + for (const slug of slugs ?? []) { + if (typeof slug !== "string") continue + const redirUrl = file.data.slug! + const fp = await write({ + ctx, + content: ` + + + + ${ogSlug} + + + + + + + `, + slug: slug as FullSlug, + ext: ".html", + }) + + fps.push(fp) } } }, diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index db1cf4213..a938cf80b 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -3,9 +3,12 @@ import remarkFrontmatter from "remark-frontmatter" import { QuartzTransformerPlugin } from "../types" import yaml from "js-yaml" import toml from "toml" -import { FilePath, FullSlug, getFileExtension, slugifyFilePath, slugTag } from "../../util/path" +import { FilePath, FullSlug, getFileExtension, joinSegments, slugifyFilePath, slugTag } from "../../util/path" import { QuartzPluginData } from "../vfile" import { i18n } from "../../i18n" +import { Argv } from "../../util/ctx" +import { VFile } from "vfile" + export interface Options { delimiters: string | [string, string] @@ -19,7 +22,10 @@ const defaultOptions: Options = { function coalesceAliases(data: { [key: string]: any }, aliases: string[]) { for (const alias of aliases) { - if (data[alias] !== undefined && data[alias] !== null) return data[alias] + if (data[alias] !== undefined && data[alias] !== null) { + if (typeof data[alias] === "string") return [data[alias]] + return data[alias] + } } } @@ -40,13 +46,14 @@ function coerceToArray(input: string | string[]): string[] | undefined { .map((tag: string | number) => tag.toString()) } -function getAliasSlugs(aliases: string[]): FullSlug[] { - const res: FullSlug[] = [] - for (const alias of aliases) { - const isMd = getFileExtension(alias) === "md" - const mockFp = isMd ? alias : alias + ".md" - const slug = slugifyFilePath(mockFp as FilePath) - res.push(slug) +export function getAliasSlugs(aliases: string[], _argv: Argv, file: VFile): FullSlug[] { + const slugs: FullSlug[] = aliases.map( + (alias) => (alias.startsWith("/") ? alias : `/${alias}`) as FullSlug, + ) + const permalink = file.data.frontmatter?.permalink + if (typeof permalink === "string") { + const absolutePermalink = permalink.startsWith("/") ? permalink : `/${permalink}` + slugs.push(absolutePermalink as FullSlug) } return res @@ -145,6 +152,7 @@ declare module "vfile" { published: string description: string socialDescription: string + permalink: string publish: boolean | string draft: boolean | string lang: string