diff --git a/package-lock.json b/package-lock.json index 8ce3bb378..901d9f536 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "vfile": "^6.0.3", "workerpool": "^10.0.1", "ws": "^8.19.0", + "yaml": "^2.8.2", "yargs": "^18.0.0" }, "bin": { @@ -8242,6 +8243,21 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "18.0.0", "license": "MIT", diff --git a/package.json b/package.json index d23f7647c..72d37ce96 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "vfile": "^6.0.3", "workerpool": "^10.0.1", "ws": "^8.19.0", + "yaml": "^2.8.2", "yargs": "^18.0.0" }, "devDependencies": { diff --git a/quartz.config.default.yaml b/quartz.config.default.yaml new file mode 100644 index 000000000..d28474d38 --- /dev/null +++ b/quartz.config.default.yaml @@ -0,0 +1,228 @@ +# yaml-language-server: $schema=./quartz/plugins/quartz-plugins.schema.json +configuration: + pageTitle: Quartz 5 + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: + provider: plausible + locale: en-US + baseUrl: quartz.jzhao.xyz + ignorePatterns: + - private + - templates + - .obsidian + defaultDateType: modified + theme: + fontOrigin: googleFonts + cdnCaching: true + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + enableInHtmlEmbed: false + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: false + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: true + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 30 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: true + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/spacer + enabled: true + layout: + position: left + priority: 15 + display: mobile-only + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: false + options: + provider: giscus + options: {} + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: false +layout: + groups: + toolbar: + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} diff --git a/quartz.plugins.default.json b/quartz.plugins.default.json deleted file mode 100644 index ab43c0085..000000000 --- a/quartz.plugins.default.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "$schema": "./quartz/plugins/quartz-plugins.schema.json", - "configuration": { - "pageTitle": "Quartz 5", - "pageTitleSuffix": "", - "enableSPA": true, - "enablePopovers": true, - "analytics": { "provider": "plausible" }, - "locale": "en-US", - "baseUrl": "quartz.jzhao.xyz", - "ignorePatterns": ["private", "templates", ".obsidian"], - "defaultDateType": "modified", - "theme": { - "fontOrigin": "googleFonts", - "cdnCaching": true, - "typography": { - "header": "Schibsted Grotesk", - "body": "Source Sans Pro", - "code": "IBM Plex Mono" - }, - "colors": { - "lightMode": { - "light": "#faf8f8", - "lightgray": "#e5e5e5", - "gray": "#b8b8b8", - "darkgray": "#4e4e4e", - "dark": "#2b2b2b", - "secondary": "#284b63", - "tertiary": "#84a59d", - "highlight": "rgba(143, 159, 169, 0.15)", - "textHighlight": "#fff23688" - }, - "darkMode": { - "light": "#161618", - "lightgray": "#393639", - "gray": "#646464", - "darkgray": "#d4d4d4", - "dark": "#ebebec", - "secondary": "#7b97aa", - "tertiary": "#84a59d", - "highlight": "rgba(143, 159, 169, 0.15)", - "textHighlight": "#b3aa0288" - } - } - } - }, - "plugins": [ - { - "source": "github:quartz-community/created-modified-date", - "enabled": true, - "options": { "priority": ["frontmatter", "git", "filesystem"] }, - "order": 10 - }, - { - "source": "github:quartz-community/syntax-highlighting", - "enabled": true, - "options": { - "theme": { "light": "github-light", "dark": "github-dark" }, - "keepBackground": false - }, - "order": 20 - }, - { - "source": "github:quartz-community/obsidian-flavored-markdown", - "enabled": true, - "options": { "enableInHtmlEmbed": false, "enableCheckbox": true }, - "order": 30 - }, - { - "source": "github:quartz-community/github-flavored-markdown", - "enabled": true, - "order": 40 - }, - { - "source": "github:quartz-community/table-of-contents", - "enabled": true, - "order": 50 - }, - { - "source": "github:quartz-community/crawl-links", - "enabled": true, - "options": { "markdownLinkResolution": "shortest" }, - "order": 60 - }, - { - "source": "github:quartz-community/description", - "enabled": true, - "order": 70 - }, - { - "source": "github:quartz-community/latex", - "enabled": true, - "options": { "renderEngine": "katex" }, - "order": 80 - }, - { - "source": "github:quartz-community/citations", - "enabled": false, - "order": 85 - }, - { - "source": "github:quartz-community/hard-line-breaks", - "enabled": false, - "order": 90 - }, - { - "source": "github:quartz-community/ox-hugo", - "enabled": false, - "order": 91 - }, - { - "source": "github:quartz-community/roam", - "enabled": false, - "order": 92 - }, - { - "source": "github:quartz-community/remove-draft", - "enabled": true - }, - { - "source": "github:quartz-community/explicit-publish", - "enabled": false - }, - { - "source": "github:quartz-community/alias-redirects", - "enabled": true - }, - { - "source": "github:quartz-community/content-index", - "enabled": true, - "options": { "enableSiteMap": true, "enableRSS": true } - }, - { - "source": "github:quartz-community/favicon", - "enabled": true - }, - { - "source": "github:quartz-community/og-image", - "enabled": true - }, - { - "source": "github:quartz-community/cname", - "enabled": true - }, - { - "source": "github:quartz-community/canvas-page", - "enabled": true - }, - { - "source": "github:quartz-community/content-page", - "enabled": true - }, - { - "source": "github:quartz-community/folder-page", - "enabled": true - }, - { - "source": "github:quartz-community/tag-page", - "enabled": true - }, - { - "source": "github:quartz-community/explorer", - "enabled": true, - "layout": { - "position": "left", - "priority": 50 - } - }, - { - "source": "github:quartz-community/graph", - "enabled": true, - "layout": { - "position": "right", - "priority": 10 - } - }, - { - "source": "github:quartz-community/search", - "enabled": true, - "layout": { - "position": "left", - "priority": 20, - "group": "toolbar", - "groupOptions": { "grow": true } - } - }, - { - "source": "github:quartz-community/backlinks", - "enabled": true, - "layout": { - "position": "right", - "priority": 30 - } - }, - { - "source": "github:quartz-community/article-title", - "enabled": true, - "layout": { - "position": "beforeBody", - "priority": 10 - } - }, - { - "source": "github:quartz-community/content-meta", - "enabled": true, - "layout": { - "position": "beforeBody", - "priority": 20 - } - }, - { - "source": "github:quartz-community/tag-list", - "enabled": true, - "layout": { - "position": "beforeBody", - "priority": 30 - } - }, - { - "source": "github:quartz-community/page-title", - "enabled": true, - "layout": { - "position": "left", - "priority": 10 - } - }, - { - "source": "github:quartz-community/darkmode", - "enabled": true, - "layout": { - "position": "left", - "priority": 30, - "group": "toolbar" - } - }, - { - "source": "github:quartz-community/reader-mode", - "enabled": true, - "layout": { - "position": "left", - "priority": 35, - "group": "toolbar" - } - }, - { - "source": "github:quartz-community/spacer", - "enabled": true, - "layout": { - "position": "left", - "priority": 15, - "display": "mobile-only" - } - }, - { - "source": "github:quartz-community/breadcrumbs", - "enabled": true, - "layout": { - "position": "beforeBody", - "priority": 5, - "condition": "not-index" - } - }, - { - "source": "github:quartz-community/comments", - "enabled": false, - "options": { "provider": "giscus", "options": {} }, - "layout": { - "position": "afterBody", - "priority": 10 - } - }, - { - "source": "github:quartz-community/footer", - "enabled": true, - "options": { - "links": { - "GitHub": "https://github.com/jackyzha0/quartz", - "Discord Community": "https://discord.gg/cRFFHYye7t" - } - } - }, - { - "source": "github:quartz-community/recent-notes", - "enabled": false - } - ], - "layout": { - "groups": { - "toolbar": { - "direction": "row", - "gap": "0.5rem" - } - }, - "byPageType": { - "content": {}, - "folder": { - "exclude": ["reader-mode"], - "positions": { - "right": [] - } - }, - "tag": { - "exclude": ["reader-mode"], - "positions": { - "right": [] - } - }, - "404": { - "positions": { - "beforeBody": [], - "left": [], - "right": [] - } - }, - "canvas": {} - } - } -} diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index 4a60e067e..d45fe3910 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -97,7 +97,7 @@ yargs(hideBin(process.argv)) .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => { await handleBuild(argv) }) - .command("migrate", "Migrate old config to quartz.plugins.json", CommonArgv, async () => { + .command("migrate", "Migrate old config to quartz.config.yaml", CommonArgv, async () => { await handleMigrate() }) .command("tui", "Launch interactive plugin manager", CommonArgv, async () => { @@ -138,7 +138,7 @@ yargs(hideBin(process.argv)) ) .command( "enable ", - "Enable plugins in quartz.plugins.json", + "Enable plugins in quartz.config.yaml", CommonArgv, async (argv) => { await handlePluginEnable(argv.names) @@ -146,7 +146,7 @@ yargs(hideBin(process.argv)) ) .command( "disable ", - "Disable plugins in quartz.plugins.json", + "Disable plugins in quartz.config.yaml", CommonArgv, async (argv) => { await handlePluginDisable(argv.names) diff --git a/quartz/cli/handlers.js b/quartz/cli/handlers.js index c65b816dc..715421b18 100644 --- a/quartz/cli/handlers.js +++ b/quartz/cli/handlers.js @@ -24,6 +24,7 @@ import { stashContentFolder, } from "./helpers.js" import { handlePluginRestore, handlePluginCheck } from "./plugin-git-handlers.js" +import { configExists, createConfigFromDefault } from "./plugin-data.js" import { UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH, @@ -217,11 +218,9 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. ) await fs.promises.writeFile(configFilePath, configContent) - const pluginsJsonPath = path.join(cwd, "quartz.plugins.json") - const defaultPluginsJsonPath = path.join(cwd, "quartz.plugins.default.json") - if (!fs.existsSync(pluginsJsonPath) && fs.existsSync(defaultPluginsJsonPath)) { - await fs.promises.copyFile(defaultPluginsJsonPath, pluginsJsonPath) - console.log(styleText("green", "Created quartz.plugins.json from defaults")) + if (!configExists()) { + createConfigFromDefault() + console.log(styleText("green", "Created quartz.config.yaml from defaults")) } // setup remote @@ -230,7 +229,7 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. }) outro(`You're all set! Not sure what to do next? Try: - • Customizing Quartz a bit more by editing \`quartz.plugins.json\` + • Customizing Quartz a bit more by editing \`quartz.config.yaml\` • Running \`npx quartz build --serve\` to preview your Quartz locally • Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting) `) diff --git a/quartz/cli/migrate-handler.js b/quartz/cli/migrate-handler.js index 9bc0f217c..a4763e9f8 100644 --- a/quartz/cli/migrate-handler.js +++ b/quartz/cli/migrate-handler.js @@ -2,12 +2,14 @@ import fs from "fs" import path from "path" import { spawnSync } from "child_process" import { styleText } from "util" +import YAML from "yaml" const CWD = process.cwd() const CONFIG_PATH = path.join(CWD, "quartz.config.ts") const LAYOUT_PATH = path.join(CWD, "quartz.layout.ts") -const PLUGINS_JSON_PATH = path.join(CWD, "quartz.plugins.json") -const DEFAULT_PLUGINS_JSON_PATH = path.join(CWD, "quartz.plugins.default.json") +const CONFIG_YAML_PATH = path.join(CWD, "quartz.config.yaml") +const DEFAULT_CONFIG_YAML_PATH = path.join(CWD, "quartz.config.default.yaml") +const LEGACY_DEFAULT_JSON_PATH = path.join(CWD, "quartz.plugins.default.json") const LOCKFILE_PATH = path.join(CWD, "quartz.lock.json") const PLUGINS_DIR = path.join(CWD, ".quartz", "plugins") const PACKAGE_JSON_PATH = path.join(CWD, "package.json") @@ -117,11 +119,11 @@ export async function handleMigrate() { return } - if (fs.existsSync(PLUGINS_JSON_PATH)) { - console.log(styleText("yellow", "⚠ quartz.plugins.json already exists. Overwriting.")) + if (fs.existsSync(CONFIG_YAML_PATH)) { + console.log(styleText("yellow", "⚠ quartz.config.yaml already exists. Overwriting.")) } - const defaultJson = readJson(DEFAULT_PLUGINS_JSON_PATH) + const defaultJson = readJson(DEFAULT_CONFIG_YAML_PATH) ?? readJson(LEGACY_DEFAULT_JSON_PATH) let configuration = defaultJson?.configuration ?? {} let layout = ensureLayoutDefaults(defaultJson?.layout ?? {}) let layoutInfo = null @@ -165,13 +167,13 @@ export async function handleMigrate() { } const outputJson = { - $schema: "./quartz/plugins/quartz-plugins.schema.json", configuration, plugins, layout, } - fs.writeFileSync(PLUGINS_JSON_PATH, JSON.stringify(outputJson, null, 2) + "\n") + const header = "# yaml-language-server: $schema=./quartz/plugins/quartz-plugins.schema.json\n" + fs.writeFileSync(CONFIG_YAML_PATH, header + YAML.stringify(outputJson, { lineWidth: 120 })) const configTemplate = 'import { loadQuartzConfig } from "./quartz/plugins/loader/config-loader"\n' + @@ -183,10 +185,10 @@ export async function handleMigrate() { fs.writeFileSync(CONFIG_PATH, configTemplate) fs.writeFileSync(LAYOUT_PATH, layoutTemplate) - console.log(styleText("green", "✓ Created quartz.plugins.json")) + console.log(styleText("green", "✓ Created quartz.config.yaml")) console.log(styleText("green", "✓ Replaced quartz.config.ts")) console.log(styleText("green", "✓ Replaced quartz.layout.ts")) console.log() - console.log(styleText("yellow", "⚠ Verify plugin options in quartz.plugins.json")) + console.log(styleText("yellow", "⚠ Verify plugin options in quartz.config.yaml")) console.log(styleText("gray", `Plugins migrated: ${plugins.length}`)) } diff --git a/quartz/cli/plugin-data.js b/quartz/cli/plugin-data.js index 7a7f282e4..89bc60676 100644 --- a/quartz/cli/plugin-data.js +++ b/quartz/cli/plugin-data.js @@ -1,32 +1,63 @@ import fs from "fs" import path from "path" import { execSync } from "child_process" +import YAML from "yaml" const LOCKFILE_PATH = path.join(process.cwd(), "quartz.lock.json") const PLUGINS_DIR = path.join(process.cwd(), ".quartz", "plugins") -const PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.json") -const DEFAULT_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.default.json") +const CONFIG_YAML_PATH = path.join(process.cwd(), "quartz.config.yaml") +const DEFAULT_CONFIG_YAML_PATH = path.join(process.cwd(), "quartz.config.default.yaml") -export function readPluginsJson() { - if (!fs.existsSync(PLUGINS_JSON_PATH)) return null +const LEGACY_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.json") +const LEGACY_DEFAULT_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.default.json") + +function resolveConfigPath() { + if (fs.existsSync(CONFIG_YAML_PATH)) return CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_PLUGINS_JSON_PATH)) return LEGACY_PLUGINS_JSON_PATH + return CONFIG_YAML_PATH +} + +function resolveDefaultConfigPath() { + if (fs.existsSync(DEFAULT_CONFIG_YAML_PATH)) return DEFAULT_CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_DEFAULT_PLUGINS_JSON_PATH)) return LEGACY_DEFAULT_PLUGINS_JSON_PATH + return DEFAULT_CONFIG_YAML_PATH +} + +function readFileAsData(filePath) { + if (!fs.existsSync(filePath)) return null try { - return JSON.parse(fs.readFileSync(PLUGINS_JSON_PATH, "utf-8")) + const raw = fs.readFileSync(filePath, "utf-8") + if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) { + return YAML.parse(raw) + } + return JSON.parse(raw) } catch { return null } } +function writeDataToFile(filePath, data) { + if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) { + const header = "# yaml-language-server: $schema=./quartz/plugins/quartz-plugins.schema.json\n" + fs.writeFileSync(filePath, header + YAML.stringify(data, { lineWidth: 120 })) + } else { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n") + } +} + +export function readPluginsJson() { + const configPath = resolveConfigPath() + return readFileAsData(configPath) +} + export function writePluginsJson(data) { - fs.writeFileSync(PLUGINS_JSON_PATH, JSON.stringify(data, null, 2) + "\n") + const { $schema, ...rest } = data + writeDataToFile(CONFIG_YAML_PATH, rest) } export function readDefaultPluginsJson() { - if (!fs.existsSync(DEFAULT_PLUGINS_JSON_PATH)) return null - try { - return JSON.parse(fs.readFileSync(DEFAULT_PLUGINS_JSON_PATH, "utf-8")) - } catch { - return null - } + const defaultPath = resolveDefaultConfigPath() + return readFileAsData(defaultPath) } export function readLockfile() { @@ -116,7 +147,7 @@ export function ensurePluginsDir() { } /** - * Merges quartz.plugins.json, quartz.lock.json, and on-disk manifest data + * Merges quartz.config.yaml, quartz.lock.json, and on-disk manifest data * into enriched plugin entries with: name, displayName, source, enabled, * options, order, layout, category, installed, locked, manifest, * currentCommit, modified. @@ -216,15 +247,14 @@ export function addPluginEntry(entry) { } export function configExists() { - return fs.existsSync(PLUGINS_JSON_PATH) + return fs.existsSync(CONFIG_YAML_PATH) || fs.existsSync(LEGACY_PLUGINS_JSON_PATH) } export function createConfigFromDefault() { - const defaultJson = readDefaultPluginsJson() - if (!defaultJson) { + const defaultData = readDefaultPluginsJson() + if (!defaultData) { // No default available — create minimal config const minimal = { - $schema: "./quartz/plugins/quartz-plugins.schema.json", configuration: { pageTitle: "Quartz", enableSPA: true, @@ -273,8 +303,12 @@ export function createConfigFromDefault() { writePluginsJson(minimal) return minimal } - writePluginsJson(defaultJson) - return defaultJson + + const { $schema, ...rest } = defaultData + writePluginsJson(rest) + return rest } -export { LOCKFILE_PATH, PLUGINS_DIR, PLUGINS_JSON_PATH, DEFAULT_PLUGINS_JSON_PATH } +export const PLUGINS_JSON_PATH = CONFIG_YAML_PATH +export const DEFAULT_PLUGINS_JSON_PATH = DEFAULT_CONFIG_YAML_PATH +export { LOCKFILE_PATH, PLUGINS_DIR } diff --git a/quartz/cli/plugin-git-handlers.js b/quartz/cli/plugin-git-handlers.js index abe977429..847f83379 100644 --- a/quartz/cli/plugin-git-handlers.js +++ b/quartz/cli/plugin-git-handlers.js @@ -320,7 +320,7 @@ export async function handlePluginRemove(names) { export async function handlePluginEnable(names) { const json = readPluginsJson() if (!json) { - console.log(styleText("red", "✗ No quartz.plugins.json found. Cannot enable plugins.")) + console.log(styleText("red", "✗ No quartz.config.yaml found. Cannot enable plugins.")) return } @@ -329,7 +329,7 @@ export async function handlePluginEnable(names) { (e) => extractPluginName(e.source) === name || e.source === name, ) if (!entry) { - console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.plugins.json`)) + console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) continue } if (entry.enabled) { @@ -346,7 +346,7 @@ export async function handlePluginEnable(names) { export async function handlePluginDisable(names) { const json = readPluginsJson() if (!json) { - console.log(styleText("red", "✗ No quartz.plugins.json found. Cannot disable plugins.")) + console.log(styleText("red", "✗ No quartz.config.yaml found. Cannot disable plugins.")) return } @@ -355,7 +355,7 @@ export async function handlePluginDisable(names) { (e) => extractPluginName(e.source) === name || e.source === name, ) if (!entry) { - console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.plugins.json`)) + console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) continue } if (!entry.enabled) { @@ -372,13 +372,13 @@ export async function handlePluginDisable(names) { export async function handlePluginConfig(name, options = {}) { const json = readPluginsJson() if (!json) { - console.log(styleText("red", "✗ No quartz.plugins.json found.")) + console.log(styleText("red", "✗ No quartz.config.yaml found.")) return } const entry = json.plugins.find((e) => extractPluginName(e.source) === name || e.source === name) if (!entry) { - console.log(styleText("red", `✗ Plugin "${name}" not found in quartz.plugins.json`)) + console.log(styleText("red", `✗ Plugin "${name}" not found in quartz.config.yaml`)) return } diff --git a/quartz/cli/tui/components/SetupWizard.tsx b/quartz/cli/tui/components/SetupWizard.tsx index 23544ed7b..d13a64fa1 100644 --- a/quartz/cli/tui/components/SetupWizard.tsx +++ b/quartz/cli/tui/components/SetupWizard.tsx @@ -19,7 +19,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) { { key: "default" as Choice, label: "Use default configuration", - description: "Copy quartz.plugins.default.json as your starting config", + description: "Copy quartz.config.default.yaml as your starting config", }, ] : []), @@ -46,7 +46,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) { - quartz.plugins.json does not exist yet. How would you like to set up your configuration? + quartz.config.yaml does not exist yet. How would you like to set up your configuration? diff --git a/quartz/cli/tui/panels/LayoutPanel.tsx b/quartz/cli/tui/panels/LayoutPanel.tsx index e4127f195..c444ae0ee 100644 --- a/quartz/cli/tui/panels/LayoutPanel.tsx +++ b/quartz/cli/tui/panels/LayoutPanel.tsx @@ -295,7 +295,41 @@ export function LayoutPanel({ notify, onFocusChange }: LayoutPanelProps) { enterView("confirm-remove-layout") } - if (event.name === "k" && event.shift && selectedComp && selectedComponent > 0) { + if (event.name === "d" && event.shift && selectedComp) { + const arrIdx = findPluginArrayIndex(selectedComp.pluginIndex) + if (arrIdx >= 0) { + const plugin = plugins[arrIdx] + const manifest = plugin.manifest as Record | null + const components = manifest?.components as + | Record> + | undefined + if (components) { + const compEntry = Object.values(components)[0] + if (compEntry) { + const defaultPosition = (compEntry.defaultPosition as string) || "left" + const defaultPriority = (compEntry.defaultPriority as number) || 50 + updateLayout(arrIdx, { + position: defaultPosition, + priority: defaultPriority, + display: "all", + }) + notify(`Restored ${selectedComp.displayName} to defaults`, "success") + } else { + notify("No component defaults found in manifest", "error") + } + } else { + // No manifest components — restore sensible defaults + updateLayout(arrIdx, { + position: selectedComp.position, + priority: 50, + display: "all", + }) + notify(`Reset ${selectedComp.displayName} display settings`, "success") + } + } + } + + if (event.name === "up" && event.shift && selectedComp && selectedComponent > 0) { const above = currentComponents[selectedComponent - 1] const arrIdx = findPluginArrayIndex(selectedComp.pluginIndex) const aboveArrIdx = findPluginArrayIndex(above.pluginIndex) @@ -312,7 +346,7 @@ export function LayoutPanel({ notify, onFocusChange }: LayoutPanelProps) { } if ( - event.name === "j" && + event.name === "down" && event.shift && selectedComp && selectedComponent < currentComponents.length - 1 @@ -685,7 +719,7 @@ export function LayoutPanel({ notify, onFocusChange }: LayoutPanelProps) { {drillMode - ? "↑↓ select │ K/J reorder │ m move │ p priority │ v display │ c condition │ x remove │ Esc: back" + ? "↑↓ select │ ⇧↑↓ reorder │ m move │ p priority │ v display │ c condition │ x remove │ ⇧D restore │ Esc: back" : `←→ columns │ ↑↓ zones │ Enter: edit zone │ g groups │ t page-types${pageTypes.length > 1 ? " │ [ prev / ] next page type" : ""}`} diff --git a/quartz/cli/tui/panels/PluginsPanel.tsx b/quartz/cli/tui/panels/PluginsPanel.tsx index fbd122cb9..20c299111 100644 --- a/quartz/cli/tui/panels/PluginsPanel.tsx +++ b/quartz/cli/tui/panels/PluginsPanel.tsx @@ -367,11 +367,11 @@ export function PluginsPanel({ notify, onFocusChange, maxHeight }: PluginsPanelP } } - if (event.name === "a") { + if (event.name === "n") { enterView("add") } - if (event.name === "r" && selectedPlugin) { + if (event.name === "x" && selectedPlugin) { enterView("confirm-remove") } diff --git a/quartz/cli/tui/panels/SettingsPanel.tsx b/quartz/cli/tui/panels/SettingsPanel.tsx index 68ad0d4fd..e04ab0487 100644 --- a/quartz/cli/tui/panels/SettingsPanel.tsx +++ b/quartz/cli/tui/panels/SettingsPanel.tsx @@ -1,6 +1,7 @@ import { useState, useMemo, useCallback, useEffect } from "react" import { useKeyboard } from "@opentui/react" import { useSettings } from "../hooks/useSettings.js" +import { readDefaultPluginsJson } from "../../plugin-data.js" type View = "list" | "edit-string" | "edit-boolean" | "edit-enum" | "edit-array" | "edit-color" @@ -116,6 +117,37 @@ function formatStringValue(value: unknown): string { return String(value) } +function getDefaultSettingValue( + defaultConfig: Record | null, + keyPath: string[], +): unknown | undefined { + if (!defaultConfig) return undefined + let current: unknown = defaultConfig + for (const key of keyPath) { + if (current === null || current === undefined || typeof current !== "object") return undefined + current = (current as Record)[key] + } + return current +} + +function deepEqual(a: unknown, b: unknown): boolean { + if (a === b) return true + if (a === null || b === null) return false + if (typeof a !== typeof b) return false + if (typeof a !== "object") return false + if (Array.isArray(a) !== Array.isArray(b)) return false + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false + return a.every((val, i) => deepEqual(val, b[i])) + } + const keysA = Object.keys(a as Record) + const keysB = Object.keys(b as Record) + if (keysA.length !== keysB.length) return false + return keysA.every((key) => + deepEqual((a as Record)[key], (b as Record)[key]), + ) +} + export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { const { config, updateField } = useSettings() const [view, setView] = useState("list") @@ -130,6 +162,11 @@ export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { const [colorError, setColorError] = useState(null) const [collapsed, setCollapsed] = useState>(new Set()) + const defaultConfig = useMemo(() => { + const data = readDefaultPluginsJson() + return (data?.configuration as Record) ?? null + }, []) + const allEntries = useMemo(() => { if (!config) return [] return flattenConfig(config) @@ -252,6 +289,21 @@ export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { enterEdit(entry) } } + if (event.name === "d" && event.shift) { + const entry = visibleEntries[highlightedIndex] + if (!entry) return + const defaultValue = getDefaultSettingValue(defaultConfig, entry.keyPath) + if (defaultValue === undefined) { + notify("No default available for " + entry.keyPath.join("."), "error") + return + } + if (deepEqual(entry.value, defaultValue)) { + notify(entry.keyPath.join(".") + " is already default", "info") + return + } + applyValue(entry.keyPath, defaultValue) + notify("Restored " + entry.keyPath.join(".") + " to default", "success") + } }) useKeyboard((event) => { @@ -416,6 +468,10 @@ export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { } const displayFg = isHighlighted ? (valueFg ?? highlightFg) : (valueFg ?? baseFg) + const isDefault = + !entry.isObject && + deepEqual(entry.value, getDefaultSettingValue(defaultConfig, entry.keyPath)) + const defaultTag = isDefault ? " (default)" : "" return ( @@ -435,7 +491,19 @@ export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { )} {swatchColor ? : null} - {isHighlighted ? {valueText} : valueText} + + {isHighlighted ? ( + + {valueText} + {defaultTag} + + ) : ( + <> + {valueText} + {defaultTag} + + )} + ) }) @@ -705,6 +773,9 @@ export function SettingsPanel({ notify, onFocusChange }: SettingsPanelProps) { > {renderTree(false)} + + ↑↓: navigate │ Enter: edit/expand │ Shift+D: restore default + ) } diff --git a/quartz/plugins/loader/config-loader.ts b/quartz/plugins/loader/config-loader.ts index ce4e7d787..a8842be58 100644 --- a/quartz/plugins/loader/config-loader.ts +++ b/quartz/plugins/loader/config-loader.ts @@ -1,5 +1,6 @@ import fs from "fs" import path from "path" +import YAML from "yaml" import { styleText } from "util" import { QuartzConfig, GlobalConfiguration, FullPageLayout } from "../../cfg" import { QuartzComponent } from "../../components/types" @@ -18,13 +19,23 @@ import { loadComponentsFromPackage } from "./componentLoader" import { componentRegistry } from "../../components/registry" import { getCondition } from "./conditions" -const PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.json") +const CONFIG_YAML_PATH = path.join(process.cwd(), "quartz.config.yaml") +const LEGACY_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.json") +function resolveConfigPath(): string { + if (fs.existsSync(CONFIG_YAML_PATH)) return CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_PLUGINS_JSON_PATH)) return LEGACY_PLUGINS_JSON_PATH + return CONFIG_YAML_PATH +} function readPluginsJson(): QuartzPluginsJson | null { - if (!fs.existsSync(PLUGINS_JSON_PATH)) { + const configPath = resolveConfigPath() + if (!fs.existsSync(configPath)) { return null } - const raw = fs.readFileSync(PLUGINS_JSON_PATH, "utf-8") + const raw = fs.readFileSync(configPath, "utf-8") + if (configPath.endsWith(".yaml") || configPath.endsWith(".yml")) { + return YAML.parse(raw) as QuartzPluginsJson + } return JSON.parse(raw) as QuartzPluginsJson } diff --git a/quartz/plugins/loader/types.ts b/quartz/plugins/loader/types.ts index 74af1c14b..012616282 100644 --- a/quartz/plugins/loader/types.ts +++ b/quartz/plugins/loader/types.ts @@ -120,7 +120,7 @@ export type PluginSpecifier = | { name: string; options?: unknown } | { plugin: LoadedPlugin["plugin"]; manifest?: Partial } -/** Layout declaration for a component-providing plugin in quartz.plugins.json */ +/** Layout declaration for a component-providing plugin in quartz.config.yaml */ export interface PluginLayoutDeclaration { position: LayoutPosition priority: number @@ -137,7 +137,7 @@ export interface PluginLayoutDeclaration { } } -/** A single plugin entry in quartz.plugins.json */ +/** A single plugin entry in quartz.config.yaml */ export interface PluginJsonEntry { source: string enabled: boolean @@ -159,13 +159,13 @@ export interface PageTypeLayoutOverride { positions?: Partial> } -/** Top-level layout section of quartz.plugins.json */ +/** Top-level layout section of quartz.config.yaml */ export interface LayoutConfig { groups?: Record byPageType?: Record } -/** Root type for quartz.plugins.json */ +/** Root type for quartz.config.yaml */ export interface QuartzPluginsJson { $schema?: string configuration: Record