Compare commits

..

No commits in common. "34fde07cf8ee7c65dd48a35b35d3fb1c801e1634" and "688c5484a9d9146214d4259745b70dc0dd0fedc4" have entirely different histories.

50 changed files with 743 additions and 955 deletions

View File

@ -7,7 +7,6 @@ on:
push:
branches:
- v4
workflow_dispatch:
jobs:
build-and-test:
@ -19,17 +18,17 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: 18
- name: Cache dependencies
uses: actions/cache@v4
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@ -48,22 +47,22 @@ jobs:
run: npx quartz build --bundleInfo
publish-tag:
if: ${{ github.repository == 'jackyzha0/quartz' && github.ref == 'refs/heads/v4' }}
if: ${{ github.repository == 'jackyzha0/quartz' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: 18
- name: Get package version
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
- name: Create release tag
uses: pkgdeps/git-tag-action@v3
uses: pkgdeps/git-tag-action@v2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_repo: ${{ github.repository }}

View File

@ -129,11 +129,11 @@ export default (() => {
return <button id="btn">Click me</button>
}
YourComponent.beforeDOMLoaded = `
YourComponent.beforeDOM = `
console.log("hello from before the page loads!")
`
YourComponent.afterDOMLoaded = `
YourComponent.afterDOM = `
document.getElementById('btn').onclick = () => {
alert('button clicked!')
}
@ -180,7 +180,7 @@ export default (() => {
return <button id="btn">Click me</button>
}
YourComponent.afterDOMLoaded = script
YourComponent.afterDOM = script
return YourComponent
}) satisfies QuartzComponentConstructor
```

View File

@ -260,11 +260,11 @@ export const ContentPage: QuartzEmitterPlugin = () => {
...defaultContentPageLayout,
pageBody: Content(),
}
const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = layout
const { head, header, beforeBody, pageBody, left, right, footer } = layout
return {
name: "ContentPage",
getQuartzComponents() {
return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer]
return [head, ...header, ...beforeBody, pageBody, ...left, ...right, footer]
},
async emit(ctx, content, resources, emit): Promise<FilePath[]> {
const cfg = ctx.cfg.configuration

View File

@ -31,7 +31,6 @@ This part of the configuration concerns anything that can affect the whole site.
- `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com);
- `{ provider: 'posthog', apiKey: '<your-posthog-project-apiKey>', host: '<your-posthog-host>' }`: use [Posthog](https://posthog.com/);
- `{ provider: 'tinylytics', siteId: '<your-site-id>' }`: use [Tinylytics](https://tinylytics.app/);
- `{ provider: 'cabin' }` or `{ provider: 'cabin', host: 'https://cabin.example.com' }` (custom domain): use [Cabin](https://withcabin.com);
- `locale`: used for [[i18n]] and date formatting
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
@ -53,7 +52,6 @@ This part of the configuration concerns anything that can affect the whole site.
- `secondary`: link colour, current [[graph view|graph]] node
- `tertiary`: hover states and visited [[graph view|graph]] nodes
- `highlight`: internal link background, highlighted text, [[syntax highlighting|highlighted lines of code]]
- `textHighlight`: markdown highlighted text background
## Plugins

View File

@ -1,83 +0,0 @@
---
title: Comments
tags:
- component
---
Quartz also has the ability to hook into various providers to enable readers to leave comments on your site.
![[giscus-example.png]]
As of today, only [Giscus](https://giscus.app/) is supported out of the box but PRs to support other providers are welcome!
## Providers
### Giscus
First, make sure that the [[setting up your GitHub repository|GitHub]] repository you are using for your Quartz meets the following requirements:
1. The **repository is [public](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/setting-repository-visibility#making-a-repository-public)**, otherwise visitors will not be able to view the discussion.
2. The **[giscus](https://github.com/apps/giscus) app is installed**, otherwise visitors will not be able to comment and react.
3. The **Discussions feature is turned on** by [enabling it for your repository](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/enabling-or-disabling-github-discussions-for-a-repository).
Then, use the [Giscus site](https://giscus.app/#repository) to figure out what your `repoId` and `categoryId` should be. Make sure you select `Announcements` for the Discussion category.
![[giscus-repo.png]]
![[giscus-discussion.png]]
After entering both your repository and selecting the discussion category, Giscus will compute some IDs that you'll need to provide back to Quartz. You won't need to manually add the script yourself as Quartz will handle that part for you but will need these values in the next step!
![[giscus-results.png]]
Finally, in `quartz.layout.ts`, edit the `afterBody` field of `sharedPageComponents` to include the following options but with the values you got from above:
```ts title="quartz.layout.ts"
afterBody: [
Component.Comments({
provider: 'giscus',
options: {
// from data-repo
repo: 'jackyzha0/quartz',
// from data-repo-id
repoId: 'MDEwOlJlcG9zaXRvcnkzODcyMTMyMDg',
// from data-category
category: 'Announcements',
// from data-category-id
categoryId: 'DIC_kwDOFxRnmM4B-Xg6',
}
}),
],
```
### Customization
Quartz also exposes a few of the other Giscus options as well and you can provide them the same way `repo`, `repoId`, `category`, and `categoryId` are provided.
```ts
type Options = {
provider: "giscus"
options: {
repo: `${string}/${string}`
repoId: string
category: string
categoryId: string
// how to map pages -> discussions
// defaults to 'url'
mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname"
// use strict title matching
// defaults to true
strict?: boolean
// whether to enable reactions for the main post
// defaults to true
reactionsEnabled?: boolean
// where to put the comment input box relative to the comments
// defaults to 'bottom'
inputPosition?: "top" | "bottom"
}
}
```

View File

@ -30,4 +30,4 @@ As with folder listings, you can also provide a description and title for a tag
## Customization
Quartz allows you to define a custom sort ordering for content on both page types. The folder listings are a functionality of the [[FolderPage]] plugin, the tag listings of the [[TagPage]] plugin. See the plugin pages for customization options.
The folder listings are a functionality of the [[FolderPage]] plugin, the tag listings of the [[TagPage]] plugin. See the plugin pages for customization options.

View File

@ -2,12 +2,22 @@
draft: true
---
## misc backlog
## high priority backlog
- static dead link detection
- block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note
- note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files
- docker support
## misc backlog
- breadcrumbs component
- cursor chat extension
- https://giscus.app/ extension
- sidenotes? https://github.com/capnfabs/paperesque
- direct match in search using double quotes
- https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI
- audio/video embed styling
- Canvas
- parse all images in page: use this for page lists if applicable?
- CV mode? with print stylesheet

View File

@ -57,16 +57,18 @@ jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all history for git info
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: 18.14
- name: Install Dependencies
run: npm ci
- name: Build Quartz
run: npx quartz build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v2
with:
path: public
@ -79,7 +81,7 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v2
```
Then:
@ -180,31 +182,35 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
## GitLab Pages
In your local Quartz, create a new file `.gitlab-ci.yml`.
In your local Quartz, create a new file `.gitlab-ci.yaml`.
```yaml title=".gitlab-ci.yml"
```yaml title=".gitlab-ci.yaml"
stages:
- build
- deploy
image: node:18
cache: # Cache modules in between jobs
key: $CI_COMMIT_REF_SLUG
paths:
- .npm/
variables:
NODE_VERSION: "18.14"
build:
stage: build
rules:
- if: '$CI_COMMIT_REF_NAME == "v4"'
before_script:
- apt-get update -q && apt-get install -y nodejs npm
- npm install -g n
- n $NODE_VERSION
- hash -r
- npm ci --cache .npm --prefer-offline
- npm ci
script:
- npx quartz build
artifacts:
paths:
- public
cache:
paths:
- ~/.npm/
key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}"
tags:
- docker

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -31,7 +31,7 @@ If you prefer instructions in a video format you can try following Nicole van de
## 🔧 Features
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[comments]] and [many more](./features) right out of the box
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]] and [many more](./features) right out of the box
- Hot-reload for both configuration and content
- Simple JSX layouts and [[creating components|page components]]
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes

View File

@ -12,7 +12,6 @@ export interface FullPageLayout {
header: QuartzComponent[] // laid out horizontally
beforeBody: QuartzComponent[] // laid out vertically
pageBody: QuartzComponent // single component
afterBody: QuartzComponent[] // laid out vertically
left: QuartzComponent[] // vertical on desktop, horizontal on mobile
right: QuartzComponent[] // vertical on desktop, horizontal on mobile
footer: QuartzComponent // single component

View File

@ -11,7 +11,7 @@ This plugin determines the created, modified, and published dates for a document
This plugin accepts the following configuration options:
- `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `["frontmatter", "git", "filesystem"]`.
- `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `"frontmatter", "git", "filesystem"]`.
> [!warning]
> If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`.

View File

@ -11,12 +11,10 @@ Example: [[advanced/|Advanced]]
> [!note]
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `FolderContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/FolderContent.tsx`).
This plugin accepts the following configuration options:
- `sort`: A function of type `(f1: QuartzPluginData, f2: QuartzPluginData) => number{:ts}` used to sort entries. Defaults to sorting by date and tie-breaking on lexographical order.
## API
- Category: Emitter

View File

@ -9,12 +9,10 @@ This plugin emits dedicated pages for each tag used in the content. See [[folder
> [!note]
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `TagContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/TagContent.tsx`).
This plugin accepts the following configuration options:
- `sort`: A function of type `(f1: QuartzPluginData, f2: QuartzPluginData) => number{:ts}` used to sort entries. Defaults to sorting by date and tie-breaking on lexographical order.
## API
- Category: Emitter

764
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.2.4",
"version": "4.2.3",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
@ -36,7 +36,7 @@
},
"dependencies": {
"@clack/prompts": "^0.7.0",
"@floating-ui/dom": "^1.6.8",
"@floating-ui/dom": "^1.6.3",
"@napi-rs/simple-git": "0.1.16",
"async-mutex": "^0.5.0",
"chalk": "^5.3.0",
@ -53,13 +53,13 @@
"hast-util-to-string": "^3.0.0",
"is-absolute-url": "^4.0.1",
"js-yaml": "^4.1.0",
"lightningcss": "^1.25.1",
"lightningcss": "^1.24.1",
"mdast-util-find-and-replace": "^3.0.1",
"mdast-util-to-hast": "^13.2.0",
"mdast-util-to-hast": "^13.1.0",
"mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5",
"preact": "^10.22.1",
"preact-render-to-string": "^6.5.5",
"preact": "^10.20.1",
"preact-render-to-string": "^6.4.2",
"pretty-bytes": "^6.1.1",
"pretty-time": "^1.1.0",
"reading-time": "^1.5.0",
@ -77,19 +77,19 @@
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"remark-smartypants": "^3.0.2",
"rfdc": "^1.4.1",
"remark-smartypants": "^2.1.0",
"rfdc": "^1.3.1",
"rimraf": "^5.0.7",
"serve-handler": "^6.1.5",
"shiki": "^1.10.3",
"shiki": "^1.6.0",
"source-map-support": "^0.5.21",
"to-vfile": "^8.0.0",
"toml": "^3.0.0",
"unified": "^11.0.4",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.1",
"workerpool": "^9.1.2",
"ws": "^8.18.0",
"workerpool": "^9.1.1",
"ws": "^8.17.0",
"yargs": "^17.7.2"
},
"devDependencies": {
@ -97,14 +97,14 @@
"@types/d3": "^7.4.3",
"@types/hast": "^3.0.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.14.11",
"@types/node": "^20.12.5",
"@types/pretty-time": "^1.1.5",
"@types/source-map-support": "^0.5.10",
"@types/ws": "^8.5.11",
"@types/ws": "^8.5.10",
"@types/yargs": "^17.0.32",
"esbuild": "^0.19.9",
"prettier": "^3.3.2",
"tsx": "^4.16.2",
"typescript": "^5.5.3"
"prettier": "^3.2.4",
"tsx": "^4.11.2",
"typescript": "^5.4.5"
}
}

View File

@ -36,7 +36,6 @@ const config: QuartzConfig = {
secondary: "#284b63",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#fff23688",
},
darkMode: {
light: "#161618",
@ -47,7 +46,6 @@ const config: QuartzConfig = {
secondary: "#7b97aa",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#b3aa0288",
},
},
},

View File

@ -5,7 +5,6 @@ import * as Component from "./quartz/components"
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
afterBody: [],
footer: Component.Footer({
links: {
GitHub: "https://github.com/jackyzha0/quartz",

View File

@ -34,10 +34,6 @@ export type Analytics =
provider: "tinylytics"
siteId: string
}
| {
provider: "cabin"
host?: string
}
export interface GlobalConfiguration {
pageTitle: string
@ -77,11 +73,10 @@ export interface FullPageLayout {
header: QuartzComponent[]
beforeBody: QuartzComponent[]
pageBody: QuartzComponent
afterBody: QuartzComponent[]
left: QuartzComponent[]
right: QuartzComponent[]
footer: QuartzComponent
}
export type PageLayout = Pick<FullPageLayout, "beforeBody" | "left" | "right">
export type SharedLayout = Pick<FullPageLayout, "head" | "header" | "footer" | "afterBody">
export type SharedLayout = Pick<FullPageLayout, "head" | "header" | "footer">

View File

@ -1,66 +0,0 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
type Options = {
provider: "giscus"
options: {
repo: `${string}/${string}`
repoId: string
category: string
categoryId: string
mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname"
strict?: boolean
reactionsEnabled?: boolean
inputPosition?: "top" | "bottom"
}
}
function boolToStringBool(b: boolean): string {
return b ? "1" : "0"
}
export default ((opts: Options) => {
const Comments: QuartzComponent = (_props: QuartzComponentProps) => <div class="giscus"></div>
Comments.afterDOMLoaded = `
const giscusScript = document.createElement("script")
giscusScript.src = "https://giscus.app/client.js"
giscusScript.async = true
giscusScript.crossOrigin = "anonymous"
giscusScript.setAttribute("data-loading", "lazy")
giscusScript.setAttribute("data-emit-metadata", "0")
giscusScript.setAttribute("data-repo", "${opts.options.repo}")
giscusScript.setAttribute("data-repo-id", "${opts.options.repoId}")
giscusScript.setAttribute("data-category", "${opts.options.category}")
giscusScript.setAttribute("data-category-id", "${opts.options.categoryId}")
giscusScript.setAttribute("data-mapping", "${opts.options.mapping ?? "url"}")
giscusScript.setAttribute("data-strict", "${boolToStringBool(opts.options.strict ?? true)}")
giscusScript.setAttribute("data-reactions-enabled", "${boolToStringBool(opts.options.reactionsEnabled ?? true)}")
giscusScript.setAttribute("data-input-position", "${opts.options.inputPosition ?? "bottom"}")
const theme = document.documentElement.getAttribute("saved-theme")
giscusScript.setAttribute("data-theme", theme)
document.head.appendChild(giscusScript)
const changeTheme = (e) => {
const theme = e.detail.theme
const iframe = document.querySelector('iframe.giscus-frame')
if (!iframe) {
return
}
iframe.contentWindow.postMessage({
giscus: {
setConfig: {
theme: theme
}
}
}, 'https://giscus.app')
}
document.addEventListener("nav", () => {
document.addEventListener("themechange", changeTheme)
window.addCleanup(() => document.removeEventListener("themechange", changeTheme))
})
`
return Comments
}) satisfies QuartzComponentConstructor<Options>

View File

@ -168,8 +168,10 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
const isDefaultOpen = opts.folderDefaultState === "open"
// Calculate current folderPath
const folderPath = node.name !== "" ? joinSegments(fullPath ?? "", node.name) : ""
const href = resolveRelative(fileData.slug!, folderPath as SimpleSlug) + "/"
let folderPath = ""
if (node.name !== "") {
folderPath = joinSegments(fullPath ?? "", node.name)
}
return (
<>
@ -203,7 +205,11 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
<div key={node.name} data-folderpath={folderPath}>
{folderBehavior === "link" ? (
<a href={href} data-for={node.name} class="folder-title">
<a
href={resolveRelative(fileData.slug!, folderPath as SimpleSlug)}
data-for={node.name}
class="folder-title"
>
{node.displayName}
</a>
) : (

View File

@ -13,6 +13,7 @@ export default ((opts?: Options) => {
const links = opts?.links ?? []
return (
<footer class={`${displayClass ?? ""}`}>
<hr />
<p>
{i18n(cfg.locale).components.footer.createdWith}{" "}
<a href="https://quartz.jzhao.xyz/">Quartz v{version}</a> © {year}

View File

@ -4,9 +4,9 @@ import { Date, getDate } from "./Date"
import { QuartzComponent, QuartzComponentProps } from "./types"
import { GlobalConfiguration } from "../cfg"
export type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number
export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn {
export function byDateAndAlphabetical(
cfg: GlobalConfiguration,
): (f1: QuartzPluginData, f2: QuartzPluginData) => number {
return (f1, f2) => {
if (f1.dates && f2.dates) {
// sort descending
@ -27,12 +27,10 @@ export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn {
type Props = {
limit?: number
sort?: SortFn
} & QuartzComponentProps
export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => {
const sorter = sort ?? byDateAndAlphabetical(cfg)
let list = allFiles.sort(sorter)
export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Props) => {
let list = allFiles.sort(byDateAndAlphabetical(cfg))
if (limit) {
list = list.slice(0, limit)
}

View File

@ -19,7 +19,6 @@ import DesktopOnly from "./DesktopOnly"
import MobileOnly from "./MobileOnly"
import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs"
import Comments from "./Comments"
export {
ArticleTitle,
@ -43,5 +42,4 @@ export {
RecentNotes,
NotFound,
Breadcrumbs,
Comments,
}

View File

@ -2,7 +2,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro
import path from "path"
import style from "../styles/listPage.scss"
import { PageList, SortFn } from "../PageList"
import { PageList } from "../PageList"
import { stripSlashes, simplifySlug } from "../../util/path"
import { Root } from "hast"
import { htmlToJsx } from "../../util/jsx"
@ -13,7 +13,6 @@ interface FolderContentOptions {
* Whether to display number of folders
*/
showFolderCount: boolean
sort?: SortFn
}
const defaultOptions: FolderContentOptions = {
@ -38,7 +37,6 @@ export default ((opts?: Partial<FolderContentOptions>) => {
const classes = ["popover-hint", ...cssClasses].join(" ")
const listProps = {
...props,
sort: options.sort,
allFiles: allPagesInFolder,
}

View File

@ -1,127 +1,113 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
import style from "../styles/listPage.scss"
import { PageList, SortFn } from "../PageList"
import { PageList } from "../PageList"
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
import { QuartzPluginData } from "../../plugins/vfile"
import { Root } from "hast"
import { htmlToJsx } from "../../util/jsx"
import { i18n } from "../../i18n"
interface TagContentOptions {
sort?: SortFn
numPages: number
}
const numPages = 10
const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
const { tree, fileData, allFiles, cfg } = props
const slug = fileData.slug
const defaultOptions: TagContentOptions = {
numPages: 10,
}
export default ((opts?: Partial<TagContentOptions>) => {
const options: TagContentOptions = { ...defaultOptions, ...opts }
const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
const { tree, fileData, allFiles, cfg } = props
const slug = fileData.slug
if (!(slug?.startsWith("tags/") || slug === "tags")) {
throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
}
const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
const allPagesWithTag = (tag: string) =>
allFiles.filter((file) =>
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
)
const content =
(tree as Root).children.length === 0
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
if (tag === "/") {
const tags = [
...new Set(
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
),
].sort((a, b) => a.localeCompare(b))
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
for (const tag of tags) {
tagItemMap.set(tag, allPagesWithTag(tag))
}
return (
<div class={classes}>
<article>
<p>{content}</p>
</article>
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
<div>
{tags.map((tag) => {
const pages = tagItemMap.get(tag)!
const listProps = {
...props,
allFiles: pages,
}
const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0)
const root = contentPage?.htmlAst
const content =
!root || root?.children.length === 0
? contentPage?.description
: htmlToJsx(contentPage.filePath!, root)
return (
<div>
<h2>
<a class="internal tag-link" href={`../tags/${tag}`}>
{tag}
</a>
</h2>
{content && <p>{content}</p>}
<div class="page-listing">
<p>
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
{pages.length > options.numPages && (
<>
{" "}
<span>
{i18n(cfg.locale).pages.tagContent.showingFirst({
count: options.numPages,
})}
</span>
</>
)}
</p>
<PageList limit={options.numPages} {...listProps} sort={opts?.sort} />
</div>
</div>
)
})}
</div>
</div>
)
} else {
const pages = allPagesWithTag(tag)
const listProps = {
...props,
allFiles: pages,
}
return (
<div class={classes}>
<article>{content}</article>
<div class="page-listing">
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
}
if (!(slug?.startsWith("tags/") || slug === "tags")) {
throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
}
TagContent.css = style + PageList.css
return TagContent
}) satisfies QuartzComponentConstructor
const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
const allPagesWithTag = (tag: string) =>
allFiles.filter((file) =>
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
)
const content =
(tree as Root).children.length === 0
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
if (tag === "/") {
const tags = [
...new Set(
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
),
].sort((a, b) => a.localeCompare(b))
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
for (const tag of tags) {
tagItemMap.set(tag, allPagesWithTag(tag))
}
return (
<div class={classes}>
<article>
<p>{content}</p>
</article>
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
<div>
{tags.map((tag) => {
const pages = tagItemMap.get(tag)!
const listProps = {
...props,
allFiles: pages,
}
const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0)
const root = contentPage?.htmlAst
const content =
!root || root?.children.length === 0
? contentPage?.description
: htmlToJsx(contentPage.filePath!, root)
return (
<div>
<h2>
<a class="internal tag-link" href={`../tags/${tag}`}>
{tag}
</a>
</h2>
{content && <p>{content}</p>}
<div class="page-listing">
<p>
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
{pages.length > numPages && (
<>
{" "}
<span>
{i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })}
</span>
</>
)}
</p>
<PageList limit={numPages} {...listProps} />
</div>
</div>
)
})}
</div>
</div>
)
} else {
const pages = allPagesWithTag(tag)
const listProps = {
...props,
allFiles: pages,
}
return (
<div class={classes}>
<article>{content}</article>
<div class="page-listing">
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
}
}
TagContent.css = style + PageList.css
export default (() => TagContent) satisfies QuartzComponentConstructor

View File

@ -14,7 +14,6 @@ interface RenderComponents {
header: QuartzComponent[]
beforeBody: QuartzComponent[]
pageBody: QuartzComponent
afterBody: QuartzComponent[]
left: QuartzComponent[]
right: QuartzComponent[]
footer: QuartzComponent
@ -188,7 +187,6 @@ export function renderPage(
header,
beforeBody,
pageBody: Content,
afterBody,
left,
right,
footer: Footer,
@ -234,12 +232,6 @@ export function renderPage(
</div>
</div>
<Content {...componentData} />
<hr />
<div class="page-footer">
{afterBody.map((BodyComponent) => (
<BodyComponent {...componentData} />
))}
</div>
</div>
{RightComponent}
</Body>

View File

@ -361,7 +361,7 @@ function renderGlobalGraph() {
document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const slug = e.detail.url
addToVisited(simplifySlug(slug))
addToVisited(slug)
await renderGraph("graph-container", slug)
const containerIcon = document.getElementById("global-graph-icon")

View File

@ -3,7 +3,7 @@ import { normalizeRelativeURLs } from "../../util/path"
const p = new DOMParser()
async function mouseEnterHandler(
this: HTMLAnchorElement,
this: HTMLLinkElement,
{ clientX, clientY }: { clientX: number; clientY: number },
) {
const link = this
@ -33,7 +33,7 @@ async function mouseEnterHandler(
thisUrl.hash = ""
thisUrl.search = ""
const targetUrl = new URL(link.href)
const hash = decodeURIComponent(targetUrl.hash)
const hash = targetUrl.hash
targetUrl.hash = ""
targetUrl.search = ""
@ -100,7 +100,7 @@ async function mouseEnterHandler(
}
document.addEventListener("nav", () => {
const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[]
const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[]
for (const link of links) {
link.addEventListener("mouseenter", mouseEnterHandler)
window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler))

View File

@ -11,7 +11,7 @@ li.section-li {
& > .section {
display: grid;
grid-template-columns: fit-content(8em) 3fr 1fr;
grid-template-columns: 6em 3fr 1fr;
@media all and (max-width: $mobileBreakpoint) {
& > .tags {
@ -24,7 +24,8 @@ li.section-li {
}
& > .meta {
margin: 0 1em 0 0;
margin: 0;
flex-basis: 6em;
opacity: 0.6;
}
}
@ -32,8 +33,7 @@ li.section-li {
// modifications in popover context
.popover .section {
grid-template-columns: fit-content(8em) 1fr !important;
grid-template-columns: 6em 1fr !important;
& > .tags {
display: none;
}

View File

@ -1,13 +1,11 @@
import { Translation, CalloutTranslation } from "./locales/definition"
import enUs from "./locales/en-US"
import enGb from "./locales/en-GB"
import en from "./locales/en-US"
import fr from "./locales/fr-FR"
import it from "./locales/it-IT"
import ja from "./locales/ja-JP"
import de from "./locales/de-DE"
import nl from "./locales/nl-NL"
import ro from "./locales/ro-RO"
import ca from "./locales/ca-ES"
import es from "./locales/es-ES"
import ar from "./locales/ar-SA"
import uk from "./locales/uk-UA"
@ -21,8 +19,7 @@ import fa from "./locales/fa-IR"
import pl from "./locales/pl-PL"
export const TRANSLATIONS = {
"en-US": enUs,
"en-GB": enGb,
"en-US": en,
"fr-FR": fr,
"it-IT": it,
"ja-JP": ja,
@ -31,7 +28,6 @@ export const TRANSLATIONS = {
"nl-BE": nl,
"ro-RO": ro,
"ro-MD": ro,
"ca-ES": ca,
"es-ES": es,
"ar-SA": ar,
"ar-AE": ar,

View File

@ -1,84 +0,0 @@
import { Translation } from "./definition"
export default {
propertyDefaults: {
title: "Sense títol",
description: "Sense descripció",
},
components: {
callout: {
note: "Nota",
abstract: "Resum",
info: "Informació",
todo: "Per fer",
tip: "Consell",
success: "Èxit",
question: "Pregunta",
warning: "Advertència",
failure: "Fall",
danger: "Perill",
bug: "Error",
example: "Exemple",
quote: "Cita",
},
backlinks: {
title: "Retroenllaç",
noBacklinksFound: "No s'han trobat retroenllaços",
},
themeToggle: {
lightMode: "Mode clar",
darkMode: "Mode fosc",
},
explorer: {
title: "Explorador",
},
footer: {
createdWith: "Creat amb",
},
graph: {
title: "Vista Gràfica",
},
recentNotes: {
title: "Notes Recents",
seeRemainingMore: ({ remaining }) => `Vegi ${remaining} més →`,
},
transcludes: {
transcludeOf: ({ targetSlug }) => `Transcluit de ${targetSlug}`,
linkToOriginal: "Enllaç a l'original",
},
search: {
title: "Cercar",
searchBarPlaceholder: "Cerca alguna cosa",
},
tableOfContents: {
title: "Taula de Continguts",
},
contentMeta: {
readingTime: ({ minutes }) => `Es llegeix en ${minutes} min`,
},
},
pages: {
rss: {
recentNotes: "Notes recents",
lastFewNotes: ({ count }) => `Últimes ${count} notes`,
},
error: {
title: "No s'ha trobat.",
notFound: "Aquesta pàgina és privada o no existeix.",
home: "Torna a la pàgina principal",
},
folderContent: {
folder: "Carpeta",
itemsUnderFolder: ({ count }) =>
count === 1 ? "1 article en aquesta carpeta." : `${count} articles en esta carpeta.`,
},
tagContent: {
tag: "Etiqueta",
tagIndex: "índex d'Etiquetes",
itemsUnderTag: ({ count }) =>
count === 1 ? "1 article amb aquesta etiqueta." : `${count} article amb aquesta etiqueta.`,
showingFirst: ({ count }) => `Mostrant les primeres ${count} etiquetes.`,
totalTags: ({ count }) => `S'han trobat ${count} etiquetes en total.`,
},
},
} as const satisfies Translation

View File

@ -1,84 +0,0 @@
import { Translation } from "./definition"
export default {
propertyDefaults: {
title: "Untitled",
description: "No description provided",
},
components: {
callout: {
note: "Note",
abstract: "Abstract",
info: "Info",
todo: "To-Do",
tip: "Tip",
success: "Success",
question: "Question",
warning: "Warning",
failure: "Failure",
danger: "Danger",
bug: "Bug",
example: "Example",
quote: "Quote",
},
backlinks: {
title: "Backlinks",
noBacklinksFound: "No backlinks found",
},
themeToggle: {
lightMode: "Light mode",
darkMode: "Dark mode",
},
explorer: {
title: "Explorer",
},
footer: {
createdWith: "Created with",
},
graph: {
title: "Graph View",
},
recentNotes: {
title: "Recent Notes",
seeRemainingMore: ({ remaining }) => `See ${remaining} more →`,
},
transcludes: {
transcludeOf: ({ targetSlug }) => `Transclude of ${targetSlug}`,
linkToOriginal: "Link to original",
},
search: {
title: "Search",
searchBarPlaceholder: "Search for something",
},
tableOfContents: {
title: "Table of Contents",
},
contentMeta: {
readingTime: ({ minutes }) => `${minutes} min read`,
},
},
pages: {
rss: {
recentNotes: "Recent notes",
lastFewNotes: ({ count }) => `Last ${count} notes`,
},
error: {
title: "Not Found",
notFound: "Either this page is private or doesn't exist.",
home: "Return to Homepage",
},
folderContent: {
folder: "Folder",
itemsUnderFolder: ({ count }) =>
count === 1 ? "1 item under this folder." : `${count} items under this folder.`,
},
tagContent: {
tag: "Tag",
tagIndex: "Tag Index",
itemsUnderTag: ({ count }) =>
count === 1 ? "1 item with this tag." : `${count} items with this tag.`,
showingFirst: ({ count }) => `Showing first ${count} tags.`,
totalTags: ({ count }) => `Found ${count} total tags.`,
},
},
} as const satisfies Translation

View File

@ -22,8 +22,8 @@ export default {
quote: "Cita",
},
backlinks: {
title: "Retroenlaces",
noBacklinksFound: "No se han encontrado retroenlaces",
title: "Enlaces de Retroceso",
noBacklinksFound: "No se han encontrado enlaces traseros",
},
themeToggle: {
lightMode: "Modo claro",
@ -54,18 +54,18 @@ export default {
title: "Tabla de Contenidos",
},
contentMeta: {
readingTime: ({ minutes }) => `Se lee en ${minutes} min`,
readingTime: ({ minutes }) => `${minutes} min read`,
},
},
pages: {
rss: {
recentNotes: "Notas recientes",
lastFewNotes: ({ count }) => `Últimas ${count} notas`,
lastFewNotes: ({ count }) => `Últimás ${count} notas`,
},
error: {
title: "No se ha encontrado.",
title: "No se encontró.",
notFound: "Esta página es privada o no existe.",
home: "Regresa a la página principal",
home: "Regresar a la página principal",
},
folderContent: {
folder: "Carpeta",
@ -78,7 +78,7 @@ export default {
itemsUnderTag: ({ count }) =>
count === 1 ? "1 artículo con esta etiqueta." : `${count} artículos con esta etiqueta.`,
showingFirst: ({ count }) => `Mostrando las primeras ${count} etiquetas.`,
totalTags: ({ count }) => `Se han encontrado ${count} etiquetas en total.`,
totalTags: ({ count }) => `Se encontraron ${count} etiquetas en total.`,
},
},
} as const satisfies Translation

View File

@ -54,7 +54,7 @@ export default {
title: "Зміст",
},
contentMeta: {
readingTime: ({ minutes }) => `${minutes} хв читання`,
readingTime: ({ minutes }) => `${minutes} min read`,
},
},
pages: {
@ -68,17 +68,17 @@ export default {
home: "Повернутися на головну сторінку",
},
folderContent: {
folder: "Тека",
folder: "Папка",
itemsUnderFolder: ({ count }) =>
count === 1 ? "У цій теці 1 елемент." : `Елементів у цій теці: ${count}.`,
count === 1 ? "У цій папці 1 елемент." : `Елементів у цій папці: ${count}.`,
},
tagContent: {
tag: "Мітка",
tagIndex: "Індекс мітки",
tag: "Тег",
tagIndex: "Індекс тегу",
itemsUnderTag: ({ count }) =>
count === 1 ? "1 елемент з цією міткою." : `Елементів з цією міткою: ${count}.`,
showingFirst: ({ count }) => `Показ перших ${count} міток.`,
totalTags: ({ count }) => `Всього знайдено міток: ${count}.`,
count === 1 ? "1 елемент з цим тегом." : `Елементів з цим тегом: ${count}.`,
showingFirst: ({ count }) => `Показ перших ${count} тегів.`,
totalTags: ({ count }) => `Всього знайдено тегів: ${count}.`,
},
},
} as const satisfies Translation

View File

@ -144,14 +144,6 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
tinylyticsScript.defer = true
document.head.appendChild(tinylyticsScript)
`)
} else if (cfg.analytics?.provider === "cabin") {
componentResources.afterDOMLoaded.push(`
const cabinScript = document.createElement("script")
cabinScript.src = "${cfg.analytics.host ?? "https://scripts.cabin.dev"}/cabin.js"
cabinScript.defer = true
cabinScript.async = true
document.head.appendChild(cabinScript)
`)
}
if (cfg.enableSPA) {

View File

@ -59,25 +59,14 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOp
...userOpts,
}
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
return {
name: "ContentPage",
getQuartzComponents() {
return [
Head,
Header,
Body,
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
]
return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer]
},
async getDependencyGraph(ctx, content, _resources) {
const graph = new DepGraph<FilePath>()

View File

@ -3,7 +3,7 @@ import { QuartzComponentProps } from "../../components/types"
import HeaderConstructor from "../../components/Header"
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile"
import { ProcessedContent, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
import path from "path"
import {
@ -21,37 +21,22 @@ import { write } from "./helpers"
import { i18n } from "../../i18n"
import DepGraph from "../../depgraph"
interface FolderPageOptions extends FullPageLayout {
sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
}
export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (userOpts) => {
export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => {
const opts: FullPageLayout = {
...sharedPageComponents,
...defaultListPageLayout,
pageBody: FolderContent({ sort: userOpts?.sort }),
pageBody: FolderContent(),
...userOpts,
}
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
return {
name: "FolderPage",
getQuartzComponents() {
return [
Head,
Header,
Body,
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
]
return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer]
},
async getDependencyGraph(_ctx, content, _resources) {
// Example graph:

View File

@ -3,7 +3,7 @@ import { QuartzComponentProps } from "../../components/types"
import HeaderConstructor from "../../components/Header"
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile"
import { ProcessedContent, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
import {
FilePath,
@ -18,37 +18,22 @@ import { write } from "./helpers"
import { i18n } from "../../i18n"
import DepGraph from "../../depgraph"
interface TagPageOptions extends FullPageLayout {
sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
}
export const TagPage: QuartzEmitterPlugin<Partial<TagPageOptions>> = (userOpts) => {
export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => {
const opts: FullPageLayout = {
...sharedPageComponents,
...defaultListPageLayout,
pageBody: TagContent({ sort: userOpts?.sort }),
pageBody: TagContent(),
...userOpts,
}
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const { head: Head, header, beforeBody, pageBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
return {
name: "TagPage",
getQuartzComponents() {
return [
Head,
Header,
Body,
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
]
return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer]
},
async getDependencyGraph(ctx, content, _resources) {
const graph = new DepGraph<FilePath>()

View File

@ -3,7 +3,7 @@ import { QuartzFilterPlugin } from "../types"
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
name: "RemoveDrafts",
shouldPublish(_ctx, [_tree, vfile]) {
const draftFlag: boolean = vfile.data?.frontmatter?.draft || false
const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false
return !draftFlag
},
})

View File

@ -93,7 +93,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
}
node.properties.className = classes
if (isExternal && opts.openLinksInNewTab) {
if (opts.openLinksInNewTab) {
node.properties.target = "_blank"
}

View File

@ -2,10 +2,10 @@ import { QuartzTransformerPlugin } from "../types"
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast"
import { Element, Literal, Root as HtmlRoot } from "hast"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import { slug as slugAnchor } from "github-slugger"
import rehypeRaw from "rehype-raw"
import { SKIP, visit } from "unist-util-visit"
import path from "path"
import { splitAnchor } from "../../util/path"
import { JSResource } from "../../util/resources"
// @ts-ignore
import calloutScript from "../../components/scripts/callout.inline.ts"
@ -97,7 +97,7 @@ function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping {
export const externalLinkRegex = /^https?:\/\//i
export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g)
export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/, "g")
// !? -> optional embedding
// \[\[ -> open brace
@ -105,30 +105,35 @@ export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g)
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
// (\\?\|[^\[\]\#]+)? -> optional escape \ then | then one or more non-special characters (alias)
export const wikilinkRegex = new RegExp(
/!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/g,
/!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/,
"g",
)
// ^\|([^\n])+\|\n(\|) -> matches the header row
// ( ?:?-{3,}:? ?\|)+ -> matches the header row separator
// (\|([^\n])+\|\n)+ -> matches the body rows
export const tableRegex = new RegExp(/^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\|([^\n])+\|\n?)+/gm)
export const tableRegex = new RegExp(
/^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\|([^\n])+\|\n?)+/,
"gm",
)
// matches any wikilink, only used for escaping wikilinks inside tables
export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/g)
export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/, "g")
const highlightRegex = new RegExp(/==([^=]+)==/g)
const commentRegex = new RegExp(/%%[\s\S]*?%%/g)
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
const commentRegex = new RegExp(/%%[\s\S]*?%%/, "g")
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
const calloutRegex = new RegExp(/^\[\!(\w+)\|?(.+?)?\]([+-]?)/)
const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/, "gm")
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
// #(...) -> capturing group, tag itself must start with #
// (?:[-_\p{L}\d\p{Z}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores
// (?:\/[-_\p{L}\d\p{Z}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
const tagRegex = new RegExp(
/(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu,
/(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/,
"gu",
)
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g)
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/, "g")
const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/
const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/)
@ -179,8 +184,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
// replace all wikilinks inside a table first
src = src.replace(tableRegex, (value) => {
// escape all aliases and headers in wikilinks inside a table
return value.replace(tableWikilinkRegex, (_value, raw) => {
// const [raw]: (string | undefined)[] = capture
return value.replace(tableWikilinkRegex, (value, ...capture) => {
const [raw]: (string | undefined)[] = capture
let escaped = raw ?? ""
escaped = escaped.replace("#", "\\#")
// escape pipe characters if they are not already escaped
@ -194,9 +199,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
src = src.replace(wikilinkRegex, (value, ...capture) => {
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`)
const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : ""
const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : ""
const fp = rawFp ?? ""
const anchor = rawHeader?.trim().replace(/^#+/, "")
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
const embedDisplay = value.startsWith("!") ? "!" : ""
@ -270,7 +276,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
return {
type: "html",
data: { hProperties: { transclude: true } },
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}" data-embed-alias="${alias}"><a href="${
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
url + anchor
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
}
@ -408,8 +414,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
return
}
// find first line and callout content
const [firstChild, ...calloutContent] = node.children
// find first line
const firstChild = node.children[0]
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
return
}
@ -486,21 +492,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
"data-callout-metadata": calloutMetaData,
},
}
// Add callout-content class to callout body if it has one.
if (calloutContent.length > 0) {
const contentData: BlockContent | DefinitionContent = {
data: {
hProperties: {
className: "callout-content",
},
hName: "div",
},
type: "blockquote",
children: [...calloutContent],
}
node.children = [node.children[0], contentData]
}
}
})
}

View File

@ -20,10 +20,11 @@ section {
}
.text-highlight {
background-color: var(--textHighlight);
background-color: #fff23688;
padding: 0 0.1rem;
border-radius: 5px;
}
::selection {
background: color-mix(in srgb, var(--tertiary) 60%, rgba(255, 255, 255, 0));
color: var(--darkgray);
@ -42,20 +43,8 @@ ul,
.math {
color: var(--darkgray);
fill: var(--darkgray);
hyphens: auto;
}
p,
ul,
text,
a,
li,
ol,
ul,
.katex,
.math {
overflow-wrap: anywhere;
/* tr and td removed from list of selectors for overflow-wrap, allowing them to use default 'normal' property value */
hyphens: auto;
}
.math {
@ -201,19 +190,11 @@ a {
}
}
& .page-header,
& .page-footer {
width: $pageWidth;
margin-top: 1rem;
@media all and (max-width: $fullPageWidth) {
width: initial;
}
}
& .page-header {
width: $pageWidth;
margin: $topSpacing auto 0 auto;
@media all and (max-width: $fullPageWidth) {
width: initial;
margin-top: 2rem;
}
}
@ -500,10 +481,6 @@ video {
flex: 1 1 auto;
}
div:has(> .overflow) {
position: relative;
}
ul.overflow,
ol.overflow {
max-height: 400;

View File

@ -10,7 +10,7 @@
transition: max-height 0.3s ease;
box-sizing: border-box;
& > .callout-content > :first-child {
& > *:nth-child(2) {
margin-top: 0;
}

View File

@ -7,7 +7,6 @@ export interface ColorScheme {
secondary: string
tertiary: string
highlight: string
textHighlight: string
}
interface Colors {
@ -50,7 +49,6 @@ ${stylesheet.join("\n\n")}
--secondary: ${theme.colors.lightMode.secondary};
--tertiary: ${theme.colors.lightMode.tertiary};
--highlight: ${theme.colors.lightMode.highlight};
--textHighlight: ${theme.colors.lightMode.textHighlight};
--headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF};
--bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF};
@ -66,7 +64,6 @@ ${stylesheet.join("\n\n")}
--secondary: ${theme.colors.darkMode.secondary};
--tertiary: ${theme.colors.darkMode.tertiary};
--highlight: ${theme.colors.darkMode.highlight};
--textHighlight: ${theme.colors.darkMode.textHighlight};
}
`
}

View File

@ -13,8 +13,8 @@
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
"jsxImportSource": "preact",
},
"include": ["**/*.ts", "**/*.tsx", "./package.json"],
"exclude": ["build/**/*.d.ts"]
"exclude": ["build/**/*.d.ts"],
}