mirror of
https://github.com/alrayyes/wiki.git
synced 2024-11-22 19:46:23 +00:00
Merge branch 'v4' of https://github.com/jackyzha0/quartz into v4
This commit is contained in:
commit
2556e5a434
44 changed files with 539 additions and 85 deletions
15
.github/workflows/ci.yaml
vendored
15
.github/workflows/ci.yaml
vendored
|
@ -7,6 +7,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- v4
|
- v4
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
|
@ -18,17 +19,17 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
@ -47,22 +48,22 @@ jobs:
|
||||||
run: npx quartz build --bundleInfo
|
run: npx quartz build --bundleInfo
|
||||||
|
|
||||||
publish-tag:
|
publish-tag:
|
||||||
if: ${{ github.repository == 'jackyzha0/quartz' }}
|
if: ${{ github.repository == 'jackyzha0/quartz' && github.ref == 'refs/heads/v4' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- name: Get package version
|
- name: Get package version
|
||||||
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
|
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
|
||||||
- name: Create release tag
|
- name: Create release tag
|
||||||
uses: pkgdeps/git-tag-action@v2
|
uses: pkgdeps/git-tag-action@v3
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
github_repo: ${{ github.repository }}
|
github_repo: ${{ github.repository }}
|
||||||
|
|
|
@ -129,11 +129,11 @@ export default (() => {
|
||||||
return <button id="btn">Click me</button>
|
return <button id="btn">Click me</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
YourComponent.beforeDOM = `
|
YourComponent.beforeDOMLoaded = `
|
||||||
console.log("hello from before the page loads!")
|
console.log("hello from before the page loads!")
|
||||||
`
|
`
|
||||||
|
|
||||||
YourComponent.afterDOM = `
|
YourComponent.afterDOMLoaded = `
|
||||||
document.getElementById('btn').onclick = () => {
|
document.getElementById('btn').onclick = () => {
|
||||||
alert('button clicked!')
|
alert('button clicked!')
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ export default (() => {
|
||||||
return <button id="btn">Click me</button>
|
return <button id="btn">Click me</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
YourComponent.afterDOM = script
|
YourComponent.afterDOMLoaded = script
|
||||||
return YourComponent
|
return YourComponent
|
||||||
}) satisfies QuartzComponentConstructor
|
}) satisfies QuartzComponentConstructor
|
||||||
```
|
```
|
||||||
|
|
|
@ -48,4 +48,4 @@ Here are the main types of slugs with a rough description of each type of path:
|
||||||
- `SimpleSlug`: cannot be relative and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path.
|
- `SimpleSlug`: cannot be relative and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path.
|
||||||
- `RelativeURL`: must start with `.` or `..` to indicate it's a relative URL. Shouldn't have `/index` as an ending or a file extension but can contain a trailing slash.
|
- `RelativeURL`: must start with `.` or `..` to indicate it's a relative URL. Shouldn't have `/index` as an ending or a file extension but can contain a trailing slash.
|
||||||
|
|
||||||
To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/path.test.ts`.
|
To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/util/path.test.ts`.
|
||||||
|
|
|
@ -28,8 +28,10 @@ This part of the configuration concerns anything that can affect the whole site.
|
||||||
- `{ provider: 'google', tagId: '<your-google-tag>' }`: use Google Analytics;
|
- `{ provider: 'google', tagId: '<your-google-tag>' }`: use Google Analytics;
|
||||||
- `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '<your-plausible-host>' }` (self-hosted): use [Plausible](https://plausible.io/);
|
- `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '<your-plausible-host>' }` (self-hosted): use [Plausible](https://plausible.io/);
|
||||||
- `{ provider: 'umami', host: '<your-umami-host>', websiteId: '<your-umami-website-id>' }`: use [Umami](https://umami.is/);
|
- `{ provider: 'umami', host: '<your-umami-host>', websiteId: '<your-umami-website-id>' }`: use [Umami](https://umami.is/);
|
||||||
- `{ 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: '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: '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
|
- `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.
|
- `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`.
|
- 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`.
|
||||||
|
|
|
@ -9,6 +9,7 @@ Quartz can generate a list of recent notes based on some filtering and sorting c
|
||||||
|
|
||||||
- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })`
|
- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })`
|
||||||
- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })`
|
- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })`
|
||||||
|
- Display the note's tags (defaults to true): `Component.RecentNotes({ showTags: false })`
|
||||||
- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists.
|
- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists.
|
||||||
- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`.
|
- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`.
|
||||||
- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example.
|
- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example.
|
||||||
|
|
|
@ -95,6 +95,16 @@ const [age, setAge] = useState(50)
|
||||||
const [name, setName] = useState("Taylor")
|
const [name, setName] = useState("Taylor")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Inline Highlighting
|
||||||
|
|
||||||
|
Append {:lang} to the end of inline code to highlight it like a regular code block.
|
||||||
|
|
||||||
|
```
|
||||||
|
This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.
|
||||||
|
```
|
||||||
|
|
||||||
|
This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.
|
||||||
|
|
||||||
### Line numbers
|
### Line numbers
|
||||||
|
|
||||||
Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`:
|
Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`:
|
||||||
|
|
|
@ -57,18 +57,16 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Fetch all history for git info
|
fetch-depth: 0 # Fetch all history for git info
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
|
||||||
node-version: 18.14
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Build Quartz
|
- name: Build Quartz
|
||||||
run: npx quartz build
|
run: npx quartz build
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v2
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: public
|
path: public
|
||||||
|
|
||||||
|
@ -81,7 +79,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v2
|
uses: actions/deploy-pages@v4
|
||||||
```
|
```
|
||||||
|
|
||||||
Then:
|
Then:
|
||||||
|
@ -182,35 +180,31 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
|
||||||
|
|
||||||
## GitLab Pages
|
## GitLab Pages
|
||||||
|
|
||||||
In your local Quartz, create a new file `.gitlab-ci.yaml`.
|
In your local Quartz, create a new file `.gitlab-ci.yml`.
|
||||||
|
|
||||||
```yaml title=".gitlab-ci.yaml"
|
```yaml title=".gitlab-ci.yml"
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
variables:
|
image: node:18
|
||||||
NODE_VERSION: "18.14"
|
cache: # Cache modules in between jobs
|
||||||
|
key: $CI_COMMIT_REF_SLUG
|
||||||
|
paths:
|
||||||
|
- .npm/
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_REF_NAME == "v4"'
|
- if: '$CI_COMMIT_REF_NAME == "v4"'
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update -q && apt-get install -y nodejs npm
|
|
||||||
- npm install -g n
|
|
||||||
- n $NODE_VERSION
|
|
||||||
- hash -r
|
- hash -r
|
||||||
- npm ci
|
- npm ci --cache .npm --prefer-offline
|
||||||
script:
|
script:
|
||||||
- npx quartz build
|
- npx quartz build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- public
|
- public
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- ~/.npm/
|
|
||||||
key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}"
|
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
|
|
|
@ -27,5 +27,7 @@ Want to see what Quartz can do? Here are some cool community gardens:
|
||||||
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
|
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
|
||||||
- [🪴Aster's notebook](https://notes.asterhu.com)
|
- [🪴Aster's notebook](https://notes.asterhu.com)
|
||||||
- [🥷🏻🌳🍃 Computer Science & Thinkering Garden](https://notes.yxy.ninja)
|
- [🥷🏻🌳🍃 Computer Science & Thinkering Garden](https://notes.yxy.ninja)
|
||||||
|
- [A Pattern Language - Christopher Alexander (Architecture)](https://patternlanguage.cc/)
|
||||||
|
- [Gatekeeper Wiki](https://www.gatekeeper.wiki)
|
||||||
|
|
||||||
If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)!
|
If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)!
|
||||||
|
|
|
@ -54,7 +54,6 @@ const config: QuartzConfig = {
|
||||||
Plugin.CreatedModifiedDate({
|
Plugin.CreatedModifiedDate({
|
||||||
priority: ["frontmatter", "filesystem"],
|
priority: ["frontmatter", "filesystem"],
|
||||||
}),
|
}),
|
||||||
Plugin.Latex({ renderEngine: "katex" }),
|
|
||||||
Plugin.SyntaxHighlighting({
|
Plugin.SyntaxHighlighting({
|
||||||
theme: {
|
theme: {
|
||||||
light: "github-light",
|
light: "github-light",
|
||||||
|
@ -68,6 +67,7 @@ const config: QuartzConfig = {
|
||||||
Plugin.TableOfContents(),
|
Plugin.TableOfContents(),
|
||||||
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
||||||
Plugin.Description(),
|
Plugin.Description(),
|
||||||
|
Plugin.Latex({ renderEngine: "katex" }),
|
||||||
],
|
],
|
||||||
filters: [Plugin.RemoveDrafts()],
|
filters: [Plugin.RemoveDrafts()],
|
||||||
emitters: [
|
emitters: [
|
||||||
|
|
|
@ -30,6 +30,14 @@ export type Analytics =
|
||||||
apiKey: string
|
apiKey: string
|
||||||
host?: string
|
host?: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
provider: "tinylytics"
|
||||||
|
siteId: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
provider: "cabin"
|
||||||
|
host?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GlobalConfiguration {
|
export interface GlobalConfiguration {
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
|
|
|
@ -168,10 +168,8 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
|
||||||
const isDefaultOpen = opts.folderDefaultState === "open"
|
const isDefaultOpen = opts.folderDefaultState === "open"
|
||||||
|
|
||||||
// Calculate current folderPath
|
// Calculate current folderPath
|
||||||
let folderPath = ""
|
const folderPath = node.name !== "" ? joinSegments(fullPath ?? "", node.name) : ""
|
||||||
if (node.name !== "") {
|
const href = resolveRelative(fileData.slug!, folderPath as SimpleSlug) + "/"
|
||||||
folderPath = joinSegments(fullPath ?? "", node.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -205,11 +203,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
|
||||||
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
|
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
|
||||||
<div key={node.name} data-folderpath={folderPath}>
|
<div key={node.name} data-folderpath={folderPath}>
|
||||||
{folderBehavior === "link" ? (
|
{folderBehavior === "link" ? (
|
||||||
<a
|
<a href={href} data-for={node.name} class="folder-title">
|
||||||
href={resolveRelative(fileData.slug!, folderPath as SimpleSlug)}
|
|
||||||
data-for={node.name}
|
|
||||||
class="folder-title"
|
|
||||||
>
|
|
||||||
{node.displayName}
|
{node.displayName}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface Options {
|
||||||
title?: string
|
title?: string
|
||||||
limit: number
|
limit: number
|
||||||
linkToMore: SimpleSlug | false
|
linkToMore: SimpleSlug | false
|
||||||
|
showTags: boolean
|
||||||
filter: (f: QuartzPluginData) => boolean
|
filter: (f: QuartzPluginData) => boolean
|
||||||
sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number
|
sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number
|
||||||
}
|
}
|
||||||
|
@ -19,6 +20,7 @@ interface Options {
|
||||||
const defaultOptions = (cfg: GlobalConfiguration): Options => ({
|
const defaultOptions = (cfg: GlobalConfiguration): Options => ({
|
||||||
limit: 3,
|
limit: 3,
|
||||||
linkToMore: false,
|
linkToMore: false,
|
||||||
|
showTags: true,
|
||||||
filter: () => true,
|
filter: () => true,
|
||||||
sort: byDateAndAlphabetical(cfg),
|
sort: byDateAndAlphabetical(cfg),
|
||||||
})
|
})
|
||||||
|
@ -56,6 +58,7 @@ export default ((userOpts?: Partial<Options>) => {
|
||||||
<Date date={getDate(cfg, page)!} locale={cfg.locale} />
|
<Date date={getDate(cfg, page)!} locale={cfg.locale} />
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{opts.showTags && (
|
||||||
<ul class="tags">
|
<ul class="tags">
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<li>
|
<li>
|
||||||
|
@ -68,6 +71,7 @@ export default ((userOpts?: Partial<Options>) => {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,10 +2,15 @@ import { i18n } from "../../i18n"
|
||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
|
|
||||||
const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => {
|
const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => {
|
||||||
|
// If baseUrl contains a pathname after the domain, use this as the home link
|
||||||
|
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
|
||||||
|
const baseDir = url.pathname
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article class="popover-hint">
|
<article class="popover-hint">
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p>{i18n(cfg.locale).pages.error.notFound}</p>
|
<p>{i18n(cfg.locale).pages.error.notFound}</p>
|
||||||
|
<a href={baseDir}>{i18n(cfg.locale).pages.error.home}</a>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,18 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||||
.transition()
|
.transition()
|
||||||
.duration(200)
|
.duration(200)
|
||||||
.style("opacity", 0.2)
|
.style("opacity", 0.2)
|
||||||
|
|
||||||
|
d3.selectAll<HTMLElement, NodeData>(".node")
|
||||||
|
.filter((d) => !connectedNodes.includes(d.id))
|
||||||
|
.nodes()
|
||||||
|
.map((it) => d3.select(it.parentNode as HTMLElement).select("text"))
|
||||||
|
.forEach((it) => {
|
||||||
|
let opacity = parseFloat(it.style("opacity"))
|
||||||
|
it.transition()
|
||||||
|
.duration(200)
|
||||||
|
.attr("opacityOld", opacity)
|
||||||
|
.style("opacity", Math.min(opacity, 0.2))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// highlight links
|
// highlight links
|
||||||
|
@ -245,6 +257,12 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||||
if (focusOnHover) {
|
if (focusOnHover) {
|
||||||
d3.selectAll<HTMLElement, NodeData>(".link").transition().duration(200).style("opacity", 1)
|
d3.selectAll<HTMLElement, NodeData>(".link").transition().duration(200).style("opacity", 1)
|
||||||
d3.selectAll<HTMLElement, NodeData>(".node").transition().duration(200).style("opacity", 1)
|
d3.selectAll<HTMLElement, NodeData>(".node").transition().duration(200).style("opacity", 1)
|
||||||
|
|
||||||
|
d3.selectAll<HTMLElement, NodeData>(".node")
|
||||||
|
.filter((d) => !connectedNodes.includes(d.id))
|
||||||
|
.nodes()
|
||||||
|
.map((it) => d3.select(it.parentNode as HTMLElement).select("text"))
|
||||||
|
.forEach((it) => it.transition().duration(200).style("opacity", it.attr("opacityOld")))
|
||||||
}
|
}
|
||||||
const currentId = d.id
|
const currentId = d.id
|
||||||
const linkNodes = d3
|
const linkNodes = d3
|
||||||
|
@ -264,6 +282,13 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
.call(drag(simulation))
|
.call(drag(simulation))
|
||||||
|
|
||||||
|
// make tags hollow circles
|
||||||
|
node
|
||||||
|
.filter((d) => d.id.startsWith("tags/"))
|
||||||
|
.attr("stroke", color)
|
||||||
|
.attr("stroke-width", 2)
|
||||||
|
.attr("fill", "var(--light)")
|
||||||
|
|
||||||
// draw labels
|
// draw labels
|
||||||
const labels = graphNode
|
const labels = graphNode
|
||||||
.append("text")
|
.append("text")
|
||||||
|
@ -336,7 +361,7 @@ function renderGlobalGraph() {
|
||||||
|
|
||||||
document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
||||||
const slug = e.detail.url
|
const slug = e.detail.url
|
||||||
addToVisited(slug)
|
addToVisited(simplifySlug(slug))
|
||||||
await renderGraph("graph-container", slug)
|
await renderGraph("graph-container", slug)
|
||||||
|
|
||||||
const containerIcon = document.getElementById("global-graph-icon")
|
const containerIcon = document.getElementById("global-graph-icon")
|
||||||
|
|
|
@ -11,7 +11,7 @@ li.section-li {
|
||||||
|
|
||||||
& > .section {
|
& > .section {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 6em 3fr 1fr;
|
grid-template-columns: fit-content(8em) 3fr 1fr;
|
||||||
|
|
||||||
@media all and (max-width: $mobileBreakpoint) {
|
@media all and (max-width: $mobileBreakpoint) {
|
||||||
& > .tags {
|
& > .tags {
|
||||||
|
@ -24,8 +24,7 @@ li.section-li {
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .meta {
|
& > .meta {
|
||||||
margin: 0;
|
margin: 0 1em 0 0;
|
||||||
flex-basis: 6em;
|
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +32,8 @@ li.section-li {
|
||||||
|
|
||||||
// modifications in popover context
|
// modifications in popover context
|
||||||
.popover .section {
|
.popover .section {
|
||||||
grid-template-columns: 6em 1fr !important;
|
grid-template-columns: fit-content(8em) 1fr !important;
|
||||||
|
|
||||||
& > .tags {
|
& > .tags {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ja from "./locales/ja-JP"
|
||||||
import de from "./locales/de-DE"
|
import de from "./locales/de-DE"
|
||||||
import nl from "./locales/nl-NL"
|
import nl from "./locales/nl-NL"
|
||||||
import ro from "./locales/ro-RO"
|
import ro from "./locales/ro-RO"
|
||||||
|
import ca from "./locales/ca-ES"
|
||||||
import es from "./locales/es-ES"
|
import es from "./locales/es-ES"
|
||||||
import ar from "./locales/ar-SA"
|
import ar from "./locales/ar-SA"
|
||||||
import uk from "./locales/uk-UA"
|
import uk from "./locales/uk-UA"
|
||||||
|
@ -15,9 +16,12 @@ import zh from "./locales/zh-CN"
|
||||||
import vi from "./locales/vi-VN"
|
import vi from "./locales/vi-VN"
|
||||||
import pt from "./locales/pt-BR"
|
import pt from "./locales/pt-BR"
|
||||||
import hu from "./locales/hu-HU"
|
import hu from "./locales/hu-HU"
|
||||||
|
import fa from "./locales/fa-IR"
|
||||||
|
import pl from "./locales/pl-PL"
|
||||||
|
|
||||||
export const TRANSLATIONS = {
|
export const TRANSLATIONS = {
|
||||||
"en-US": en,
|
"en-US": en,
|
||||||
|
"en-GB": en,
|
||||||
"fr-FR": fr,
|
"fr-FR": fr,
|
||||||
"it-IT": it,
|
"it-IT": it,
|
||||||
"ja-JP": ja,
|
"ja-JP": ja,
|
||||||
|
@ -26,6 +30,7 @@ export const TRANSLATIONS = {
|
||||||
"nl-BE": nl,
|
"nl-BE": nl,
|
||||||
"ro-RO": ro,
|
"ro-RO": ro,
|
||||||
"ro-MD": ro,
|
"ro-MD": ro,
|
||||||
|
"ca-ES": ca,
|
||||||
"es-ES": es,
|
"es-ES": es,
|
||||||
"ar-SA": ar,
|
"ar-SA": ar,
|
||||||
"ar-AE": ar,
|
"ar-AE": ar,
|
||||||
|
@ -54,6 +59,8 @@ export const TRANSLATIONS = {
|
||||||
"vi-VN": vi,
|
"vi-VN": vi,
|
||||||
"pt-BR": pt,
|
"pt-BR": pt,
|
||||||
"hu-HU": hu,
|
"hu-HU": hu,
|
||||||
|
"fa-IR": fa,
|
||||||
|
"pl-PL": pl,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const defaultTranslation = "en-US"
|
export const defaultTranslation = "en-US"
|
||||||
|
|
|
@ -70,6 +70,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "غير موجود",
|
title: "غير موجود",
|
||||||
notFound: "إما أن هذه الصفحة خاصة أو غير موجودة.",
|
notFound: "إما أن هذه الصفحة خاصة أو غير موجودة.",
|
||||||
|
home: "العوده للصفحة الرئيسية",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "مجلد",
|
folder: "مجلد",
|
||||||
|
|
84
quartz/i18n/locales/ca-ES.ts
Normal file
84
quartz/i18n/locales/ca-ES.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
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
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Nicht gefunden",
|
title: "Nicht gefunden",
|
||||||
notFound: "Diese Seite ist entweder nicht öffentlich oder existiert nicht.",
|
notFound: "Diese Seite ist entweder nicht öffentlich oder existiert nicht.",
|
||||||
|
home: "Return to Homepage",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Ordner",
|
folder: "Ordner",
|
||||||
|
|
|
@ -67,6 +67,7 @@ export interface Translation {
|
||||||
error: {
|
error: {
|
||||||
title: string
|
title: string
|
||||||
notFound: string
|
notFound: string
|
||||||
|
home: string
|
||||||
}
|
}
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: string
|
folder: string
|
||||||
|
|
84
quartz/i18n/locales/en-GB.ts
Normal file
84
quartz/i18n/locales/en-GB.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
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
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Not Found",
|
title: "Not Found",
|
||||||
notFound: "Either this page is private or doesn't exist.",
|
notFound: "Either this page is private or doesn't exist.",
|
||||||
|
home: "Return to Homepage",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Folder",
|
folder: "Folder",
|
||||||
|
|
|
@ -22,8 +22,8 @@ export default {
|
||||||
quote: "Cita",
|
quote: "Cita",
|
||||||
},
|
},
|
||||||
backlinks: {
|
backlinks: {
|
||||||
title: "Enlaces de Retroceso",
|
title: "Retroenlaces",
|
||||||
noBacklinksFound: "No se han encontrado enlaces traseros",
|
noBacklinksFound: "No se han encontrado retroenlaces",
|
||||||
},
|
},
|
||||||
themeToggle: {
|
themeToggle: {
|
||||||
lightMode: "Modo claro",
|
lightMode: "Modo claro",
|
||||||
|
@ -54,17 +54,18 @@ export default {
|
||||||
title: "Tabla de Contenidos",
|
title: "Tabla de Contenidos",
|
||||||
},
|
},
|
||||||
contentMeta: {
|
contentMeta: {
|
||||||
readingTime: ({ minutes }) => `${minutes} min read`,
|
readingTime: ({ minutes }) => `Se lee en ${minutes} min`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
rss: {
|
rss: {
|
||||||
recentNotes: "Notas recientes",
|
recentNotes: "Notas recientes",
|
||||||
lastFewNotes: ({ count }) => `Últimás ${count} notas`,
|
lastFewNotes: ({ count }) => `Últimas ${count} notas`,
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
title: "No se encontró.",
|
title: "No se ha encontrado.",
|
||||||
notFound: "Esta página es privada o no existe.",
|
notFound: "Esta página es privada o no existe.",
|
||||||
|
home: "Regresa a la página principal",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Carpeta",
|
folder: "Carpeta",
|
||||||
|
@ -77,7 +78,7 @@ export default {
|
||||||
itemsUnderTag: ({ count }) =>
|
itemsUnderTag: ({ count }) =>
|
||||||
count === 1 ? "1 artículo con esta etiqueta." : `${count} artículos con esta etiqueta.`,
|
count === 1 ? "1 artículo con esta etiqueta." : `${count} artículos con esta etiqueta.`,
|
||||||
showingFirst: ({ count }) => `Mostrando las primeras ${count} etiquetas.`,
|
showingFirst: ({ count }) => `Mostrando las primeras ${count} etiquetas.`,
|
||||||
totalTags: ({ count }) => `Se encontraron ${count} etiquetas en total.`,
|
totalTags: ({ count }) => `Se han encontrado ${count} etiquetas en total.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies Translation
|
} as const satisfies Translation
|
||||||
|
|
84
quartz/i18n/locales/fa-IR.ts
Normal file
84
quartz/i18n/locales/fa-IR.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { Translation } from "./definition"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
propertyDefaults: {
|
||||||
|
title: "بدون عنوان",
|
||||||
|
description: "توضیح خاصی اضافه نشده است",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
callout: {
|
||||||
|
note: "یادداشت",
|
||||||
|
abstract: "چکیده",
|
||||||
|
info: "اطلاعات",
|
||||||
|
todo: "اقدام",
|
||||||
|
tip: "نکته",
|
||||||
|
success: "تیک",
|
||||||
|
question: "سؤال",
|
||||||
|
warning: "هشدار",
|
||||||
|
failure: "شکست",
|
||||||
|
danger: "خطر",
|
||||||
|
bug: "باگ",
|
||||||
|
example: "مثال",
|
||||||
|
quote: "نقل قول",
|
||||||
|
},
|
||||||
|
backlinks: {
|
||||||
|
title: "بکلینکها",
|
||||||
|
noBacklinksFound: "بدون بکلینک",
|
||||||
|
},
|
||||||
|
themeToggle: {
|
||||||
|
lightMode: "حالت روشن",
|
||||||
|
darkMode: "حالت تاریک",
|
||||||
|
},
|
||||||
|
explorer: {
|
||||||
|
title: "مطالب",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
createdWith: "ساخته شده با",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
title: "نمای گراف",
|
||||||
|
},
|
||||||
|
recentNotes: {
|
||||||
|
title: "یادداشتهای اخیر",
|
||||||
|
seeRemainingMore: ({ remaining }) => `${remaining} یادداشت دیگر →`,
|
||||||
|
},
|
||||||
|
transcludes: {
|
||||||
|
transcludeOf: ({ targetSlug }) => `از ${targetSlug}`,
|
||||||
|
linkToOriginal: "پیوند به اصلی",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: "جستجو",
|
||||||
|
searchBarPlaceholder: "مطلبی را جستجو کنید",
|
||||||
|
},
|
||||||
|
tableOfContents: {
|
||||||
|
title: "فهرست",
|
||||||
|
},
|
||||||
|
contentMeta: {
|
||||||
|
readingTime: ({ minutes }) => `زمان تقریبی مطالعه: ${minutes} دقیقه`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
rss: {
|
||||||
|
recentNotes: "یادداشتهای اخیر",
|
||||||
|
lastFewNotes: ({ count }) => `${count} یادداشت اخیر`,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: "یافت نشد",
|
||||||
|
notFound: "این صفحه یا خصوصی است یا وجود ندارد",
|
||||||
|
home: "بازگشت به صفحه اصلی",
|
||||||
|
},
|
||||||
|
folderContent: {
|
||||||
|
folder: "پوشه",
|
||||||
|
itemsUnderFolder: ({ count }) =>
|
||||||
|
count === 1 ? ".یک مطلب در این پوشه است" : `${count} مطلب در این پوشه است.`,
|
||||||
|
},
|
||||||
|
tagContent: {
|
||||||
|
tag: "برچسب",
|
||||||
|
tagIndex: "فهرست برچسبها",
|
||||||
|
itemsUnderTag: ({ count }) =>
|
||||||
|
count === 1 ? "یک مطلب با این برچسب" : `${count} مطلب با این برچسب.`,
|
||||||
|
showingFirst: ({ count }) => `در حال نمایش ${count} برچسب.`,
|
||||||
|
totalTags: ({ count }) => `${count} برچسب یافت شد.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const satisfies Translation
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Introuvable",
|
title: "Introuvable",
|
||||||
notFound: "Cette page est soit privée, soit elle n'existe pas.",
|
notFound: "Cette page est soit privée, soit elle n'existe pas.",
|
||||||
|
home: "Retour à la page d'accueil",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Dossier",
|
folder: "Dossier",
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Nem található",
|
title: "Nem található",
|
||||||
notFound: "Ez a lap vagy privát vagy nem létezik.",
|
notFound: "Ez a lap vagy privát vagy nem létezik.",
|
||||||
|
home: "Vissza a kezdőlapra",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Mappa",
|
folder: "Mappa",
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Non trovato",
|
title: "Non trovato",
|
||||||
notFound: "Questa pagina è privata o non esiste.",
|
notFound: "Questa pagina è privata o non esiste.",
|
||||||
|
home: "Ritorna alla home page",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Cartella",
|
folder: "Cartella",
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Not Found",
|
title: "Not Found",
|
||||||
notFound: "ページが存在しないか、非公開設定になっています。",
|
notFound: "ページが存在しないか、非公開設定になっています。",
|
||||||
|
home: "ホームページに戻る",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "フォルダ",
|
folder: "フォルダ",
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Not Found",
|
title: "Not Found",
|
||||||
notFound: "페이지가 존재하지 않거나 비공개 설정이 되어 있습니다.",
|
notFound: "페이지가 존재하지 않거나 비공개 설정이 되어 있습니다.",
|
||||||
|
home: "홈페이지로 돌아가기",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "폴더",
|
folder: "폴더",
|
||||||
|
|
|
@ -66,6 +66,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Niet gevonden",
|
title: "Niet gevonden",
|
||||||
notFound: "Deze pagina is niet zichtbaar of bestaat niet.",
|
notFound: "Deze pagina is niet zichtbaar of bestaat niet.",
|
||||||
|
home: "Keer terug naar de start pagina",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Map",
|
folder: "Map",
|
||||||
|
|
84
quartz/i18n/locales/pl-PL.ts
Normal file
84
quartz/i18n/locales/pl-PL.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { Translation } from "./definition"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
propertyDefaults: {
|
||||||
|
title: "Bez nazwy",
|
||||||
|
description: "Brak opisu",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
callout: {
|
||||||
|
note: "Notatka",
|
||||||
|
abstract: "Streszczenie",
|
||||||
|
info: "informacja",
|
||||||
|
todo: "Do zrobienia",
|
||||||
|
tip: "Wskazówka",
|
||||||
|
success: "Zrobione",
|
||||||
|
question: "Pytanie",
|
||||||
|
warning: "Ostrzeżenie",
|
||||||
|
failure: "Usterka",
|
||||||
|
danger: "Niebiezpieczeństwo",
|
||||||
|
bug: "Błąd w kodzie",
|
||||||
|
example: "Przykład",
|
||||||
|
quote: "Cytat",
|
||||||
|
},
|
||||||
|
backlinks: {
|
||||||
|
title: "Odnośniki zwrotne",
|
||||||
|
noBacklinksFound: "Brak połączeń zwrotnych",
|
||||||
|
},
|
||||||
|
themeToggle: {
|
||||||
|
lightMode: "Trzyb jasny",
|
||||||
|
darkMode: "Tryb ciemny",
|
||||||
|
},
|
||||||
|
explorer: {
|
||||||
|
title: "Przeglądaj",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
createdWith: "Stworzone z użyciem",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
title: "Graf",
|
||||||
|
},
|
||||||
|
recentNotes: {
|
||||||
|
title: "Najnowsze notatki",
|
||||||
|
seeRemainingMore: ({ remaining }) => `Zobacz ${remaining} nastepnych →`,
|
||||||
|
},
|
||||||
|
transcludes: {
|
||||||
|
transcludeOf: ({ targetSlug }) => `Osadzone ${targetSlug}`,
|
||||||
|
linkToOriginal: "Łącze do oryginału",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: "Szukaj",
|
||||||
|
searchBarPlaceholder: "Search for something",
|
||||||
|
},
|
||||||
|
tableOfContents: {
|
||||||
|
title: "Spis treści",
|
||||||
|
},
|
||||||
|
contentMeta: {
|
||||||
|
readingTime: ({ minutes }) => `${minutes} min. czytania `,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
rss: {
|
||||||
|
recentNotes: "Najnowsze notatki",
|
||||||
|
lastFewNotes: ({ count }) => `Ostatnie ${count} notatek`,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: "Nie znaleziono",
|
||||||
|
notFound: "Ta strona jest prywatna lub nie istnieje.",
|
||||||
|
home: "Powrót do strony głównej",
|
||||||
|
},
|
||||||
|
folderContent: {
|
||||||
|
folder: "Folder",
|
||||||
|
itemsUnderFolder: ({ count }) =>
|
||||||
|
count === 1 ? "W tym folderze jest 1 element." : `Elementów w folderze: ${count}.`,
|
||||||
|
},
|
||||||
|
tagContent: {
|
||||||
|
tag: "Znacznik",
|
||||||
|
tagIndex: "Spis znaczników",
|
||||||
|
itemsUnderTag: ({ count }) =>
|
||||||
|
count === 1 ? "Oznaczony 1 element." : `Elementów z tym znacznikiem: ${count}.`,
|
||||||
|
showingFirst: ({ count }) => `Pokazuje ${count} pierwszych znaczników.`,
|
||||||
|
totalTags: ({ count }) => `Znalezionych wszystkich znaczników: ${count}.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const satisfies Translation
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Não encontrado",
|
title: "Não encontrado",
|
||||||
notFound: "Esta página é privada ou não existe.",
|
notFound: "Esta página é privada ou não existe.",
|
||||||
|
home: "Retornar a página inicial",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Arquivo",
|
folder: "Arquivo",
|
||||||
|
|
|
@ -66,6 +66,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Pagina nu a fost găsită",
|
title: "Pagina nu a fost găsită",
|
||||||
notFound: "Fie această pagină este privată, fie nu există.",
|
notFound: "Fie această pagină este privată, fie nu există.",
|
||||||
|
home: "Reveniți la pagina de pornire",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Dosar",
|
folder: "Dosar",
|
||||||
|
|
|
@ -67,6 +67,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Страница не найдена",
|
title: "Страница не найдена",
|
||||||
notFound: "Эта страница приватная или не существует",
|
notFound: "Эта страница приватная или не существует",
|
||||||
|
home: "Вернуться на главную страницу",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Папка",
|
folder: "Папка",
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
||||||
title: "Зміст",
|
title: "Зміст",
|
||||||
},
|
},
|
||||||
contentMeta: {
|
contentMeta: {
|
||||||
readingTime: ({ minutes }) => `${minutes} min read`,
|
readingTime: ({ minutes }) => `${minutes} хв читання`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
|
@ -65,19 +65,20 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Не знайдено",
|
title: "Не знайдено",
|
||||||
notFound: "Ця сторінка або приватна, або не існує.",
|
notFound: "Ця сторінка або приватна, або не існує.",
|
||||||
|
home: "Повернутися на головну сторінку",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Папка",
|
folder: "Тека",
|
||||||
itemsUnderFolder: ({ count }) =>
|
itemsUnderFolder: ({ count }) =>
|
||||||
count === 1 ? "У цій папці 1 елемент." : `Елементів у цій папці: ${count}.`,
|
count === 1 ? "У цій теці 1 елемент." : `Елементів у цій теці: ${count}.`,
|
||||||
},
|
},
|
||||||
tagContent: {
|
tagContent: {
|
||||||
tag: "Тег",
|
tag: "Мітка",
|
||||||
tagIndex: "Індекс тегу",
|
tagIndex: "Індекс мітки",
|
||||||
itemsUnderTag: ({ count }) =>
|
itemsUnderTag: ({ count }) =>
|
||||||
count === 1 ? "1 елемент з цим тегом." : `Елементів з цим тегом: ${count}.`,
|
count === 1 ? "1 елемент з цією міткою." : `Елементів з цією міткою: ${count}.`,
|
||||||
showingFirst: ({ count }) => `Показ перших ${count} тегів.`,
|
showingFirst: ({ count }) => `Показ перших ${count} міток.`,
|
||||||
totalTags: ({ count }) => `Всього знайдено тегів: ${count}.`,
|
totalTags: ({ count }) => `Всього знайдено міток: ${count}.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies Translation
|
} as const satisfies Translation
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "Không Tìm Thấy",
|
title: "Không Tìm Thấy",
|
||||||
notFound: "Trang này được bảo mật hoặc không tồn tại.",
|
notFound: "Trang này được bảo mật hoặc không tồn tại.",
|
||||||
|
home: "Trở về trang chủ",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Thư Mục",
|
folder: "Thư Mục",
|
||||||
|
|
|
@ -65,6 +65,7 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
title: "无法找到",
|
title: "无法找到",
|
||||||
notFound: "私有笔记或笔记不存在。",
|
notFound: "私有笔记或笔记不存在。",
|
||||||
|
home: "返回首页",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "文件夹",
|
folder: "文件夹",
|
||||||
|
|
|
@ -136,6 +136,22 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
||||||
posthog.init('${cfg.analytics.apiKey}',{api_host:'${cfg.analytics.host ?? "https://app.posthog.com"}'})\`
|
posthog.init('${cfg.analytics.apiKey}',{api_host:'${cfg.analytics.host ?? "https://app.posthog.com"}'})\`
|
||||||
document.head.appendChild(posthogScript)
|
document.head.appendChild(posthogScript)
|
||||||
`)
|
`)
|
||||||
|
} else if (cfg.analytics?.provider === "tinylytics") {
|
||||||
|
const siteId = cfg.analytics.siteId
|
||||||
|
componentResources.afterDOMLoaded.push(`
|
||||||
|
const tinylyticsScript = document.createElement("script")
|
||||||
|
tinylyticsScript.src = "https://tinylytics.app/embed/${siteId}.js"
|
||||||
|
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) {
|
if (cfg.enableSPA) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { QuartzFilterPlugin } from "../types"
|
||||||
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
|
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
|
||||||
name: "RemoveDrafts",
|
name: "RemoveDrafts",
|
||||||
shouldPublish(_ctx, [_tree, vfile]) {
|
shouldPublish(_ctx, [_tree, vfile]) {
|
||||||
const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false
|
const draftFlag: boolean = vfile.data?.frontmatter?.draft || false
|
||||||
return !draftFlag
|
return !draftFlag
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -93,7 +93,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
|
||||||
}
|
}
|
||||||
node.properties.className = classes
|
node.properties.className = classes
|
||||||
|
|
||||||
if (opts.openLinksInNewTab) {
|
if (isExternal && opts.openLinksInNewTab) {
|
||||||
node.properties.target = "_blank"
|
node.properties.target = "_blank"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { slug as slugAnchor } from "github-slugger"
|
||||||
import rehypeRaw from "rehype-raw"
|
import rehypeRaw from "rehype-raw"
|
||||||
import { SKIP, visit } from "unist-util-visit"
|
import { SKIP, visit } from "unist-util-visit"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { splitAnchor } from "../../util/path"
|
||||||
import { JSResource } from "../../util/resources"
|
import { JSResource } from "../../util/resources"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import calloutScript from "../../components/scripts/callout.inline.ts"
|
import calloutScript from "../../components/scripts/callout.inline.ts"
|
||||||
|
@ -123,8 +124,8 @@ export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/, "g")
|
||||||
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
|
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
|
||||||
const commentRegex = new RegExp(/%%[\s\S]*?%%/, "g")
|
const commentRegex = new RegExp(/%%[\s\S]*?%%/, "g")
|
||||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||||
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
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
|
// (?:^| ) -> 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 #
|
// #(...) -> 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, non-empty string of (Unicode-aware) alpha-numeric characters and symbols, hyphens and/or underscores
|
||||||
|
@ -199,10 +200,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||||
src = src.replace(wikilinkRegex, (value, ...capture) => {
|
src = src.replace(wikilinkRegex, (value, ...capture) => {
|
||||||
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
|
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
|
||||||
|
|
||||||
const fp = rawFp ?? ""
|
const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`)
|
||||||
const anchor = rawHeader?.trim().replace(/^#+/, "")
|
|
||||||
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
|
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
|
||||||
const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
|
const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : ""
|
||||||
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
|
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
|
||||||
const embedDisplay = value.startsWith("!") ? "!" : ""
|
const embedDisplay = value.startsWith("!") ? "!" : ""
|
||||||
|
|
||||||
|
@ -414,8 +414,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// find first line
|
// find first line and callout content
|
||||||
const firstChild = node.children[0]
|
const [firstChild, ...calloutContent] = node.children
|
||||||
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
|
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -427,7 +427,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||||
|
|
||||||
const match = firstLine.match(calloutRegex)
|
const match = firstLine.match(calloutRegex)
|
||||||
if (match && match.input) {
|
if (match && match.input) {
|
||||||
const [calloutDirective, typeString, collapseChar] = match
|
const [calloutDirective, typeString, calloutMetaData, collapseChar] = match
|
||||||
const calloutType = canonicalizeCallout(typeString.toLowerCase())
|
const calloutType = canonicalizeCallout(typeString.toLowerCase())
|
||||||
const collapse = collapseChar === "+" || collapseChar === "-"
|
const collapse = collapseChar === "+" || collapseChar === "-"
|
||||||
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
|
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
|
||||||
|
@ -489,8 +489,24 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||||
className: classNames.join(" "),
|
className: classNames.join(" "),
|
||||||
"data-callout": calloutType,
|
"data-callout": calloutType,
|
||||||
"data-callout-fold": collapse,
|
"data-callout-fold": collapse,
|
||||||
|
"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]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,22 @@ ul,
|
||||||
.math {
|
.math {
|
||||||
color: var(--darkgray);
|
color: var(--darkgray);
|
||||||
fill: var(--darkgray);
|
fill: var(--darkgray);
|
||||||
overflow-wrap: anywhere;
|
|
||||||
hyphens: auto;
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
.math {
|
.math {
|
||||||
&.math-display {
|
&.math-display {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -481,6 +493,10 @@ video {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div:has(> .overflow) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
ul.overflow,
|
ul.overflow,
|
||||||
ol.overflow {
|
ol.overflow {
|
||||||
max-height: 400;
|
max-height: 400;
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
transition: max-height 0.3s ease;
|
transition: max-height 0.3s ease;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
& > *:nth-child(2) {
|
& > .callout-content > :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "preact",
|
"jsxImportSource": "preact"
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx", "./package.json"],
|
"include": ["**/*.ts", "**/*.tsx", "./package.json"],
|
||||||
"exclude": ["build/**/*.d.ts"],
|
"exclude": ["build/**/*.d.ts"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue