diff --git a/docs/hosting.md b/docs/hosting.md index bae7ed8..eeb9308 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -228,3 +228,25 @@ pages: When `.gitlab-ci.yaml` is committed, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar. By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`. + +## Self-Hosting + +Copy the `public` directory to your web server and configure it to serve the files. You can use any web server to host your site. Since Quartz generates links that do not include the `.html` extension, you need to let your web server know how to deal with it. + +### Using Nginx + +Here's an example of how to do this with Nginx: + +```nginx title="nginx.conf" +server { + listen 80; + server_name example.com; + root /path/to/quartz/public; + index index.html; + error_page 404 /404.html; + + location / { + try_files $uri $uri.html $uri/ =404; + } +} +``` diff --git a/package-lock.json b/package-lock.json index bb5f034..71f6a37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "esbuild-sass-plugin": "^2.16.1", "flexsearch": "0.7.43", "github-slugger": "^2.0.0", - "globby": "^14.0.0", + "globby": "^14.0.1", "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.0", "hast-util-to-jsx-runtime": "^2.3.0", @@ -32,7 +32,7 @@ "mdast-util-to-hast": "^13.1.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "preact": "^10.19.4", + "preact": "^10.19.5", "preact-render-to-string": "^6.3.1", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -73,7 +73,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.16", + "@types/node": "^20.11.19", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", @@ -743,9 +743,9 @@ } }, "node_modules/@sindresorhus/merge-streams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", - "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "engines": { "node": ">=18" }, @@ -1093,9 +1093,9 @@ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, "node_modules/@types/node": { - "version": "20.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", - "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2314,11 +2314,11 @@ } }, "node_modules/globby": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", - "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", + "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", "dependencies": { - "@sindresorhus/merge-streams": "^1.0.0", + "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", @@ -4293,9 +4293,9 @@ } }, "node_modules/preact": { - "version": "10.19.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.4.tgz", - "integrity": "sha512-dwaX5jAh0Ga8uENBX1hSOujmKWgx9RtL80KaKUFLc6jb4vCEAc3EeZ0rnQO/FO4VgjfPMfoLFWnNG8bHuZ9VLw==", + "version": "10.19.5", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.5.tgz", + "integrity": "sha512-OPELkDmSVbKjbFqF9tgvOowiiQ9TmsJljIzXRyNE8nGiis94pwv1siF78rQkAP1Q1738Ce6pellRg/Ns/CtHqQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" diff --git a/package.json b/package.json index f89bb2a..54c67fb 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "esbuild-sass-plugin": "^2.16.1", "flexsearch": "0.7.43", "github-slugger": "^2.0.0", - "globby": "^14.0.0", + "globby": "^14.0.1", "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.0", "hast-util-to-jsx-runtime": "^2.3.0", @@ -57,7 +57,7 @@ "mdast-util-to-hast": "^13.1.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "preact": "^10.19.4", + "preact": "^10.19.5", "preact-render-to-string": "^6.3.1", "pretty-bytes": "^6.1.1", "pretty-time": "^1.1.0", @@ -95,7 +95,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.16", + "@types/node": "^20.11.19", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", diff --git a/quartz/build.ts b/quartz/build.ts index 452a2f1..3d95f31 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -60,7 +60,7 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { const release = await mut.acquire() perf.addEvent("clean") - await rimraf(output) + await rimraf(path.join(output, "*"), { glob: true }) console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) perf.addEvent("glob") @@ -375,7 +375,7 @@ async function rebuildFromEntrypoint( // TODO: we can probably traverse the link graph to figure out what's safe to delete here // instead of just deleting everything - await rimraf(argv.output) + await rimraf(path.join(argv.output, ".*"), { glob: true }) await emitContent(ctx, filteredContent) console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) } catch (err) { diff --git a/quartz/i18n/index.ts b/quartz/i18n/index.ts index 5224f66..38d3562 100644 --- a/quartz/i18n/index.ts +++ b/quartz/i18n/index.ts @@ -1,6 +1,7 @@ import { Translation, CalloutTranslation } from "./locales/definition" 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" @@ -10,10 +11,12 @@ import ar from "./locales/ar-SA" import uk from "./locales/uk-UA" import ru from "./locales/ru-RU" import ko from "./locales/ko-KR" +import zh from "./locales/zh-CN" export const TRANSLATIONS = { "en-US": en, "fr-FR": fr, + "it-IT": it, "ja-JP": ja, "de-DE": de, "nl-NL": nl, @@ -44,6 +47,7 @@ export const TRANSLATIONS = { "uk-UA": uk, "ru-RU": ru, "ko-KR": ko, + "zh-CN": zh, } as const export const defaultTranslation = "en-US" diff --git a/quartz/i18n/locales/it-IT.ts b/quartz/i18n/locales/it-IT.ts new file mode 100644 index 0000000..a0cc042 --- /dev/null +++ b/quartz/i18n/locales/it-IT.ts @@ -0,0 +1,83 @@ +import { Translation } from "./definition" + +export default { + propertyDefaults: { + title: "Senza titolo", + description: "Nessuna descrizione", + }, + components: { + callout: { + note: "Nota", + abstract: "Astratto", + info: "Info", + todo: "Da fare", + tip: "Consiglio", + success: "Completato", + question: "Domanda", + warning: "Attenzione", + failure: "Errore", + danger: "Pericolo", + bug: "Bug", + example: "Esempio", + quote: "Citazione", + }, + backlinks: { + title: "Link entranti", + noBacklinksFound: "Nessun link entrante", + }, + themeToggle: { + lightMode: "Tema chiaro", + darkMode: "Tema scuro", + }, + explorer: { + title: "Esplora", + }, + footer: { + createdWith: "Creato con", + }, + graph: { + title: "Vista grafico", + }, + recentNotes: { + title: "Note recenti", + seeRemainingMore: ({ remaining }) => `Vedi ${remaining} altro →`, + }, + transcludes: { + transcludeOf: ({ targetSlug }) => `Transclusione di ${targetSlug}`, + linkToOriginal: "Link all'originale", + }, + search: { + title: "Cerca", + searchBarPlaceholder: "Cerca qualcosa", + }, + tableOfContents: { + title: "Tabella dei contenuti", + }, + contentMeta: { + readingTime: ({ minutes }) => `${minutes} minuti`, + }, + }, + pages: { + rss: { + recentNotes: "Note recenti", + lastFewNotes: ({ count }) => `Ultime ${count} note`, + }, + error: { + title: "Non trovato", + notFound: "Questa pagina è privata o non esiste.", + }, + folderContent: { + folder: "Cartella", + itemsUnderFolder: ({ count }) => + count === 1 ? "1 oggetto in questa cartella" : `${count} oggetti in questa cartella.`, + }, + tagContent: { + tag: "Etichetta", + tagIndex: "Indice etichette", + itemsUnderTag: ({ count }) => + count === 1 ? "1 oggetto con questa etichetta" : `${count} oggetti con questa etichetta.`, + showingFirst: ({ count }) => `Prime ${count} etichette.`, + totalTags: ({ count }) => `Trovate ${count} etichette totali.`, + }, + }, +} as const satisfies Translation diff --git a/quartz/i18n/locales/ko-KR.ts b/quartz/i18n/locales/ko-KR.ts index ed859a9..ea735b0 100644 --- a/quartz/i18n/locales/ko-KR.ts +++ b/quartz/i18n/locales/ko-KR.ts @@ -68,12 +68,12 @@ export default { }, folderContent: { folder: "폴더", - itemsUnderFolder: ({ count }) => `${count}건의 페이지`, + itemsUnderFolder: ({ count }) => `${count}건의 항목`, }, tagContent: { tag: "태그", tagIndex: "태그 목록", - itemsUnderTag: ({ count }) => `${count}건의 페이지`, + itemsUnderTag: ({ count }) => `${count}건의 항목`, showingFirst: ({ count }) => `처음 ${count}개의 태그`, totalTags: ({ count }) => `총 ${count}개의 태그를 찾았습니다.`, }, diff --git a/quartz/i18n/locales/zh-CN.ts b/quartz/i18n/locales/zh-CN.ts new file mode 100644 index 0000000..43d0111 --- /dev/null +++ b/quartz/i18n/locales/zh-CN.ts @@ -0,0 +1,81 @@ +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: "Created with", + }, + 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: "私有笔记或笔记不存在。", + }, + folderContent: { + folder: "文件夹", + itemsUnderFolder: ({ count }) => `此文件夹下有${count}条笔记。`, + }, + tagContent: { + tag: "标签", + tagIndex: "标签索引", + itemsUnderTag: ({ count }) => `此标签下有${count}条笔记。`, + showingFirst: ({ count }) => `显示前${count}个标签。`, + totalTags: ({ count }) => `总共有${count}个标签。`, + }, + }, +} as const satisfies Translation diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx index 332c758..3eb6975 100644 --- a/quartz/plugins/emitters/tagPage.tsx +++ b/quartz/plugins/emitters/tagPage.tsx @@ -35,9 +35,26 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) getQuartzComponents() { return [Head, Header, Body, ...header, ...beforeBody, pageBody, ...left, ...right, Footer] }, - async getDependencyGraph(ctx, _content, _resources) { - // TODO implement - return new DepGraph() + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + for (const [_tree, file] of content) { + const sourcePath = file.data.filePath! + const tags = (file.data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes) + // if the file has at least one tag, it is used in the tag index page + if (tags.length > 0) { + tags.push("index") + } + + for (const tag of tags) { + graph.addEdge( + sourcePath, + joinSegments(ctx.argv.output, "tags", tag + ".html") as FilePath, + ) + } + } + + return graph }, async emit(ctx, content, resources): Promise { const fps: FilePath[] = [] diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index 7073d43..79aa5f3 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -57,9 +57,9 @@ export const FrontMatter: QuartzTransformerPlugin | undefined> }, }) - if (data.title) { + if (data.title != null && data.title.toString() !== "") { data.title = data.title.toString() - } else if (data.title === null || data.title === undefined) { + } else { data.title = file.stem ?? i18n(cfg.configuration.locale).propertyDefaults.title }