forked from GitHub/quartz
Compare commits
14 Commits
jackyzha0/
...
v4
| Author | SHA1 | Date | |
|---|---|---|---|
| 7173eba66c | |||
| 883c16d75b | |||
|
|
eccad3da5d | ||
|
|
bcde2abcb2 | ||
|
|
25979ab216 | ||
|
|
9818e1ad57 | ||
|
|
771110a72a | ||
|
|
dc6a9f3b12 | ||
|
|
c0b73ddaa4 | ||
|
|
e86544064c | ||
|
|
a737207981 | ||
|
|
a72b1a4224 | ||
|
|
fbb4523853 | ||
|
|
da1b6b37fe |
2
.github/workflows/docker-build-push.yaml
vendored
2
.github/workflows/docker-build-push.yaml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Inject slug/short variables
|
- name: Inject slug/short variables
|
||||||
uses: rlespinasse/github-slug-action@v5.0.0
|
uses: rlespinasse/github-slug-action@v5.1.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
160
action.sh
Normal file
160
action.sh
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# To fetch and use this script in a GitHub action:
|
||||||
|
#
|
||||||
|
# curl -s -S https://raw.githubusercontent.com/saberzero1/quartz-themes/master/action.sh | bash -s -- <THEME_NAME>
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[1;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo_err() { echo -e "${RED}$1${NC}"; }
|
||||||
|
echo_warn() { echo -e "${YELLOW}$1${NC}"; }
|
||||||
|
echo_ok() { echo -e "${GREEN}$1${NC}"; }
|
||||||
|
echo_info() { echo -e "${BLUE}$1${NC}"; }
|
||||||
|
|
||||||
|
THEME_DIR="themes"
|
||||||
|
QUARTZ_STYLES_DIR="quartz/styles"
|
||||||
|
|
||||||
|
if test -f ${QUARTZ_STYLES_DIR}/custom.scss; then
|
||||||
|
echo_ok "Quartz root succesfully detected..."
|
||||||
|
THEME_DIR="${QUARTZ_STYLES_DIR}/${THEME_DIR}"
|
||||||
|
else
|
||||||
|
echo_warn "Quartz root not detected, checking if we are in the styles directory..."
|
||||||
|
if test -f custom.scss; then
|
||||||
|
echo_ok "Styles directory detected..."
|
||||||
|
else
|
||||||
|
echo_err "Cannot detect Quartz repository. Are you in the correct working directory?" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "Input theme: ${BLUE}$*${NC}"
|
||||||
|
|
||||||
|
echo "Parsing input theme..."
|
||||||
|
|
||||||
|
# Concat parameters
|
||||||
|
result=""
|
||||||
|
|
||||||
|
for param in "$@"; do
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
result="$result-"
|
||||||
|
fi
|
||||||
|
|
||||||
|
result="$result$param"
|
||||||
|
done
|
||||||
|
|
||||||
|
if "$result" = ""; then
|
||||||
|
echo_warn "No theme provided, defaulting to Tokyo Night..."
|
||||||
|
result="tokyo-night"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert to lowercase
|
||||||
|
THEME=$(echo "$result" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
echo -e "Theme ${BLUE}$*${NC} parsed to $(echo_info ${THEME})"
|
||||||
|
|
||||||
|
echo "Validating theme..."
|
||||||
|
|
||||||
|
GITHUB_URL_BASE="https://raw.githubusercontent.com/saberzero1/quartz-themes/master/__CONVERTER/"
|
||||||
|
GITHUB_OUTPUT_DIR="__OUTPUT/"
|
||||||
|
GITHUB_OVERRIDE_DIR="__OVERRIDES/"
|
||||||
|
GITHUB_THEME_DIR="${THEME}/"
|
||||||
|
CSS_INDEX_URL="${GITHUB_URL_BASE}${GITHUB_OUTPUT_DIR}${GITHUB_THEME_DIR}_index.scss"
|
||||||
|
CSS_FONT_URL="${GITHUB_URL_BASE}${GITHUB_OUTPUT_DIR}${GITHUB_THEME_DIR}_fonts.scss"
|
||||||
|
CSS_DARK_URL="${GITHUB_URL_BASE}${GITHUB_OUTPUT_DIR}${GITHUB_THEME_DIR}_dark.scss"
|
||||||
|
CSS_LIGHT_URL="${GITHUB_URL_BASE}${GITHUB_OUTPUT_DIR}${GITHUB_THEME_DIR}_light.scss"
|
||||||
|
CSS_OVERRIDE_URL="${GITHUB_URL_BASE}${GITHUB_OVERRIDE_DIR}${GITHUB_THEME_DIR}_index.scss"
|
||||||
|
README_URL="${GITHUB_URL_BASE}${GITHUB_OVERRIDE_DIR}${GITHUB_THEME_DIR}README.md"
|
||||||
|
|
||||||
|
PULSE=$(curl -o /dev/null --silent -lw '%{http_code}' "${CSS_INDEX_URL}")
|
||||||
|
|
||||||
|
if [ "${PULSE}" = "200" ]; then
|
||||||
|
echo_ok "Theme '${THEME}' found. Preparing to fetch files..."
|
||||||
|
else
|
||||||
|
if [ "${PULSE}" = "404" ]; then
|
||||||
|
echo_err "Theme '${THEME}' not found. Please check the compatibility list." 1>&2
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo_err "Something weird happened. If this issue persists, please open an Issue on GitHub." !>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cleaning theme directory..."
|
||||||
|
|
||||||
|
rm -rf ${THEME_DIR}
|
||||||
|
|
||||||
|
echo "Creating theme directory..."
|
||||||
|
|
||||||
|
mkdir -p ${THEME_DIR}/overrides
|
||||||
|
|
||||||
|
echo "Fetching theme files..."
|
||||||
|
|
||||||
|
curl -s -S -o ${THEME_DIR}/_index.scss "${CSS_INDEX_URL}"
|
||||||
|
curl -s -S -o ${THEME_DIR}/_fonts.scss "${CSS_FONT_URL}"
|
||||||
|
curl -s -S -o ${THEME_DIR}/_dark.scss "${CSS_DARK_URL}"
|
||||||
|
curl -s -S -o ${THEME_DIR}/_light.scss "${CSS_LIGHT_URL}"
|
||||||
|
curl -s -S -o ${THEME_DIR}/overrides/_index.scss "${CSS_OVERRIDE_URL}"
|
||||||
|
|
||||||
|
echo "Fetching README file..."
|
||||||
|
|
||||||
|
curl -s -S -o ${THEME_DIR}/README.md "${README_URL}"
|
||||||
|
|
||||||
|
echo "Checking theme files..."
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/_index.scss; then
|
||||||
|
echo_ok "_index.scss exists"
|
||||||
|
else
|
||||||
|
echo_err "_index.scss missing" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/_fonts.scss; then
|
||||||
|
echo_ok "_fonts.scss exists"
|
||||||
|
else
|
||||||
|
echo_err "_fonts.scss missing" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/_dark.scss; then
|
||||||
|
echo_ok "_dark.scss exists"
|
||||||
|
else
|
||||||
|
echo_err "_dark.scss missing" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/_light.scss; then
|
||||||
|
echo_ok "_light.scss exists"
|
||||||
|
else
|
||||||
|
echo_err "_light.scss missing" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/overrides/_index.scss; then
|
||||||
|
echo_ok "overrides/_index.scss exists"
|
||||||
|
else
|
||||||
|
echo_err "overrides/_index.scss missing" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f ${THEME_DIR}/README.md; then
|
||||||
|
echo_ok "README file exists"
|
||||||
|
else
|
||||||
|
echo_warn "README file missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verifying setup..."
|
||||||
|
|
||||||
|
if grep -q '^@use "./themes";' ${THEME_DIR}/../custom.scss; then
|
||||||
|
# Import already present in custom.scss
|
||||||
|
echo_warn "Theme import line already present in custom.scss. Skipping..."
|
||||||
|
else
|
||||||
|
# Add `@use "./themes";` import to custom.scss
|
||||||
|
sed -ir 's#@use "./base.scss";#@use "./base.scss";\n@use "./themes";#' ${THEME_DIR}/../custom.scss
|
||||||
|
echo_info "Added import line to custom.scss..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo_ok "Finished fetching and applying theme '${THEME}'."
|
||||||
@ -41,11 +41,12 @@ This part of the configuration concerns anything that can affect the whole site.
|
|||||||
- `ignorePatterns`: a list of [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details.
|
- `ignorePatterns`: a list of [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details.
|
||||||
- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings.
|
- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings.
|
||||||
- `theme`: configure how the site looks.
|
- `theme`: configure how the site looks.
|
||||||
- `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
|
- `cdnCaching`: if `true` (default), use Google CDN to cache the fonts. This will generally be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
|
||||||
- `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here.
|
- `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here.
|
||||||
- `header`: Font to use for headers
|
- `title`: font for the title of the site (optional, same as `header` by default)
|
||||||
- `code`: Font for inline and block quotes.
|
- `header`: font to use for headers
|
||||||
- `body`: Font for everything
|
- `code`: font for inline and block quotes
|
||||||
|
- `body`: font for everything
|
||||||
- `colors`: controls the theming of the site.
|
- `colors`: controls the theming of the site.
|
||||||
- `light`: page background
|
- `light`: page background
|
||||||
- `lightgray`: borders
|
- `lightgray`: borders
|
||||||
|
|||||||
@ -8,24 +8,24 @@ import * as Plugin from "./quartz/plugins"
|
|||||||
*/
|
*/
|
||||||
const config: QuartzConfig = {
|
const config: QuartzConfig = {
|
||||||
configuration: {
|
configuration: {
|
||||||
pageTitle: "Quartz 4",
|
pageTitle: "isuckatcode.lol",
|
||||||
pageTitleSuffix: "",
|
pageTitleSuffix: " | isuckatcode.lol",
|
||||||
enableSPA: true,
|
enableSPA: true,
|
||||||
enablePopovers: true,
|
enablePopovers: true,
|
||||||
analytics: {
|
analytics: {
|
||||||
provider: "plausible",
|
provider: "plausible",
|
||||||
},
|
},
|
||||||
locale: "en-US",
|
locale: "en-US",
|
||||||
baseUrl: "quartz.jzhao.xyz",
|
baseUrl: "isuckatcode.lol",
|
||||||
ignorePatterns: ["private", "templates", ".obsidian"],
|
ignorePatterns: ["private", "templates", ".obsidian"],
|
||||||
defaultDateType: "created",
|
defaultDateType: "created",
|
||||||
theme: {
|
theme: {
|
||||||
fontOrigin: "googleFonts",
|
fontOrigin: "googleFonts",
|
||||||
cdnCaching: true,
|
cdnCaching: true,
|
||||||
typography: {
|
typography: {
|
||||||
header: "Schibsted Grotesk",
|
header: "Courier Prime",
|
||||||
body: "Source Sans Pro",
|
body: "Roboto",
|
||||||
code: "IBM Plex Mono",
|
code: "Courier Prime",
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
lightMode: {
|
lightMode: {
|
||||||
@ -57,7 +57,7 @@ const config: QuartzConfig = {
|
|||||||
transformers: [
|
transformers: [
|
||||||
Plugin.FrontMatter(),
|
Plugin.FrontMatter(),
|
||||||
Plugin.CreatedModifiedDate({
|
Plugin.CreatedModifiedDate({
|
||||||
priority: ["git", "frontmatter", "filesystem"],
|
priority: ["frontmatter", "git", "filesystem"],
|
||||||
}),
|
}),
|
||||||
Plugin.SyntaxHighlighting({
|
Plugin.SyntaxHighlighting({
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { i18n } from "../i18n"
|
import { i18n } from "../i18n"
|
||||||
import { FullSlug, getFileExtension, joinSegments, pathToRoot } from "../util/path"
|
import { FullSlug, getFileExtension, joinSegments, pathToRoot } from "../util/path"
|
||||||
import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources"
|
import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources"
|
||||||
import { googleFontHref } from "../util/theme"
|
import { googleFontHref, googleFontSubsetHref } from "../util/theme"
|
||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import { unescapeHTML } from "../util/escape"
|
import { unescapeHTML } from "../util/escape"
|
||||||
import { CustomOgImagesEmitterName } from "../plugins/emitters/ogImage"
|
import { CustomOgImagesEmitterName } from "../plugins/emitters/ogImage"
|
||||||
@ -45,6 +45,9 @@ export default (() => {
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||||
<link rel="stylesheet" href={googleFontHref(cfg.theme)} />
|
<link rel="stylesheet" href={googleFontHref(cfg.theme)} />
|
||||||
|
{cfg.theme.typography.title && (
|
||||||
|
<link rel="stylesheet" href={googleFontSubsetHref(cfg.theme, cfg.pageTitle)} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="anonymous" />
|
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="anonymous" />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { FullSlug, resolveRelative } from "../util/path"
|
import { FullSlug, isFolderPath, resolveRelative } from "../util/path"
|
||||||
import { QuartzPluginData } from "../plugins/vfile"
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
import { Date, getDate } from "./Date"
|
import { Date, getDate } from "./Date"
|
||||||
import { QuartzComponent, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentProps } from "./types"
|
||||||
@ -8,6 +8,13 @@ export type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number
|
|||||||
|
|
||||||
export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn {
|
export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn {
|
||||||
return (f1, f2) => {
|
return (f1, f2) => {
|
||||||
|
// Sort folders first
|
||||||
|
const f1IsFolder = isFolderPath(f1.slug ?? "")
|
||||||
|
const f2IsFolder = isFolderPath(f2.slug ?? "")
|
||||||
|
if (f1IsFolder && !f2IsFolder) return -1
|
||||||
|
if (!f1IsFolder && f2IsFolder) return 1
|
||||||
|
|
||||||
|
// If both are folders or both are files, sort by date/alphabetical
|
||||||
if (f1.dates && f2.dates) {
|
if (f1.dates && f2.dates) {
|
||||||
// sort descending
|
// sort descending
|
||||||
return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime()
|
return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime()
|
||||||
|
|||||||
@ -17,6 +17,7 @@ PageTitle.css = `
|
|||||||
.page-title {
|
.page-title {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: var(--titleFont);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import path from "path"
|
|
||||||
|
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
import { byDateAndAlphabetical, PageList, SortFn } from "../PageList"
|
import { PageList, SortFn } from "../PageList"
|
||||||
import { stripSlashes, simplifySlug, joinSegments, FullSlug } from "../../util/path"
|
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
import { htmlToJsx } from "../../util/jsx"
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
import { i18n } from "../../i18n"
|
import { i18n } from "../../i18n"
|
||||||
import { QuartzPluginData } from "../../plugins/vfile"
|
import { QuartzPluginData } from "../../plugins/vfile"
|
||||||
import { ComponentChildren } from "preact"
|
import { ComponentChildren } from "preact"
|
||||||
import { concatenateResources } from "../../util/resources"
|
import { concatenateResources } from "../../util/resources"
|
||||||
|
import { FileTrieNode } from "../../util/fileTrie"
|
||||||
interface FolderContentOptions {
|
interface FolderContentOptions {
|
||||||
/**
|
/**
|
||||||
* Whether to display number of folders
|
* Whether to display number of folders
|
||||||
@ -27,51 +25,88 @@ const defaultOptions: FolderContentOptions = {
|
|||||||
|
|
||||||
export default ((opts?: Partial<FolderContentOptions>) => {
|
export default ((opts?: Partial<FolderContentOptions>) => {
|
||||||
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
||||||
|
let trie: FileTrieNode<
|
||||||
|
QuartzPluginData & {
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
filePath: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
const FolderContent: QuartzComponent = (props: QuartzComponentProps) => {
|
const FolderContent: QuartzComponent = (props: QuartzComponentProps) => {
|
||||||
const { tree, fileData, allFiles, cfg } = props
|
const { tree, fileData, allFiles, cfg } = props
|
||||||
const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
|
|
||||||
const folderParts = folderSlug.split(path.posix.sep)
|
|
||||||
|
|
||||||
const allPagesInFolder: QuartzPluginData[] = []
|
if (!trie) {
|
||||||
const allPagesInSubfolders: Map<FullSlug, QuartzPluginData[]> = new Map()
|
trie = new FileTrieNode([])
|
||||||
|
allFiles.forEach((file) => {
|
||||||
|
if (file.frontmatter) {
|
||||||
|
trie.add({
|
||||||
|
...file,
|
||||||
|
slug: file.slug!,
|
||||||
|
title: file.frontmatter.title,
|
||||||
|
filePath: file.filePath!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
allFiles.forEach((file) => {
|
const folder = trie.findNode(fileData.slug!.split("/"))
|
||||||
const fileSlug = stripSlashes(simplifySlug(file.slug!))
|
if (!folder) {
|
||||||
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
|
return null
|
||||||
const fileParts = fileSlug.split(path.posix.sep)
|
}
|
||||||
const isDirectChild = fileParts.length === folderParts.length + 1
|
|
||||||
|
|
||||||
if (!prefixed) {
|
const allPagesInFolder: QuartzPluginData[] =
|
||||||
return
|
folder.children
|
||||||
}
|
.map((node) => {
|
||||||
|
// regular file, proceed
|
||||||
|
if (node.data) {
|
||||||
|
return node.data
|
||||||
|
}
|
||||||
|
|
||||||
if (isDirectChild) {
|
if (node.isFolder && options.showSubfolders) {
|
||||||
allPagesInFolder.push(file)
|
// folders that dont have data need synthetic files
|
||||||
} else if (options.showSubfolders) {
|
const getMostRecentDates = (): QuartzPluginData["dates"] => {
|
||||||
const subfolderSlug = joinSegments(
|
let maybeDates: QuartzPluginData["dates"] | undefined = undefined
|
||||||
...fileParts.slice(0, folderParts.length + 1),
|
for (const child of node.children) {
|
||||||
) as FullSlug
|
if (child.data?.dates) {
|
||||||
const pagesInFolder = allPagesInSubfolders.get(subfolderSlug) || []
|
// compare all dates and assign to maybeDates if its more recent or its not set
|
||||||
allPagesInSubfolders.set(subfolderSlug, [...pagesInFolder, file])
|
if (!maybeDates) {
|
||||||
}
|
maybeDates = { ...child.data.dates }
|
||||||
})
|
} else {
|
||||||
|
if (child.data.dates.created > maybeDates.created) {
|
||||||
|
maybeDates.created = child.data.dates.created
|
||||||
|
}
|
||||||
|
|
||||||
allPagesInSubfolders.forEach((files, subfolderSlug) => {
|
if (child.data.dates.modified > maybeDates.modified) {
|
||||||
const hasIndex = allPagesInFolder.some(
|
maybeDates.modified = child.data.dates.modified
|
||||||
(file) => subfolderSlug === stripSlashes(simplifySlug(file.slug!)),
|
}
|
||||||
)
|
|
||||||
if (!hasIndex) {
|
if (child.data.dates.published > maybeDates.published) {
|
||||||
const subfolderDates = files.sort(byDateAndAlphabetical(cfg))[0].dates
|
maybeDates.published = child.data.dates.published
|
||||||
const subfolderTitle = subfolderSlug.split(path.posix.sep).at(-1)!
|
}
|
||||||
allPagesInFolder.push({
|
}
|
||||||
slug: subfolderSlug,
|
}
|
||||||
dates: subfolderDates,
|
}
|
||||||
frontmatter: { title: subfolderTitle, tags: ["folder"] },
|
return (
|
||||||
|
maybeDates ?? {
|
||||||
|
created: new Date(),
|
||||||
|
modified: new Date(),
|
||||||
|
published: new Date(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
slug: node.slug,
|
||||||
|
dates: getMostRecentDates(),
|
||||||
|
frontmatter: {
|
||||||
|
title: node.displayName,
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
.filter((page) => page !== undefined) ?? []
|
||||||
})
|
|
||||||
|
|
||||||
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
|
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
|
||||||
const classes = cssClasses.join(" ")
|
const classes = cssClasses.join(" ")
|
||||||
const listProps = {
|
const listProps = {
|
||||||
|
|||||||
@ -134,9 +134,9 @@ function createFolderNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
const childNode = child.data
|
const childNode = child.isFolder
|
||||||
? createFileNode(currentSlug, child)
|
? createFolderNode(currentSlug, child, opts)
|
||||||
: createFolderNode(currentSlug, child, opts)
|
: createFileNode(currentSlug, child)
|
||||||
ul.appendChild(childNode)
|
ul.appendChild(childNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,8 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.mobile-explorer {
|
button.mobile-explorer {
|
||||||
|
|||||||
@ -9,7 +9,12 @@ import styles from "../../styles/custom.scss"
|
|||||||
import popoverStyle from "../../components/styles/popover.scss"
|
import popoverStyle from "../../components/styles/popover.scss"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { QuartzComponent } from "../../components/types"
|
import { QuartzComponent } from "../../components/types"
|
||||||
import { googleFontHref, joinStyles, processGoogleFonts } from "../../util/theme"
|
import {
|
||||||
|
googleFontHref,
|
||||||
|
googleFontSubsetHref,
|
||||||
|
joinStyles,
|
||||||
|
processGoogleFonts,
|
||||||
|
} from "../../util/theme"
|
||||||
import { Features, transform } from "lightningcss"
|
import { Features, transform } from "lightningcss"
|
||||||
import { transform as transpile } from "esbuild"
|
import { transform as transpile } from "esbuild"
|
||||||
import { write } from "./helpers"
|
import { write } from "./helpers"
|
||||||
@ -211,9 +216,16 @@ export const ComponentResources: QuartzEmitterPlugin = () => {
|
|||||||
// let the user do it themselves in css
|
// let the user do it themselves in css
|
||||||
} else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) {
|
} else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) {
|
||||||
// when cdnCaching is true, we link to google fonts in Head.tsx
|
// when cdnCaching is true, we link to google fonts in Head.tsx
|
||||||
const response = await fetch(googleFontHref(ctx.cfg.configuration.theme))
|
const theme = ctx.cfg.configuration.theme
|
||||||
|
const response = await fetch(googleFontHref(theme))
|
||||||
googleFontsStyleSheet = await response.text()
|
googleFontsStyleSheet = await response.text()
|
||||||
|
|
||||||
|
if (theme.typography.title) {
|
||||||
|
const title = ctx.cfg.configuration.pageTitle
|
||||||
|
const response = await fetch(googleFontSubsetHref(theme, title))
|
||||||
|
googleFontsStyleSheet += `\n${await response.text()}`
|
||||||
|
}
|
||||||
|
|
||||||
if (!cfg.baseUrl) {
|
if (!cfg.baseUrl) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"baseUrl must be defined when using Google Fonts without cfg.theme.cdnCaching",
|
"baseUrl must be defined when using Google Fonts without cfg.theme.cdnCaching",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import { i18n } from "../../i18n"
|
import { i18n } from "../../i18n"
|
||||||
import { unescapeHTML } from "../../util/escape"
|
import { unescapeHTML } from "../../util/escape"
|
||||||
import { FullSlug, getFileExtension } from "../../util/path"
|
import { FullSlug, getFileExtension, joinSegments, QUARTZ } from "../../util/path"
|
||||||
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
||||||
import sharp from "sharp"
|
import sharp from "sharp"
|
||||||
import satori, { SatoriOptions } from "satori"
|
import satori, { SatoriOptions } from "satori"
|
||||||
@ -10,6 +10,8 @@ import { Readable } from "stream"
|
|||||||
import { write } from "./helpers"
|
import { write } from "./helpers"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { QuartzPluginData } from "../vfile"
|
import { QuartzPluginData } from "../vfile"
|
||||||
|
import fs from "node:fs/promises"
|
||||||
|
import chalk from "chalk"
|
||||||
|
|
||||||
const defaultOptions: SocialImageOptions = {
|
const defaultOptions: SocialImageOptions = {
|
||||||
colorScheme: "lightMode",
|
colorScheme: "lightMode",
|
||||||
@ -28,7 +30,25 @@ async function generateSocialImage(
|
|||||||
userOpts: SocialImageOptions,
|
userOpts: SocialImageOptions,
|
||||||
): Promise<Readable> {
|
): Promise<Readable> {
|
||||||
const { width, height } = userOpts
|
const { width, height } = userOpts
|
||||||
const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData)
|
const iconPath = joinSegments(QUARTZ, "static", "icon.png")
|
||||||
|
let iconBase64: string | undefined = undefined
|
||||||
|
try {
|
||||||
|
const iconData = await fs.readFile(iconPath)
|
||||||
|
iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(chalk.yellow(`Warning: Could not find icon at ${iconPath}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageComponent = userOpts.imageStructure({
|
||||||
|
cfg,
|
||||||
|
userOpts,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
fonts,
|
||||||
|
fileData,
|
||||||
|
iconBase64,
|
||||||
|
})
|
||||||
|
|
||||||
const svg = await satori(imageComponent, {
|
const svg = await satori(imageComponent, {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import path from "path"
|
|
||||||
import { Repository } from "@napi-rs/simple-git"
|
import { Repository } from "@napi-rs/simple-git"
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
priority: ("frontmatter" | "git" | "filesystem")[]
|
priority: ("frontmatter" | "git" | "filesystem")[]
|
||||||
@ -35,13 +35,25 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (u
|
|||||||
return [
|
return [
|
||||||
() => {
|
() => {
|
||||||
let repo: Repository | undefined = undefined
|
let repo: Repository | undefined = undefined
|
||||||
|
let repositoryWorkdir: string
|
||||||
|
if (opts.priority.includes("git")) {
|
||||||
|
try {
|
||||||
|
repo = Repository.discover(ctx.argv.directory)
|
||||||
|
repositoryWorkdir = repo.workdir() ?? ctx.argv.directory
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(`\nWarning: couldn't find git repository for ${ctx.argv.directory}`),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return async (_tree, file) => {
|
return async (_tree, file) => {
|
||||||
let created: MaybeDate = undefined
|
let created: MaybeDate = undefined
|
||||||
let modified: MaybeDate = undefined
|
let modified: MaybeDate = undefined
|
||||||
let published: MaybeDate = undefined
|
let published: MaybeDate = undefined
|
||||||
|
|
||||||
const fp = file.data.relativePath!
|
const fp = file.data.relativePath!
|
||||||
const fullFp = path.posix.join(ctx.argv.directory, fp)
|
const fullFp = file.data.filePath!
|
||||||
for (const source of opts.priority) {
|
for (const source of opts.priority) {
|
||||||
if (source === "filesystem") {
|
if (source === "filesystem") {
|
||||||
const st = await fs.promises.stat(fullFp)
|
const st = await fs.promises.stat(fullFp)
|
||||||
@ -51,21 +63,14 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (u
|
|||||||
created ||= file.data.frontmatter.created as MaybeDate
|
created ||= file.data.frontmatter.created as MaybeDate
|
||||||
modified ||= file.data.frontmatter.modified as MaybeDate
|
modified ||= file.data.frontmatter.modified as MaybeDate
|
||||||
published ||= file.data.frontmatter.published as MaybeDate
|
published ||= file.data.frontmatter.published as MaybeDate
|
||||||
} else if (source === "git") {
|
} else if (source === "git" && repo) {
|
||||||
if (!repo) {
|
|
||||||
// Get a reference to the main git repo.
|
|
||||||
// It's either the same as the workdir,
|
|
||||||
// or 1+ level higher in case of a submodule/subtree setup
|
|
||||||
repo = Repository.discover(ctx.argv.directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
modified ||= await repo.getFileLatestModifiedDateAsync(fullFp)
|
const relativePath = path.relative(repositoryWorkdir, fullFp)
|
||||||
|
modified ||= await repo.getFileLatestModifiedDateAsync(relativePath)
|
||||||
} catch {
|
} catch {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.yellow(
|
chalk.yellow(
|
||||||
`\nWarning: ${file.data
|
`\nWarning: ${file.data.filePath!} isn't yet tracked by git, dates will be inaccurate`,
|
||||||
.filePath!} isn't yet tracked by git, last modification date is not available for this file`,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,7 +172,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
|||||||
workerType: "thread",
|
workerType: "thread",
|
||||||
})
|
})
|
||||||
const errorHandler = (err: any) => {
|
const errorHandler = (err: any) => {
|
||||||
console.error(`${err}`.replace(/^error:\s*/i, ""))
|
console.error(err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
|||||||
|
|
||||||
const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
|
const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
|
||||||
processedFiles = 0
|
processedFiles = 0
|
||||||
for (const [mdChunk, _] of mdResults) {
|
for (const mdChunk of mdResults) {
|
||||||
markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
|
markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
|
||||||
}
|
}
|
||||||
const results: ProcessedContent[][] = await Promise.all(
|
const results: ProcessedContent[][] = await Promise.all(
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
@use "./base.scss";
|
@use "./base.scss";
|
||||||
|
@use "./themes";
|
||||||
|
|
||||||
// put your custom CSS here!
|
// put your custom CSS here!
|
||||||
|
|||||||
3
quartz/styles/custom.scssr
Normal file
3
quartz/styles/custom.scssr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@use "./base.scss";
|
||||||
|
|
||||||
|
// put your custom CSS here!
|
||||||
1
quartz/styles/themes/README.md
Normal file
1
quartz/styles/themes/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
404: Not Found
|
||||||
170
quartz/styles/themes/_dark.scss
Normal file
170
quartz/styles/themes/_dark.scss
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
:root[saved-theme="dark"] {
|
||||||
|
--accent-h: 202;
|
||||||
|
--accent-s: 100%;
|
||||||
|
--accent-l: 75%;
|
||||||
|
--bg_dark2_x: 18, 18, 24;
|
||||||
|
--bg_dark2: rgb(var(--bg_dark2_x));
|
||||||
|
--bg_dark_x: 22, 22, 30;
|
||||||
|
--bg_dark: rgb(var(--bg_dark_x));
|
||||||
|
--bg_x: 26, 27, 38;
|
||||||
|
--bg: rgb(var(--bg_x));
|
||||||
|
--bg_highlight_x: 41, 46, 66;
|
||||||
|
--bg_highlight: rgb(var(--bg_highlight_x));
|
||||||
|
--bg_highlight_dark_x: 36, 40, 59;
|
||||||
|
--bg_highlight_dark: rgb(var(--bg_highlight_dark_x));
|
||||||
|
--terminal_black_x: 65, 72, 104;
|
||||||
|
--terminal_black: rgb(var(--terminal_black_x));
|
||||||
|
--fg_x: 192, 202, 245;
|
||||||
|
--fg: rgb(var(--fg_x));
|
||||||
|
--fg_dark_x: 169, 177, 214;
|
||||||
|
--fg_dark: rgb(var(--fg_dark_x));
|
||||||
|
--comment_x: 86, 95, 137;
|
||||||
|
--comment: rgb(var(--comment_x));
|
||||||
|
--blue0_x: 61, 89, 161;
|
||||||
|
--blue0: rgb(var(--blue0_x));
|
||||||
|
--blue_x: 122, 162, 247;
|
||||||
|
--blue: rgb(var(--blue_x));
|
||||||
|
--cyan_hsl: 202 100% 75%;
|
||||||
|
--cyan_x: 125, 207, 255;
|
||||||
|
--cyan: rgb(var(--cyan_x));
|
||||||
|
--magent_hsl: 261 85% 79%;
|
||||||
|
--magenta_x: 187, 154, 247;
|
||||||
|
--magenta: rgb(var(--magenta_x));
|
||||||
|
--orange_x: 255, 158, 100;
|
||||||
|
--orange: rgb(var(--orange_x));
|
||||||
|
--yellow_x: 224, 175, 104;
|
||||||
|
--yellow: rgb(var(--yellow_x));
|
||||||
|
--green_x: 158, 206, 106;
|
||||||
|
--green: rgb(var(--green_x));
|
||||||
|
--teal_x: 26, 188, 156;
|
||||||
|
--teal: rgb(var(--teal_x));
|
||||||
|
--red_x: 255, 117, 127;
|
||||||
|
--red: rgb(var(--red_x));
|
||||||
|
--red1_x: 219, 75, 75;
|
||||||
|
--red1: rgb(var(--red1_x));
|
||||||
|
--unknown: #ffffff;
|
||||||
|
--color_red_rgb: var(--red_x);
|
||||||
|
--color-red: var(--red);
|
||||||
|
--color_purple_rgb: var(--magenta_x);
|
||||||
|
--color-purple: var(--magenta);
|
||||||
|
--color_green_rgb: var(--green_x);
|
||||||
|
--color-green: var(--green);
|
||||||
|
--color_cyan_rgb: var(--cyan_x);
|
||||||
|
--color-cyan: var(--cyan);
|
||||||
|
--color_blue_rgb: var(--blue_x);
|
||||||
|
--color-blue: var(--blue);
|
||||||
|
--color_yellow_rgb: var(--yellow_x);
|
||||||
|
--color-yellow: var(--yellow);
|
||||||
|
--color_orange_rgb: var(--orange_x);
|
||||||
|
--color-orange: var(--orange);
|
||||||
|
--color_pink_rgb: var(--magenta_x);
|
||||||
|
--color-pink: var(--magenta);
|
||||||
|
--background-primary: var(--bg);
|
||||||
|
--background-primary-alt: var(--bg);
|
||||||
|
--background-secondary: var(--bg_dark);
|
||||||
|
--background-secondary-alt: var(--bg_dark);
|
||||||
|
--background-modifier-border: var(--bg_highlight);
|
||||||
|
--background-modifier-border-focus: var(--bg_highlight);
|
||||||
|
--background-modifier-border-hover: var(--bg_highlight);
|
||||||
|
--background-modifier-form-field: var(--bg_dark);
|
||||||
|
--background-modifier-form-field-highlighted: var(--bg_dark);
|
||||||
|
--background-modifier-box-shadow: rgba(0, 0, 0, 0.3);
|
||||||
|
--background-modifier-success: var(--green);
|
||||||
|
--background-modifier-error: var(--red1);
|
||||||
|
--background-modifier-error-hover: var(--red);
|
||||||
|
--background-modifier-cover: rgba(var(--bg_dark_x), 0.8);
|
||||||
|
--background-modifier-hover: var(--bg_highlight);
|
||||||
|
--background-modifier-message: rgba(var(--bg_highlight_x), 0.9);
|
||||||
|
--background-modifier-active-hover: var(--bg_highlight);
|
||||||
|
--text-normal: var(--fg);
|
||||||
|
--text-faint: var(--comment);
|
||||||
|
--text-muted: var(--fg_dark);
|
||||||
|
--text-error: var(--red);
|
||||||
|
--text-accent: var(--magenta);
|
||||||
|
--text-accent-hover: var(--cyan);
|
||||||
|
--text-error: var(--red1);
|
||||||
|
--text-error-hover: var(--red);
|
||||||
|
--text-selection: var(--unknown);
|
||||||
|
--text-on-accent: var(--bg);
|
||||||
|
--text-highlight-bg: rgba(var(--orange_x), 0.4);
|
||||||
|
--text-selection: rgba(var(--blue0_x), 0.6);
|
||||||
|
--bold-color: var(--cyan);
|
||||||
|
--italic-color: var(--cyan);
|
||||||
|
--interactive-normal: var(--bg_dark);
|
||||||
|
--interactive-hover: var(--bg);
|
||||||
|
--interactive-success: var(--green);
|
||||||
|
--interactive-accent: hsl(var(--accent-h), var(--accent-s), var(--accent-l));
|
||||||
|
--interactive-accent-hover: var(--blue);
|
||||||
|
--scrollbar-bg: var(--bg_dark2);
|
||||||
|
--scrollbar-thumb-bg: var(--comment);
|
||||||
|
--scrollbar-active-thumb-bg: var(--bg_dark);
|
||||||
|
--scrollbar-width: 0px;
|
||||||
|
--h1-color: var(--red);
|
||||||
|
--h2-color: var(--yellow);
|
||||||
|
--h3-color: var(--green);
|
||||||
|
--h4-color: var(--cyan);
|
||||||
|
--h5-color: var(--blue);
|
||||||
|
--h6-color: var(--magenta);
|
||||||
|
--border-width: 2px;
|
||||||
|
--tag-color: var(--magenta);
|
||||||
|
--tag-background: rgba(var(--magenta_x), 0.15);
|
||||||
|
--tag-color-hover: var(--cyan);
|
||||||
|
--tag-background-hover: rgba(var(--cyan_x), 0.15);
|
||||||
|
--link-color: var(--magenta);
|
||||||
|
--link-color-hover: var(--cyan);
|
||||||
|
--link-external-color: var(--magenta);
|
||||||
|
--link-external-color-hover: var(--cyan);
|
||||||
|
--checkbox-radius: var(--radius-l);
|
||||||
|
--checkbox-color: var(--green);
|
||||||
|
--checkbox-color-hover: var(--green);
|
||||||
|
--checkbox-marker-color: var(--bg);
|
||||||
|
--checkbox-border-color: var(--comment);
|
||||||
|
--checkbox-border-color-hover: var(--comment);
|
||||||
|
--table-header-background: var(--bg_dark2);
|
||||||
|
--table-header-background-hover: var(--bg_dark2);
|
||||||
|
--flashing-background: rgba(var(--blue0_x), 0.3);
|
||||||
|
--code-normal: var(--fg);
|
||||||
|
--code-background: var(--bg_highlight_dark);
|
||||||
|
--mermaid-note: var(--blue0);
|
||||||
|
--mermaid-actor: var(--fg_dark);
|
||||||
|
--mermaid-loopline: var(--blue);
|
||||||
|
--blockquote-background-color: var(--bg_dark);
|
||||||
|
--callout-default: var(--blue_x);
|
||||||
|
--callout-info: var(--blue_x);
|
||||||
|
--callout-summary: var(--cyan_x);
|
||||||
|
--callout-tip: var(--cyan_x);
|
||||||
|
--callout-todo: var(--cyan_x);
|
||||||
|
--callout-bug: var(--red_x);
|
||||||
|
--callout-error: var(--red1_x);
|
||||||
|
--callout-fail: var(--red1_x);
|
||||||
|
--callout-example: var(--magenta_x);
|
||||||
|
--callout-important: var(--green_x);
|
||||||
|
--callout-success: var(--teal_x);
|
||||||
|
--callout-question: var(--yellow_x);
|
||||||
|
--callout-warning: var(--orange_x);
|
||||||
|
--callout-quote: var(--fg_dark_x);
|
||||||
|
--icon-color-hover: var(--blue);
|
||||||
|
--icon-color-focused: var(--magenta);
|
||||||
|
--icon-color-active: var(--magenta);
|
||||||
|
--nav-item-color-hover: var(--fg);
|
||||||
|
--nav-item-background-hover: var(--bg_highlight);
|
||||||
|
--nav-item-color-active: var(--red);
|
||||||
|
--nav-item-background-active: var(--bg_highlight);
|
||||||
|
--nav-file-tag: rgba(var(--yellow_x), 0.9);
|
||||||
|
--nav-indentation-guide-color: var(--bg_highlight);
|
||||||
|
--indentation-guide-color: var(--comment);
|
||||||
|
--indentation-guide-color-active: var(--comment);
|
||||||
|
--graph-line: var(--comment);
|
||||||
|
--graph-node: var(--fg);
|
||||||
|
--graph-node-tag: var(--orange);
|
||||||
|
--graph-node-attachment: var(--blue);
|
||||||
|
--tab-text-color-focused-active: rgba(var(--red_x), 0.8);
|
||||||
|
--tab-text-color-focused-active-current: var(--red);
|
||||||
|
--modal-border-color: var(--bg_highlight);
|
||||||
|
--prompt-border-color: var(--bg_highlight);
|
||||||
|
--slider-track-background: var(--bg_highlight);
|
||||||
|
--embed-background: var(--bg_dark);
|
||||||
|
--embed-padding: 1.5rem 1.5rem 0.5rem;
|
||||||
|
--canvas-color: var(--bg_highlight_x);
|
||||||
|
--toggle-thumb-color: var(--bg);
|
||||||
|
}
|
||||||
0
quartz/styles/themes/_fonts.scss
Normal file
0
quartz/styles/themes/_fonts.scss
Normal file
1468
quartz/styles/themes/_index.scss
Normal file
1468
quartz/styles/themes/_index.scss
Normal file
File diff suppressed because it is too large
Load Diff
169
quartz/styles/themes/_light.scss
Normal file
169
quartz/styles/themes/_light.scss
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
:root[saved-theme="light"] {
|
||||||
|
--accent-h: 202;
|
||||||
|
--accent-s: 86%;
|
||||||
|
--accent-l: 43%;
|
||||||
|
--bg_dark2_x: 188, 189, 194;
|
||||||
|
--bg_dark2: rgb(var(--bg_dark2_x));
|
||||||
|
--bg_dark_x: 203, 204, 209;
|
||||||
|
--bg_dark: rgb(var(--bg_dark_x));
|
||||||
|
--bg_x: 213, 214, 219;
|
||||||
|
--bg: rgb(var(--bg_x));
|
||||||
|
--bg_highlight_x: 220, 222, 226;
|
||||||
|
--bg_highlight: rgb(var(--bg_highlight_x));
|
||||||
|
--bg_highlight_dark_x: 195, 197, 201;
|
||||||
|
--bg_highlight_dark: rgb(var(--bg_highlight_dark_x));
|
||||||
|
--terminal_black_x: 15, 15, 20;
|
||||||
|
--terminal_black: rgb(var(--terminal_black_x));
|
||||||
|
--fg_x: 52, 59, 88;
|
||||||
|
--fg: rgb(var(--fg_x));
|
||||||
|
--fg_dark_x: 39, 46, 75;
|
||||||
|
--fg_dark: rgb(var(--fg_dark_x));
|
||||||
|
--comment_x: 150, 153, 163;
|
||||||
|
--comment: rgb(var(--comment_x));
|
||||||
|
--blue0_x: 39, 71, 125;
|
||||||
|
--blue0: rgb(var(--blue0_x));
|
||||||
|
--blue_x: 52, 84, 138;
|
||||||
|
--blue: rgb(var(--blue_x));
|
||||||
|
--cyan_x: 15, 75, 110;
|
||||||
|
--cyan: rgb(var(--cyan_x));
|
||||||
|
--magent_hsl: 261 24% 38%;
|
||||||
|
--magenta_x: 90, 74, 120;
|
||||||
|
--magenta: rgb(var(--magenta_x));
|
||||||
|
--orange_x: 150, 80, 39;
|
||||||
|
--orange: rgb(var(--orange_x));
|
||||||
|
--yellow_x: 143, 94, 21;
|
||||||
|
--yellow: rgb(var(--yellow_x));
|
||||||
|
--green_x: 51, 99, 92;
|
||||||
|
--green: rgb(var(--green_x));
|
||||||
|
--teal_x: 22, 103, 117;
|
||||||
|
--teal: rgb(var(--teal_x));
|
||||||
|
--red_x: 140, 67, 81;
|
||||||
|
--red: rgb(var(--red_x));
|
||||||
|
--red1_x: 115, 42, 56;
|
||||||
|
--red1: rgb(var(--red1_x));
|
||||||
|
--unknown: #000000;
|
||||||
|
--color_red_rgb: var(--red_x);
|
||||||
|
--color-red: var(--red);
|
||||||
|
--color_purple_rgb: var(--magenta_x);
|
||||||
|
--color-purple: var(--magenta);
|
||||||
|
--color_green_rgb: var(--green_x);
|
||||||
|
--color-green: var(--green);
|
||||||
|
--color_cyan_rgb: var(--cyan_x);
|
||||||
|
--color-cyan: var(--cyan);
|
||||||
|
--color_blue_rgb: var(--blue_x);
|
||||||
|
--color-blue: var(--blue);
|
||||||
|
--color_yellow_rgb: var(--yellow_x);
|
||||||
|
--color-yellow: var(--yellow);
|
||||||
|
--color_orange_rgb: var(--orange_x);
|
||||||
|
--color-orange: var(--orange);
|
||||||
|
--color_pink_rgb: var(--magenta_x);
|
||||||
|
--color-pink: var(--magenta);
|
||||||
|
--background-primary: var(--bg);
|
||||||
|
--background-primary-alt: var(--bg);
|
||||||
|
--background-secondary: var(--bg_dark);
|
||||||
|
--background-secondary-alt: var(--bg_dark);
|
||||||
|
--background-modifier-border: var(--bg_highlight);
|
||||||
|
--background-modifier-border-focus: var(--bg_highlight);
|
||||||
|
--background-modifier-border-hover: var(--bg_highlight);
|
||||||
|
--background-modifier-form-field: var(--bg_dark);
|
||||||
|
--background-modifier-form-field-highlighted: var(--bg_dark);
|
||||||
|
--background-modifier-box-shadow: rgba(0, 0, 0, 0.3);
|
||||||
|
--background-modifier-success: var(--green);
|
||||||
|
--background-modifier-error: var(--red1);
|
||||||
|
--background-modifier-error-hover: var(--red);
|
||||||
|
--background-modifier-cover: rgba(var(--bg_dark_x), 0.8);
|
||||||
|
--background-modifier-hover: var(--bg_highlight);
|
||||||
|
--background-modifier-message: rgba(var(--bg_highlight_x), 0.9);
|
||||||
|
--background-modifier-active-hover: var(--bg_highlight);
|
||||||
|
--text-normal: var(--fg);
|
||||||
|
--text-faint: var(--comment);
|
||||||
|
--text-muted: var(--fg_dark);
|
||||||
|
--text-error: var(--red);
|
||||||
|
--text-accent: var(--magenta);
|
||||||
|
--text-accent-hover: var(--cyan);
|
||||||
|
--text-error: var(--red1);
|
||||||
|
--text-error-hover: var(--red);
|
||||||
|
--text-selection: var(--unknown);
|
||||||
|
--text-on-accent: var(--bg);
|
||||||
|
--text-highlight-bg: rgba(var(--orange_x), 0.4);
|
||||||
|
--text-selection: rgba(var(--blue0_x), 0.6);
|
||||||
|
--bold-color: var(--cyan);
|
||||||
|
--italic-color: var(--cyan);
|
||||||
|
--interactive-normal: var(--bg_dark);
|
||||||
|
--interactive-hover: var(--bg);
|
||||||
|
--interactive-success: var(--green);
|
||||||
|
--interactive-accent: hsl(var(--accent-h), var(--accent-s), var(--accent-l));
|
||||||
|
--interactive-accent-hover: var(--blue);
|
||||||
|
--scrollbar-bg: var(--bg_dark2);
|
||||||
|
--scrollbar-thumb-bg: var(--comment);
|
||||||
|
--scrollbar-active-thumb-bg: var(--bg_dark);
|
||||||
|
--scrollbar-width: 0px;
|
||||||
|
--h1-color: var(--red);
|
||||||
|
--h2-color: var(--yellow);
|
||||||
|
--h3-color: var(--green);
|
||||||
|
--h4-color: var(--cyan);
|
||||||
|
--h5-color: var(--blue);
|
||||||
|
--h6-color: var(--magenta);
|
||||||
|
--border-width: 2px;
|
||||||
|
--tag-color: var(--magenta);
|
||||||
|
--tag-background: rgba(var(--magenta_x), 0.15);
|
||||||
|
--tag-color-hover: var(--cyan);
|
||||||
|
--tag-background-hover: rgba(var(--cyan_x), 0.15);
|
||||||
|
--link-color: var(--magenta);
|
||||||
|
--link-color-hover: var(--cyan);
|
||||||
|
--link-external-color: var(--magenta);
|
||||||
|
--link-external-color-hover: var(--cyan);
|
||||||
|
--checkbox-radius: var(--radius-l);
|
||||||
|
--checkbox-color: var(--green);
|
||||||
|
--checkbox-color-hover: var(--green);
|
||||||
|
--checkbox-marker-color: var(--bg);
|
||||||
|
--checkbox-border-color: var(--comment);
|
||||||
|
--checkbox-border-color-hover: var(--comment);
|
||||||
|
--table-header-background: var(--bg_dark2);
|
||||||
|
--table-header-background-hover: var(--bg_dark2);
|
||||||
|
--flashing-background: rgba(var(--blue0_x), 0.3);
|
||||||
|
--code-normal: var(--fg);
|
||||||
|
--code-background: var(--bg_highlight_dark);
|
||||||
|
--mermaid-note: var(--blue0);
|
||||||
|
--mermaid-actor: var(--fg_dark);
|
||||||
|
--mermaid-loopline: var(--blue);
|
||||||
|
--blockquote-background-color: var(--bg_dark);
|
||||||
|
--callout-default: var(--blue_x);
|
||||||
|
--callout-info: var(--blue_x);
|
||||||
|
--callout-summary: var(--cyan_x);
|
||||||
|
--callout-tip: var(--cyan_x);
|
||||||
|
--callout-todo: var(--cyan_x);
|
||||||
|
--callout-bug: var(--red_x);
|
||||||
|
--callout-error: var(--red1_x);
|
||||||
|
--callout-fail: var(--red1_x);
|
||||||
|
--callout-example: var(--magenta_x);
|
||||||
|
--callout-important: var(--green_x);
|
||||||
|
--callout-success: var(--teal_x);
|
||||||
|
--callout-question: var(--yellow_x);
|
||||||
|
--callout-warning: var(--orange_x);
|
||||||
|
--callout-quote: var(--fg_dark_x);
|
||||||
|
--icon-color-hover: var(--blue);
|
||||||
|
--icon-color-focused: var(--magenta);
|
||||||
|
--icon-color-active: var(--magenta);
|
||||||
|
--nav-item-color-hover: var(--fg);
|
||||||
|
--nav-item-background-hover: var(--bg_highlight);
|
||||||
|
--nav-item-color-active: var(--red);
|
||||||
|
--nav-item-background-active: var(--bg_highlight);
|
||||||
|
--nav-file-tag: rgba(var(--yellow_x), 0.9);
|
||||||
|
--nav-indentation-guide-color: var(--bg_highlight);
|
||||||
|
--indentation-guide-color: var(--comment);
|
||||||
|
--indentation-guide-color-active: var(--comment);
|
||||||
|
--graph-line: var(--comment);
|
||||||
|
--graph-node: var(--fg);
|
||||||
|
--graph-node-tag: var(--orange);
|
||||||
|
--graph-node-attachment: var(--blue);
|
||||||
|
--tab-text-color-focused-active: rgba(var(--red_x), 0.8);
|
||||||
|
--tab-text-color-focused-active-current: var(--red);
|
||||||
|
--modal-border-color: var(--bg_highlight);
|
||||||
|
--prompt-border-color: var(--bg_highlight);
|
||||||
|
--slider-track-background: var(--bg_highlight);
|
||||||
|
--embed-background: var(--bg_dark);
|
||||||
|
--embed-padding: 1.5rem 1.5rem 0.5rem;
|
||||||
|
--canvas-color: var(--bg_highlight_x);
|
||||||
|
--toggle-thumb-color: var(--bg);
|
||||||
|
}
|
||||||
0
quartz/styles/themes/overrides/_index.scss
Normal file
0
quartz/styles/themes/overrides/_index.scss
Normal file
@ -1,6 +1,7 @@
|
|||||||
import test, { describe, beforeEach } from "node:test"
|
import test, { describe, beforeEach } from "node:test"
|
||||||
import assert from "node:assert"
|
import assert from "node:assert"
|
||||||
import { FileTrieNode } from "./fileTrie"
|
import { FileTrieNode } from "./fileTrie"
|
||||||
|
import { FullSlug } from "./path"
|
||||||
|
|
||||||
interface TestData {
|
interface TestData {
|
||||||
title: string
|
title: string
|
||||||
@ -192,6 +193,94 @@ describe("FileTrie", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("fromEntries", () => {
|
||||||
|
test("nested", () => {
|
||||||
|
const trie = FileTrieNode.fromEntries([
|
||||||
|
["index" as FullSlug, { title: "Root", slug: "index", filePath: "index.md" }],
|
||||||
|
[
|
||||||
|
"folder/file1" as FullSlug,
|
||||||
|
{ title: "File 1", slug: "folder/file1", filePath: "folder/file1.md" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"folder/index" as FullSlug,
|
||||||
|
{ title: "Folder Index", slug: "folder/index", filePath: "folder/index.md" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"folder/file2" as FullSlug,
|
||||||
|
{ title: "File 2", slug: "folder/file2", filePath: "folder/file2.md" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"folder/folder2/index" as FullSlug,
|
||||||
|
{
|
||||||
|
title: "Subfolder Index",
|
||||||
|
slug: "folder/folder2/index",
|
||||||
|
filePath: "folder/folder2/index.md",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(trie.children.length, 1)
|
||||||
|
assert.strictEqual(trie.children[0].slug, "folder/index")
|
||||||
|
assert.strictEqual(trie.children[0].children.length, 3)
|
||||||
|
assert.strictEqual(trie.children[0].children[0].slug, "folder/file1")
|
||||||
|
assert.strictEqual(trie.children[0].children[1].slug, "folder/file2")
|
||||||
|
assert.strictEqual(trie.children[0].children[2].slug, "folder/folder2/index")
|
||||||
|
assert.strictEqual(trie.children[0].children[2].children.length, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("findNode", () => {
|
||||||
|
test("should find root node with empty path", () => {
|
||||||
|
const data = { title: "Root", slug: "index", filePath: "index.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const found = trie.findNode([])
|
||||||
|
assert.strictEqual(found, trie)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find node at first level", () => {
|
||||||
|
const data = { title: "Test", slug: "test", filePath: "test.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const found = trie.findNode(["test"])
|
||||||
|
assert.strictEqual(found?.data, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find nested node", () => {
|
||||||
|
const data = {
|
||||||
|
title: "Nested",
|
||||||
|
slug: "folder/subfolder/test",
|
||||||
|
filePath: "folder/subfolder/test.md",
|
||||||
|
}
|
||||||
|
trie.add(data)
|
||||||
|
const found = trie.findNode(["folder", "subfolder", "test"])
|
||||||
|
assert.strictEqual(found?.data, data)
|
||||||
|
|
||||||
|
// should find the folder and subfolder indexes too
|
||||||
|
assert.strictEqual(
|
||||||
|
trie.findNode(["folder", "subfolder", "index"]),
|
||||||
|
trie.children[0].children[0],
|
||||||
|
)
|
||||||
|
assert.strictEqual(trie.findNode(["folder", "index"]), trie.children[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return undefined for non-existent path", () => {
|
||||||
|
const data = { title: "Test", slug: "test", filePath: "test.md" }
|
||||||
|
trie.add(data)
|
||||||
|
const found = trie.findNode(["nonexistent"])
|
||||||
|
assert.strictEqual(found, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return undefined for partial path", () => {
|
||||||
|
const data = {
|
||||||
|
title: "Nested",
|
||||||
|
slug: "folder/subfolder/test",
|
||||||
|
filePath: "folder/subfolder/test.md",
|
||||||
|
}
|
||||||
|
trie.add(data)
|
||||||
|
const found = trie.findNode(["folder"])
|
||||||
|
assert.strictEqual(found?.data, null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("getFolderPaths", () => {
|
describe("getFolderPaths", () => {
|
||||||
test("should return all folder paths", () => {
|
test("should return all folder paths", () => {
|
||||||
const data1 = {
|
const data1 = {
|
||||||
|
|||||||
@ -89,6 +89,14 @@ export class FileTrieNode<T extends FileTrieData = ContentDetails> {
|
|||||||
this.insert(file.slug.split("/"), file)
|
this.insert(file.slug.split("/"), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findNode(path: string[]): FileTrieNode<T> | undefined {
|
||||||
|
if (path.length === 0 || (path.length === 1 && path[0] === "index")) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter trie nodes. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
* Filter trie nodes. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export class QuartzLogger {
|
|||||||
const truncated = truncate(output, columns)
|
const truncated = truncate(output, columns)
|
||||||
process.stdout.write(truncated)
|
process.stdout.write(truncated)
|
||||||
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
|
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
|
||||||
}, 20)
|
}, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import chalk from "chalk"
|
|||||||
|
|
||||||
const defaultHeaderWeight = [700]
|
const defaultHeaderWeight = [700]
|
||||||
const defaultBodyWeight = [400]
|
const defaultBodyWeight = [400]
|
||||||
|
|
||||||
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
|
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
|
||||||
// Get all weights for header and body fonts
|
// Get all weights for header and body fonts
|
||||||
const headerWeights: FontWeight[] = (
|
const headerWeights: FontWeight[] = (
|
||||||
@ -134,21 +135,12 @@ export type SocialImageOptions = {
|
|||||||
excludeRoot: boolean
|
excludeRoot: boolean
|
||||||
/**
|
/**
|
||||||
* JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori)
|
* JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori)
|
||||||
* @param cfg global quartz config
|
|
||||||
* @param userOpts options that can be set by user
|
|
||||||
* @param title title of current page
|
|
||||||
* @param description description of current page
|
|
||||||
* @param fonts global font that can be used for styling
|
|
||||||
* @param fileData full fileData of current page
|
|
||||||
* @returns prepared jsx to be used for generating image
|
|
||||||
*/
|
*/
|
||||||
imageStructure: (
|
imageStructure: (
|
||||||
cfg: GlobalConfiguration,
|
options: ImageOptions & {
|
||||||
userOpts: UserOpts,
|
userOpts: UserOpts
|
||||||
title: string,
|
iconBase64?: string
|
||||||
description: string,
|
},
|
||||||
fonts: SatoriOptions["fonts"],
|
|
||||||
fileData: QuartzPluginData,
|
|
||||||
) => JSXInternal.Element
|
) => JSXInternal.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,17 +170,17 @@ export type ImageOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is the default template for generated social image.
|
// This is the default template for generated social image.
|
||||||
export const defaultImage: SocialImageOptions["imageStructure"] = (
|
export const defaultImage: SocialImageOptions["imageStructure"] = ({
|
||||||
cfg: GlobalConfiguration,
|
cfg,
|
||||||
{ colorScheme }: UserOpts,
|
userOpts,
|
||||||
title: string,
|
title,
|
||||||
description: string,
|
description,
|
||||||
_fonts: SatoriOptions["fonts"],
|
fileData,
|
||||||
fileData: QuartzPluginData,
|
iconBase64,
|
||||||
) => {
|
}) => {
|
||||||
|
const { colorScheme } = userOpts
|
||||||
const fontBreakPoint = 32
|
const fontBreakPoint = 32
|
||||||
const useSmallerFont = title.length > fontBreakPoint
|
const useSmallerFont = title.length > fontBreakPoint
|
||||||
const iconPath = `https://${cfg.baseUrl}/static/icon.png`
|
|
||||||
|
|
||||||
// Format date if available
|
// Format date if available
|
||||||
const rawDate = getDate(cfg, fileData)
|
const rawDate = getDate(cfg, fileData)
|
||||||
@ -226,14 +218,16 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
marginBottom: "0.5rem",
|
marginBottom: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
{iconBase64 && (
|
||||||
src={iconPath}
|
<img
|
||||||
width={56}
|
src={iconBase64}
|
||||||
height={56}
|
width={56}
|
||||||
style={{
|
height={56}
|
||||||
borderRadius: "50%",
|
style={{
|
||||||
}}
|
borderRadius: "50%",
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
@ -247,7 +247,7 @@ export function transformLink(src: FullSlug, target: string, opts: TransformOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// path helpers
|
// path helpers
|
||||||
function isFolderPath(fplike: string): boolean {
|
export function isFolderPath(fplike: string): boolean {
|
||||||
return (
|
return (
|
||||||
fplike.endsWith("/") ||
|
fplike.endsWith("/") ||
|
||||||
endsWith(fplike, "index") ||
|
endsWith(fplike, "index") ||
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export type FontSpecification =
|
|||||||
|
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
typography: {
|
typography: {
|
||||||
|
title?: FontSpecification
|
||||||
header: FontSpecification
|
header: FontSpecification
|
||||||
body: FontSpecification
|
body: FontSpecification
|
||||||
code: FontSpecification
|
code: FontSpecification
|
||||||
@ -48,7 +49,10 @@ export function getFontSpecificationName(spec: FontSpecification): string {
|
|||||||
return spec.name
|
return spec.name
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatFontSpecification(type: "header" | "body" | "code", spec: FontSpecification) {
|
function formatFontSpecification(
|
||||||
|
type: "title" | "header" | "body" | "code",
|
||||||
|
spec: FontSpecification,
|
||||||
|
) {
|
||||||
if (typeof spec === "string") {
|
if (typeof spec === "string") {
|
||||||
spec = { name: spec }
|
spec = { name: spec }
|
||||||
}
|
}
|
||||||
@ -82,12 +86,19 @@ function formatFontSpecification(type: "header" | "body" | "code", spec: FontSpe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function googleFontHref(theme: Theme) {
|
export function googleFontHref(theme: Theme) {
|
||||||
const { code, header, body } = theme.typography
|
const { header, body, code } = theme.typography
|
||||||
const headerFont = formatFontSpecification("header", header)
|
const headerFont = formatFontSpecification("header", header)
|
||||||
const bodyFont = formatFontSpecification("body", body)
|
const bodyFont = formatFontSpecification("body", body)
|
||||||
const codeFont = formatFontSpecification("code", code)
|
const codeFont = formatFontSpecification("code", code)
|
||||||
|
|
||||||
return `https://fonts.googleapis.com/css2?family=${bodyFont}&family=${headerFont}&family=${codeFont}&display=swap`
|
return `https://fonts.googleapis.com/css2?family=${headerFont}&family=${bodyFont}&family=${codeFont}&display=swap`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function googleFontSubsetHref(theme: Theme, text: string) {
|
||||||
|
const title = theme.typography.title || theme.typography.header
|
||||||
|
const titleFont = formatFontSpecification("title", title)
|
||||||
|
|
||||||
|
return `https://fonts.googleapis.com/css2?family=${titleFont}&text=${encodeURIComponent(text)}&display=swap`
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GoogleFontFile {
|
export interface GoogleFontFile {
|
||||||
@ -135,6 +146,7 @@ ${stylesheet.join("\n\n")}
|
|||||||
--highlight: ${theme.colors.lightMode.highlight};
|
--highlight: ${theme.colors.lightMode.highlight};
|
||||||
--textHighlight: ${theme.colors.lightMode.textHighlight};
|
--textHighlight: ${theme.colors.lightMode.textHighlight};
|
||||||
|
|
||||||
|
--titleFont: "${getFontSpecificationName(theme.typography.title || theme.typography.header)}", ${DEFAULT_SANS_SERIF};
|
||||||
--headerFont: "${getFontSpecificationName(theme.typography.header)}", ${DEFAULT_SANS_SERIF};
|
--headerFont: "${getFontSpecificationName(theme.typography.header)}", ${DEFAULT_SANS_SERIF};
|
||||||
--bodyFont: "${getFontSpecificationName(theme.typography.body)}", ${DEFAULT_SANS_SERIF};
|
--bodyFont: "${getFontSpecificationName(theme.typography.body)}", ${DEFAULT_SANS_SERIF};
|
||||||
--codeFont: "${getFontSpecificationName(theme.typography.code)}", ${DEFAULT_MONO};
|
--codeFont: "${getFontSpecificationName(theme.typography.code)}", ${DEFAULT_MONO};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user