diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c93f2d4c..6c9010c5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,6 +12,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -23,16 +24,11 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] +**Device:** -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] +- OS: [e.g. iOS] +- Front Matter CMS Version [e.g. 10.2.0] +- Browser [e.g. chrome, safari] **Additional context** Add any other context about the problem here. diff --git a/.vscode/launch.json b/.vscode/launch.json index 03fa15a5..6fd45c2d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,9 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter" + ], "outFiles": ["${workspaceFolder}/dist/**/*.js"], "preLaunchTask": "npm: build:ext" }, @@ -19,7 +21,9 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter" + ], "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index c2383302..24f5c98b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,15 @@ // Place your settings in this file to overwrite default and user settings. { + "commitHelper.messages": [ + { + "type": "👨‍💻 apps", + "values": ["#search", "#profile"] + }, + { + "type": "⚙️ tasks", + "values": ["#build", "#deploy", "#skip"] + } + ], "workbench.colorCustomizations": { "titleBar.activeBackground": "#15c2cb", "titleBar.inactiveBackground": "#44ffd299", diff --git a/CHANGELOG.md b/CHANGELOG.md index d01d1bba..6098947d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Change Log +## [10.2.0] - 2024-06-12 - [Release notes](https://beta.frontmatter.codes/updates/v10.2.0) + +### ✨ New features + +- [#797](https://github.com/estruyf/vscode-front-matter/issues/797): Adding common actions at the bottom of the snippet cards + +### 🎨 Enhancements + +- [#441](https://github.com/estruyf/vscode-front-matter/issues/441): Show input descriptions for snippet and data forms +- [#442](https://github.com/estruyf/vscode-front-matter/issues/442): Hide sidebar on data view when data file is selected + show dropdown of data files +- [#788](https://github.com/estruyf/vscode-front-matter/issues/788): Show a warning on setting update when it exists in an extended configuration +- [#798](https://github.com/estruyf/vscode-front-matter/issues/798): Changed dialog to slide-over for the snippet forms +- [#799](https://github.com/estruyf/vscode-front-matter/issues/799): Added `frontMatter.logging` setting to define the logging output. Options are `info`, `warn`, `error`, and `verbose`. The default is `info`. +- [#800](https://github.com/estruyf/vscode-front-matter/issues/800): Add colors for the Front Matter CMS output +- [#808](https://github.com/estruyf/vscode-front-matter/issues/808): Add support to generate field groups and `block` fields in content type generation +- [#810](https://github.com/estruyf/vscode-front-matter/issues/810): Update the tab title based on the view +- [#811](https://github.com/estruyf/vscode-front-matter/issues/811): Added `panel.gitActions` view mode option to hide the Git actions in the panel +- [#812](https://github.com/estruyf/vscode-front-matter/issues/812): Added the `{{locale}}` placeholder, which can be used in the `previewPath` property + +### ⚡️ Optimizations + +- [#802](https://github.com/estruyf/vscode-front-matter/issues/802): Update `glob` to the latest version and remove the sync method + +### 🐞 Fixes + +- [#796](https://github.com/estruyf/vscode-front-matter/issues/796): Fix issue in retrieving folders/files on dashboard load +- [#801](https://github.com/estruyf/vscode-front-matter/issues/801): Faster folder processing on updates +- [#804](https://github.com/estruyf/vscode-front-matter/issues/804): Fix blinking of the front matter content area +- [#806](https://github.com/estruyf/vscode-front-matter/issues/804): Fix preview URL for `index.md` files in root of the page folder path +- [#809](https://github.com/estruyf/vscode-front-matter/issues/809): Fix retrieving the `filePrefix` when updating the file name on slug change + ## [10.1.0] - 2024-04-11 - [Release notes](https://beta.frontmatter.codes/updates/v10.1.0) ### ✨ New features diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index c7410152..bb6235cb 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -274,6 +274,8 @@ "dashboard.preview.button.refresh.title": "Refresh", "dashboard.preview.button.open.title": "Open", + "dashboard.snippetsView.item.type.content": "Content snippet", + "dashboard.snippetsView.item.type.media": "Media snippet", "dashboard.snippetsView.item.quickAction.editSnippet": "Edit snippet", "dashboard.snippetsView.item.quickAction.deleteSnippet": "Delete snippet", "dashboard.snippetsView.item.quickAction.viewSnippet": "View snippet file", @@ -725,6 +727,7 @@ "helpers.settingsHelper.readConfig.progress.title": "{0}: Reading dynamic config file...", "helpers.settingsHelper.readConfig.error": "Error reading your configuration.", "helpers.settingsHelper.refreshConfig.success": "Settings have been refreshed.", + "helpers.settingsHelper.safeUpdate.warning": "Cannot update setting \"{0}\" because you've extended or split the Front Matter CMS configuration. Please manually add your changes. Check the output for the setting update.", "helpers.taxonomyHelper.rename.input.title": "Rename the {0}", "helpers.taxonomyHelper.rename.validate.equalValue": "The new value must be different from the old one.", diff --git a/package-lock.json b/package-lock.json index de7ee19e..ccdf1577 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "10.1.0", + "version": "10.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "10.1.0", + "version": "10.2.0", "license": "MIT", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.0.6" @@ -21,7 +21,6 @@ "@sentry/react": "^6.19.7", "@sentry/tracing": "^6.19.7", "@tailwindcss/forms": "^0.5.3", - "@types/glob": "7.1.3", "@types/invariant": "^2.2.35", "@types/js-yaml": "^4.0.9", "@types/lodash.omit": "^4.5.7", @@ -54,7 +53,7 @@ "eslint": "^8.33.0", "fuse.js": "6.5.3", "github-directory-downloader": "^1.3.6", - "glob": "7.1.6", + "glob": "^10.3.12", "gray-matter": "4.0.3", "html-loader": "1.3.2", "html-webpack-plugin": "4.5.0", @@ -1813,16 +1812,6 @@ "@types/send": "*" } }, - "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "node_modules/@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -1925,12 +1914,6 @@ "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", "dev": true }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", @@ -3043,13 +3026,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -3057,7 +3040,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -3590,9 +3573,9 @@ } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -4590,17 +4573,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4867,9 +4850,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -5110,20 +5093,22 @@ } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5147,6 +5132,30 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -8534,12 +8543,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -8550,9 +8559,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -9174,9 +9183,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -10551,6 +10560,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11442,52 +11471,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11969,9 +11952,9 @@ } }, "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -12595,9 +12578,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", diff --git a/package.json b/package.json index 509933d6..0784479f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter CMS", "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "10.1.0", + "version": "10.2.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -78,7 +78,8 @@ "key": "ctrl+r", "mac": "cmd+r", "when": "activeWebviewPanelId == frontMatterDashboard" - }, { + }, + { "command": "frontMatter.insertMedia", "key": "ctrl+shift+i", "mac": "cmd+shift+i", @@ -289,6 +290,10 @@ "default": null, "description": "%setting.frontMatter.content.pageFolders.items.properties.previewPath.description%" }, + "trailingSlash": { + "type": "boolean", + "description": "%setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description%" + }, "filePrefix": { "type": [ "null", @@ -1170,6 +1175,12 @@ "markdownDescription": "%setting.frontMatter.preview.pathName.markdownDescription%", "scope": "Site preview" }, + "frontMatter.preview.trailingSlash": { + "type": "boolean", + "default": "", + "markdownDescription": "%setting.frontMatter.preview.trailingSlash.markdownDescription%", + "scope": "Site preview" + }, "frontMatter.site.baseURL": { "type": "string", "default": "", @@ -1714,6 +1725,10 @@ "default": null, "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description%" }, + "trailingSlash": { + "type": "boolean", + "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description%" + }, "slugTemplate": { "type": [ "null", @@ -1987,6 +2002,16 @@ "frontMatter.website.host": { "type": "string", "markdownDescription": "%setting.frontMatter.website.host.markdownDescription%" + }, + "frontMatter.logging": { + "type": "string", + "default": "info", + "enum": [ + "error", + "warn", + "info", + "verbose" + ] } } }, @@ -2640,13 +2665,25 @@ } ] }, - "grammars": [{ - "path": "./syntaxes/hugo.tmLanguage.json", - "scopeName": "frontmatter.markdown.hugo", - "injectTo": [ - "text.html.markdown" + "languages": [{ + "id": "frontmatter.project.output", + "mimetypes": [ + "text/x-code-output" ] }], + "grammars": [{ + "path": "./syntaxes/hugo.tmLanguage.json", + "scopeName": "frontmatter.markdown.hugo", + "injectTo": [ + "text.html.markdown" + ] + }, + { + "language": "frontmatter.project.output", + "scopeName": "frontmatter.project.output", + "path": "./syntaxes/frontmatter-output.tmLanguage.json" + } + ], "walkthroughs": [{ "id": "frontmatter.welcome", "title": "Get started with Front Matter", @@ -2719,7 +2756,6 @@ "@sentry/react": "^6.19.7", "@sentry/tracing": "^6.19.7", "@tailwindcss/forms": "^0.5.3", - "@types/glob": "7.1.3", "@types/invariant": "^2.2.35", "@types/js-yaml": "^4.0.9", "@types/lodash.omit": "^4.5.7", @@ -2752,7 +2788,7 @@ "eslint": "^8.33.0", "fuse.js": "6.5.3", "github-directory-downloader": "^1.3.6", - "glob": "7.1.6", + "glob": "^10.3.12", "gray-matter": "4.0.3", "html-loader": "1.3.2", "html-webpack-plugin": "4.5.0", diff --git a/package.nls.json b/package.nls.json index a0f96076..115dc96c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -73,11 +73,13 @@ "setting.frontMatter.content.pageFolders.items.properties.path.description": "Path of the folder", "setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description": "Exclude sub-directories", "setting.frontMatter.content.pageFolders.items.properties.previewPath.description": "Defines a custom preview path for the folder.", + "setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.", "setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "Defines a prefix for the file name.", "setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "Defines which content types can be used for the current location. If not defined, all content types will be available.", "setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "Disable the creation of new content in the folder.", "setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "Set the default locale ID for the page folder. All content from this folder is translatable to the languages defined in the `frontMatter.content.i18n` setting.", "setting.frontMatter.content.pageFolders.items.properties.locales.description": "Define the locales for the page folder. This will be used for the translation of the content.", + "setting.frontMatter.content.pageFolders.items.properties.slugTemplate.description": "Defines a custom slug template.", "setting.frontMatter.content.i18n.markdownDescription": "Specify the locales you want to use for your website. This setting can be overwritten on page folder level. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)", "setting.frontMatter.content.i18n.items.properties.title.description": "Title of the locale", "setting.frontMatter.content.i18n.items.properties.locale.description": "Locale code", @@ -175,6 +177,7 @@ "setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)", "setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)", "setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)", + "setting.frontMatter.preview.trailingSlash.markdownDescription": "Specify if you want to add a trailing slash to the preview URL. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.trailingslash)", "setting.frontMatter.preview.pathName.markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.pathname)", "setting.frontMatter.site.baseURL.markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.site.baseurl)", "setting.frontMatter.taxonomy.alignFilename.markdownDescription": "Align the filename with the new slug when it gets generated. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.alignfilename)", @@ -229,6 +232,7 @@ "setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "Specify if the comparison is case sensitive. Default: true", "setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "Specify if you want to create a folder when creating new content.", "setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "Defines a custom preview path for the content type.", + "setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.", "setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "Defines a custom slug template for the content type.", "setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "An optional template that can be used for creating new content.", "setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "An optional post script that can be used after new content creation.", diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 5b7b0028..ffb19993 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -27,6 +27,7 @@ import { CustomPlaceholder, Field } from '../models'; import { format } from 'date-fns'; import { ArticleHelper, + Logger, Settings, SlugHelper, processArticlePlaceholdersFromData, @@ -80,7 +81,7 @@ export class Article { return; } - article = this.updateDate(article); + article = await this.updateDate(article); try { ArticleHelper.update(editor, article); @@ -95,8 +96,8 @@ export class Article { * Update the date in the front matter * @param article */ - public static updateDate(article: ParsedFrontMatter) { - article.data = ArticleHelper.updateDates(article); + public static async updateDate(article: ParsedFrontMatter) { + article.data = await ArticleHelper.updateDates(article); return article; } @@ -109,7 +110,7 @@ export class Article { return; } - const updatedArticle = this.setLastModifiedDateInner(editor.document); + const updatedArticle = await this.setLastModifiedDateInner(editor.document); if (typeof updatedArticle === 'undefined') { return; @@ -119,7 +120,7 @@ export class Article { } public static async setLastModifiedDateOnSave(document: TextDocument): Promise { - const updatedArticle = this.setLastModifiedDateInner(document); + const updatedArticle = await this.setLastModifiedDateInner(document); if (typeof updatedArticle === 'undefined') { return []; @@ -130,7 +131,10 @@ export class Article { return [update]; } - private static setLastModifiedDateInner(document: TextDocument): ParsedFrontMatter | undefined { + private static async setLastModifiedDateInner( + document: TextDocument + ): Promise { + Logger.verbose(`Article:setLastModifiedDateInner:Start`); const article = ArticleHelper.getFrontMatterFromDocument(document); // Only set the date, if there is already front matter set @@ -139,10 +143,17 @@ export class Article { } const cloneArticle = Object.assign({}, article); - const dateField = ArticleHelper.getModifiedDateField(article); + const dateField = await ArticleHelper.getModifiedDateField(article); + Logger.verbose(`Article:setLastModifiedDateInner:DateField - ${JSON.stringify(dateField)}`); + try { const fieldName = dateField?.name || DefaultFields.LastModified; - cloneArticle.data[fieldName] = Article.formatDate(new Date(), dateField?.dateFormat); + const fieldValue = Article.formatDate(new Date(), dateField?.dateFormat); + cloneArticle.data[fieldName] = fieldValue; + Logger.verbose( + `Article:setLastModifiedDateInner:DateField name - ${fieldName} - value - ${fieldValue}` + ); + Logger.verbose(`Article:setLastModifiedDateInner:End`); return cloneArticle; } catch (e: unknown) { Notifications.error( @@ -195,8 +206,12 @@ export class Article { } let filePrefix = Settings.get(SETTING_TEMPLATES_PREFIX); - const contentType = ArticleHelper.getContentType(article); - filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType); + const contentType = await ArticleHelper.getContentType(article); + filePrefix = await ArticleHelper.getFilePrefix( + filePrefix, + editor.document.uri.fsPath, + contentType + ); const titleField = 'title'; const articleTitle: string = article.data[titleField]; @@ -285,6 +300,13 @@ export class Article { return; } + const file = parseWinPath(editor.document.fileName); + if (!isValidFile(file)) { + return; + } + + const parsedFile = parse(file); + const slugTemplate = Settings.get(SETTING_SLUG_TEMPLATE); if (slugTemplate) { if (slugTemplate === '{{title}}') { @@ -300,16 +322,11 @@ export class Article { } } - const file = parseWinPath(editor.document.fileName); - - if (!isValidFile(file)) { - return; - } - - const parsedFile = parse(file); + const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string; + const prefix = Settings.get(SETTING_SLUG_PREFIX) as string; if (parsedFile.name.toLowerCase() !== 'index') { - return parsedFile.name; + return `${prefix}${parsedFile.name}${suffix}`; } const folderName = basename(dirname(file)); @@ -344,7 +361,7 @@ export class Article { const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE); // Is article located in one of the content folders - const folders = Folders.get(); + const folders = Folders.getCached(); const documentPath = parseWinPath(document.fileName); const folder = folders.find((f) => documentPath.startsWith(f.path)); if (!folder) { @@ -363,11 +380,16 @@ export class Article { public static formatDate(dateValue: Date, fieldDateFormat?: string): string { const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; + Logger.verbose(`Article:formatDate:Start`); + if (fieldDateFormat) { + Logger.verbose(`Article:formatDate:FieldDateFormat - ${fieldDateFormat}`); return format(dateValue, DateHelper.formatUpdate(fieldDateFormat) as string); } else if (dateFormat && typeof dateFormat === 'string') { + Logger.verbose(`Article:formatDate:DateFormat - ${dateFormat}`); return format(dateValue, DateHelper.formatUpdate(dateFormat) as string); } else { + Logger.verbose(`Article:formatDate:toISOString - ${dateValue}`); return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString(); @@ -385,7 +407,7 @@ export class Article { const article = ArticleHelper.getFrontMatter(editor); const contentType = - article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE; + article && article.data ? await ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE; const position = editor.selection.active; const selectionText = editor.document.getText(editor.selection); @@ -463,7 +485,7 @@ export class Article { } const article = ArticleHelper.getFrontMatter(editor); - const contentType = article ? ArticleHelper.getContentType(article) : undefined; + const contentType = article ? await ArticleHelper.getContentType(article) : undefined; await commands.executeCommand(COMMAND_NAME.dashboard, { type: NavigationType.Snippets, diff --git a/src/commands/Content.ts b/src/commands/Content.ts index b5201a24..9f056681 100644 --- a/src/commands/Content.ts +++ b/src/commands/Content.ts @@ -1,10 +1,25 @@ import { commands, QuickPickItem, window } from 'vscode'; import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants'; -import { Settings } from '../helpers'; +import { Extension, Settings } from '../helpers'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; export class Content { + /** + * Registers the commands for the Content class. + */ + public static async registerCommands() { + const ext = Extension.getInstance(); + const subscriptions = ext.subscriptions; + + subscriptions.push(commands.registerCommand(COMMAND_NAME.createContent, Content.create)); + } + + /** + * Creates content based on user selection. + * If templates are enabled, shows a quick pick menu to choose between content type and template. + * If templates are disabled, executes the createByContentType command directly. + */ public static async create() { const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED); if (!templatesEnabled) { diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 598b1ef5..aaf2443b 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -34,6 +34,7 @@ import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; import { DashboardMessage } from '../dashboardWebView/DashboardMessage'; import { NavigationType } from '../dashboardWebView/models'; +import { ignoreMsgCommand } from '../utils'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -44,6 +45,13 @@ export class Dashboard { return Dashboard._viewData; } + public static setTitle(title: string) { + if (title && Dashboard.webview) { + Dashboard.webview.title = + title || `Front Matter ${l10n.t(LocalizationKey.commandsDashboardTitle)}`; + } + } + /** * Init the dashboard */ @@ -222,7 +230,9 @@ export class Dashboard { }); Dashboard.webview.webview.onDidReceiveMessage(async (msg) => { - Logger.info(`Receiving message from webview: ${msg.command}`); + if (!ignoreMsgCommand(msg.command)) { + Logger.verbose(`Receiving message from dashboard: ${msg.command}`); + } LocalizationListener.process(msg); DashboardListener.process(msg); diff --git a/src/commands/Diagnostics.ts b/src/commands/Diagnostics.ts index 622a8fce..147c063c 100644 --- a/src/commands/Diagnostics.ts +++ b/src/commands/Diagnostics.ts @@ -1,13 +1,26 @@ import { Folders } from './Folders'; -import { ViewColumn, workspace } from 'vscode'; +import { ViewColumn, commands, workspace } from 'vscode'; import ContentProvider from '../providers/ContentProvider'; import { join } from 'path'; import { ContentFolder } from '../models'; import { Settings } from '../helpers/SettingsHelper'; +import { + COMMAND_NAME, + DEFAULT_FILE_TYPES, + SETTING_CONTENT_SUPPORTED_FILETYPES +} from '../constants'; +import { Extension } from '../helpers'; export class Diagnostics { + public static async registerCommands() { + const ext = Extension.getInstance(); + const subscriptions = ext.subscriptions; + + subscriptions.push(commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show)); + } + public static async show() { - const folders = Folders.get(); + const folders = await Folders.get(); const projectName = Folders.getProjectFolderName(); const wsFolder = Folders.getWorkspaceFolder(); @@ -18,27 +31,38 @@ export class Diagnostics { const all = await Diagnostics.allProjectFiles(); - const logging = `# Project name + const fileTypes = Diagnostics.getFileTypes(); -${projectName} + const logging = `# ${Extension.getInstance().displayName} - Diagnostics -# Folders +Beta: \`${Extension.getInstance().isBetaVersion()}\` +Version: \`${Extension.getInstance().version}\` + +## Project name -${folders.map((f) => `- ${f.title}: "${f.path}"`).join('\n')} +${projectName} -# Workspace folder +## Workspace folder -${wsFolder ? wsFolder.fsPath : 'No workspace folder'} +\`${wsFolder ? wsFolder.fsPath : 'No workspace folder'}\` -# Total files +## Total files ${all} -# Folders to search files +## Folders +| Title | Path | +| ----- | ---- | +${folders.map((f) => `| ${f.title} | \`${f.path}\` |`).join('\n')} + +### Files in folders + +| Project start length | Search in | ${fileTypes.join(` | `)} | +|--- | --- | --- | --- | --- | ${folderData.join('\n')} -# Complete frontmatter.json config +## Complete frontmatter.json config \`\`\`json ${JSON.stringify(Settings.globalConfig, null, 2)} @@ -48,8 +72,12 @@ ${JSON.stringify(Settings.globalConfig, null, 2)} ContentProvider.show(logging, `${projectName} diagnostics`, 'markdown', ViewColumn.One); } + private static getFileTypes = (): string[] => { + return Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; + }; + private static async allProjectFiles() { - const allFiles = await workspace.findFiles(`**/*.*`); + const allFiles = await workspace.findFiles(`**/*.*`, '**/node_modules/**'); return `Total files found: ${allFiles.length}`; } @@ -59,22 +87,19 @@ ${JSON.stringify(Settings.globalConfig, null, 2)} projectStart = projectStart?.replace(/\\/g, '/'); projectStart = projectStart?.startsWith('/') ? projectStart.substring(1) : projectStart; - const mdFiles = await workspace.findFiles( - join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md') - ); - const mdxFiles = await workspace.findFiles( - join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx') - ); - const markdownFiles = await workspace.findFiles( - join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown') + const fileTypes = Diagnostics.getFileTypes(); + const fileTypeLengths = await Promise.all( + fileTypes.map(async (ft) => { + const path = join(projectStart || '', folder.excludeSubdir ? '/' : '**/', `*.${ft}`); + const files = await workspace.findFiles(path, '**/node_modules/**'); + return (files || []).length; + }) ); - return `- Project start length: ${projectStart.length} | Search in: "${join( + return `| ${projectStart.length} | \`${join( projectStart, folder.excludeSubdir ? '/' : '**/', '*.*' - )}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${ - markdownFiles.length - }`; + )}\` | ${fileTypeLengths.join(` | `)} |`; } } diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 108ecde9..e2efee57 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -1,6 +1,7 @@ import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder'; import { Questions } from './../helpers/Questions'; import { + COMMAND_NAME, SETTING_CONTENT_I18N, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, @@ -14,7 +15,7 @@ import { ContentFolder, FileInfo, FolderInfo, I18nConfig, StaticFolder } from '. import uniqBy = require('lodash.uniqby'); import { Template } from './Template'; import { Notifications } from '../helpers/Notifications'; -import { Logger, Settings, processTimePlaceholders } from '../helpers'; +import { Extension, Logger, Settings, processTimePlaceholders } from '../helpers'; import { existsSync } from 'fs'; import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; @@ -25,13 +26,27 @@ import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; import { Telemetry } from '../helpers/Telemetry'; import { glob } from 'glob'; import { mkdirAsync } from '../utils/mkdirAsync'; -import { existsAsync } from '../utils'; +import { existsAsync, isWindows } from '../utils'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; export class Folders { + private static _folders: ContentFolder[] = []; + + public static async registerCommands() { + const ext = Extension.getInstance(); + const subscriptions = ext.subscriptions; + + subscriptions.push(commands.registerCommand(COMMAND_NAME.createByTemplate, Folders.create)); + } + + public static clearCached() { + Logger.verbose(`Folders:clearCached`); + Folders._folders = []; + } + /** * Add a media folder * @returns @@ -98,7 +113,8 @@ export class Folders { return; } - const folders = Folders.get().filter((f) => !f.disableCreation); + let folders = await Folders.get(); + folders = folders.filter((f) => !f.disableCreation); const location = folders.find((f) => f.path === selectedFolder.path); if (location) { const folderPath = Folders.getFolderPath(Uri.file(location.path)); @@ -122,7 +138,7 @@ export class Folders { if (folder && folder.fsPath) { const wslPath = folder.fsPath.replace(/\//g, '\\'); - let folders = Folders.get(); + let folders = await Folders.get(); const exists = folders.find( (f) => f.path.includes(folder.fsPath) || f.path.includes(wslPath) @@ -171,7 +187,7 @@ export class Folders { */ public static async unregister(folder: Uri) { if (folder && folder.path) { - let folders = Folders.get(); + let folders = await Folders.get(); folders = folders.filter((f) => f.path !== folder.fsPath); await Folders.update(folders); @@ -282,8 +298,9 @@ export class Folders { * Get the registered folders information */ public static async getInfo(limit?: number): Promise { + Logger.verbose('Folders:getInfo:start'); const supportedFiles = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES); - const folders = Folders.get(); + const folders = await Folders.get(); if (folders && folders.length > 0) { const folderInfo: FolderInfo[] = []; @@ -295,9 +312,11 @@ export class Folders { } } + Logger.verbose('Folders:getInfo:end'); return folderInfo; } + Logger.verbose('Folders:getInfo:end - no folders found'); return null; } @@ -305,7 +324,14 @@ export class Folders { * Get the folder settings * @returns */ - public static get(): ContentFolder[] { + public static async get(): Promise { + Logger.verbose('Folders:get:start'); + + if (Folders._folders.length > 0) { + Logger.verbose('Folders:get:end - cached folders'); + return Folders._folders; + } + const wsFolder = Folders.getWorkspaceFolder(); let folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[]; const i18nSettings = Settings.get(SETTING_CONTENT_I18N); @@ -322,7 +348,7 @@ export class Folders { folders = folders.filter((f) => f.path !== folder.path); const folderPath = Folders.absWsFolder(folder, wsFolder); - const subFolders = glob.sync(folderPath, { ignore: '**/node_modules/**' }); + const subFolders = await Folders.findFolders(folderPath); for (const subFolder of subFolders) { const subFolderPath = parseWinPath(subFolder); @@ -353,11 +379,11 @@ export class Folders { ), l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction), l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction) - ).then((answer) => { + ).then(async (answer) => { if ( answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction) ) { - const folders = Folders.get(); + const folders = await Folders.get(); Folders.update(folders.filter((f) => f.path !== folder.path)); } else if ( answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction) @@ -417,7 +443,17 @@ export class Folders { } }); - return contentFolders.filter((folder) => folder !== null) as ContentFolder[]; + Logger.verbose('Folders:get:end'); + Folders._folders = contentFolders.filter((folder) => folder !== null) as ContentFolder[]; + return Folders._folders; + } + + /** + * Get the cached folder settings + * @returns {ContentFolder[]} - The cached folder settings + */ + public static getCached(): ContentFolder[] { + return Folders._folders; } /** @@ -439,9 +475,8 @@ export class Folders { path: Folders.relWsFolder(folder, wsFolder) }; - if (detail['$schema'] || detail.extended) { - return null; - } + delete detail['$schema']; + delete detail.extended; if (detail.locale && detail.locale === detail.defaultLocale) { // Check if the folder was on the original list @@ -462,7 +497,7 @@ export class Folders { }) .filter((folder) => folder !== null); - await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true); + await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true); // Reinitialize the folder listeners PagesListener.startWatchers(); @@ -475,11 +510,10 @@ export class Folders { */ public static getAbsFilePath(filePath: string): string { const wsFolder = Folders.getWorkspaceFolder(); - const isWindows = process.platform === 'win32'; if (filePath.includes(WORKSPACE_PLACEHOLDER)) { let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || '')); - absPath = isWindows ? absPath.split('/').join('\\') : absPath; + absPath = isWindows() ? absPath.split('/').join('\\') : absPath; return parseWinPath(absPath); } @@ -493,7 +527,6 @@ export class Folders { */ public static getAbsFolderPath(folderPath: string): string { const wsFolder = Folders.getWorkspaceFolder(); - const isWindows = process.platform === 'win32'; let absPath = ''; if (folderPath.includes(WORKSPACE_PLACEHOLDER)) { @@ -502,7 +535,7 @@ export class Folders { absPath = join(parseWinPath(wsFolder?.fsPath || ''), folderPath); } - absPath = isWindows ? absPath.split('/').join('\\') : absPath; + absPath = isWindows() ? absPath.split('/').join('\\') : absPath; return parseWinPath(absPath); } @@ -513,14 +546,13 @@ export class Folders { * @returns */ private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) { - const isWindows = process.platform === 'win32'; let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || '')); if (absPath.includes('../')) { absPath = join(absPath); } - absPath = isWindows ? absPath.split('/').join('\\') : absPath; + absPath = isWindows() ? absPath.split('/').join('\\') : absPath; return parseWinPath(absPath); } @@ -532,12 +564,11 @@ export class Folders { * @returns */ public static relWsFolder(folder: ContentFolder, wsFolder?: Uri) { - const isWindows = process.platform === 'win32'; let absPath = parseWinPath(folder.path).replace( parseWinPath(wsFolder?.fsPath || ''), WORKSPACE_PLACEHOLDER ); - absPath = isWindows ? absPath.split('\\').join('/') : absPath; + absPath = isWindows() ? absPath.split('\\').join('/') : absPath; return absPath; } @@ -545,9 +576,11 @@ export class Folders { * Find the content folders */ public static async getContentFolders() { + Logger.verbose('Folders:getContentFolders:start'); // Find folders that contain files const wsFolder = Folders.getWorkspaceFolder(); if (!wsFolder) { + Logger.error('Folders:getContentFolders:workspaceFolderNotFound'); return []; } @@ -568,7 +601,7 @@ export class Folders { folders = [...folders, ...(await this.findFolders(pattern))]; } catch (e) { Logger.error( - `Something went wrong while searching for folders with pattern "${pattern}": ${ + `Folders:getContentFolders:error: Something went wrong while searching for folders with pattern "${pattern}": ${ (e as Error).message }` ); @@ -581,6 +614,8 @@ export class Folders { } const uniqueFolders = [...new Set(folders)]; + + Logger.verbose('Folders:getContentFolders:end'); return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder)); } @@ -589,8 +624,8 @@ export class Folders { * @param folderPath * @returns */ - public static getFilePrefixByFolderPath(folderPath: string) { - const folders = Folders.get(); + public static async getFilePrefixByFolderPath(folderPath: string) { + const folders = await Folders.get(); const pageFolder = folders.find((f) => parseWinPath(f.path) === parseWinPath(folderPath)); if (pageFolder && typeof pageFolder.filePrefix !== 'undefined') { @@ -605,8 +640,8 @@ export class Folders { * @param filePath * @returns */ - public static getFilePrefixBeFilePath(filePath: string) { - const folders = Folders.get(); + public static async getFilePrefixBeFilePath(filePath: string) { + const folders = await Folders.get(); if (folders.length > 0) { filePath = parseWinPath(filePath); @@ -634,8 +669,10 @@ export class Folders { * @param filePath - The file path to match against the page folders. * @returns The page folder that matches the file path, or undefined if no match is found. */ - public static getPageFolderByFilePath(filePath: string): ContentFolder | undefined { - const folders = Folders.get(); + public static async getPageFolderByFilePath( + filePath: string + ): Promise { + const folders = await Folders.get(); const parsedPath = parseWinPath(filePath); const pageFolderMatches = folders .filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path)) @@ -648,6 +685,27 @@ export class Folders { return; } + /** + * Retrieves the file stats for a given file. + * @param file - The URI of the file. + * @param folderPath - The path of the folder containing the file. + * @returns An object containing the file path, file name, folder name, folder path, and file stats. + */ + public static async getFileStats(file: Uri, folderPath: string) { + const fileName = basename(file.fsPath); + const folderName = dirname(file.fsPath).split(sep).pop(); + + const stats = await workspace.fs.stat(file); + + return { + filePath: file.fsPath, + fileName, + folderName, + folderPath, + ...stats + }; + } + private static async getFilesByFolder( folder: ContentFolder, supportedFiles: string[] | undefined, @@ -655,6 +713,7 @@ export class Folders { ): Promise { try { const folderPath = parseWinPath(folder.path); + const folderUri = Uri.file(folderPath); if (typeof folderPath === 'string') { let files: Uri[] = []; @@ -673,7 +732,9 @@ export class Folders { let foundFiles = await Folders.findFiles(filePath); // Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces) - foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath)); + foundFiles = foundFiles.filter((f) => + parseWinPath(f.fsPath).startsWith(parseWinPath(folderUri.fsPath)) + ); files = [...files, ...foundFiles]; } @@ -683,17 +744,8 @@ export class Folders { for (const file of files) { try { - const fileName = basename(file.fsPath); - const folderName = dirname(file.fsPath).split(sep).pop(); - - const stats = await workspace.fs.stat(file); - - fileStats.push({ - filePath: file.fsPath, - fileName, - folderName, - ...stats - }); + const fileInfo = await Folders.getFileStats(file, folderPath); + fileStats.push(fileInfo); } catch (error) { // Skip the file } @@ -707,6 +759,7 @@ export class Folders { return { title: folder.title, + path: folderPath, files: files.length, lastModified: fileStats, locale: folder.locale, @@ -726,14 +779,20 @@ export class Folders { * @param pattern * @returns */ - private static findFolders(pattern: string): Promise { - return new Promise((resolve) => { - glob(pattern, { ignore: '**/node_modules/**', dot: true }, (err, files) => { - const allFolders = files.map((file) => dirname(file)); - const uniqueFolders = [...new Set(allFolders)]; - resolve(uniqueFolders); - }); - }); + private static async findFolders(pattern: string): Promise { + Logger.verbose(`Folders:findFolders:start - ${pattern}`); + + try { + pattern = isWindows() ? parseWinPath(pattern) : pattern; + const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true }); + const allFolders = (files || []).map((file) => dirname(file)); + const uniqueFolders = [...new Set(allFolders)]; + Logger.verbose(`Folders:findFolders:end - ${uniqueFolders.length}`); + return uniqueFolders; + } catch (e) { + Logger.error(`Folders:findFolders:error - ${(e as Error).message}`); + return []; + } } /** @@ -742,11 +801,17 @@ export class Folders { * @returns */ private static async findFiles(pattern: string): Promise { - return new Promise((resolve) => { - glob(pattern, { ignore: '**/node_modules/**' }, (err, files) => { - const allFiles = files.map((file) => Uri.file(file)); - resolve(allFiles); - }); - }); + Logger.verbose(`Folders:findFiles:start - ${pattern}`); + + try { + pattern = isWindows() ? parseWinPath(pattern) : pattern; + const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true }); + const allFiles = (files || []).map((file) => Uri.file(file)); + Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`); + return allFiles; + } catch (e) { + Logger.error(`Folders:findFiles:error - ${(e as Error).message}`); + return []; + } } } diff --git a/src/commands/Preview.ts b/src/commands/Preview.ts index f0252f1d..0072ad4b 100644 --- a/src/commands/Preview.ts +++ b/src/commands/Preview.ts @@ -1,6 +1,3 @@ -import { processFmPlaceholders } from './../helpers/processFmPlaceholders'; -import { processPathPlaceholders } from './../helpers/processPathPlaceholders'; -import { Telemetry } from './../helpers/Telemetry'; import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, @@ -9,23 +6,33 @@ import { PreviewCommands, SETTING_EXPERIMENTAL, SETTING_DATE_FORMAT, - GeneralCommands + GeneralCommands, + SETTING_PREVIEW_TRAILING_SLASH } from './../constants'; -import { ArticleHelper } from './../helpers/ArticleHelper'; import { join, parse } from 'path'; import { commands, env, Uri, ViewColumn, window, WebviewPanel, extensions } from 'vscode'; -import { Extension, parseWinPath, processTimePlaceholders, Settings } from '../helpers'; +import { + ArticleHelper, + Extension, + parseWinPath, + processI18nPlaceholders, + processTimePlaceholders, + processFmPlaceholders, + processPathPlaceholders, + Settings, + Telemetry, + processDateTimePlaceholders +} from '../helpers'; import { ContentFolder, ContentType, PreviewSettings } from '../models'; -import { format } from 'date-fns'; -import { DateHelper } from '../helpers/DateHelper'; import { Article } from '.'; -import { urlJoin } from 'url-join-ts'; import { WebviewHelper } from '@estruyf/vscode'; import { Folders } from './Folders'; import { ParsedFrontMatter } from '../parsers'; import { getLocalizationFile } from '../utils/getLocalizationFile'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; +import { joinUrl } from '../utils'; +import { i18n } from './i18n'; export class Preview { public static filePath: string | undefined = undefined; @@ -65,7 +72,7 @@ export class Preview { const localhostUrl = await this.getLocalServerUrl(); if (browserLiteCommand) { - const pageUrl = urlJoin(localhostUrl.toString(), slug || ''); + const pageUrl = joinUrl(localhostUrl.toString(), slug || ''); commands.executeCommand(browserLiteCommand, pageUrl); return; } @@ -177,7 +184,7 @@ export class Preview { Front Matter Preview -
+ + { + props.description && ( + {props.description} + ) + }
); } diff --git a/src/components/uniforms-frontmatter/DateField.tsx b/src/components/uniforms-frontmatter/DateField.tsx index 512536ec..a6fe36b2 100644 --- a/src/components/uniforms-frontmatter/DateField.tsx +++ b/src/components/uniforms-frontmatter/DateField.tsx @@ -9,7 +9,7 @@ const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8); export type DateFieldProps = HTMLFieldProps< Date, HTMLDivElement, - { inputRef?: Ref; max?: Date; min?: Date } + { inputRef?: Ref; max?: Date; min?: Date, description?: string } >; function Date({ @@ -50,6 +50,12 @@ function Date({ type="datetime-local" value={dateFormat(value) ?? ''} /> + + { + props.description && ( + {props.description} + ) + } ); } diff --git a/src/components/uniforms-frontmatter/LongTextField.tsx b/src/components/uniforms-frontmatter/LongTextField.tsx index e8ee54fc..a36cf44c 100644 --- a/src/components/uniforms-frontmatter/LongTextField.tsx +++ b/src/components/uniforms-frontmatter/LongTextField.tsx @@ -6,7 +6,7 @@ import { LabelField } from './LabelField'; export type LongTextFieldProps = HTMLFieldProps< string, HTMLDivElement, - { inputRef?: Ref } + { inputRef?: Ref, description?: string } >; function LongText({ @@ -36,6 +36,12 @@ function LongText({ ref={inputRef} value={value ?? ''} /> + + { + props.description && ( + {props.description} + ) + } ); } diff --git a/src/components/uniforms-frontmatter/NumField.tsx b/src/components/uniforms-frontmatter/NumField.tsx index e61aaeb4..09026e56 100644 --- a/src/components/uniforms-frontmatter/NumField.tsx +++ b/src/components/uniforms-frontmatter/NumField.tsx @@ -6,7 +6,7 @@ import { LabelField } from './LabelField'; export type NumFieldProps = HTMLFieldProps< number, HTMLDivElement, - { decimal?: boolean; inputRef?: Ref } + { decimal?: boolean; inputRef?: Ref, description?: string } >; function Num({ @@ -47,6 +47,12 @@ function Num({ type="number" value={value ?? ''} /> + + { + props.description && ( + {props.description} + ) + } ); } diff --git a/src/components/uniforms-frontmatter/TextField.tsx b/src/components/uniforms-frontmatter/TextField.tsx index 90e35396..b67a59c3 100644 --- a/src/components/uniforms-frontmatter/TextField.tsx +++ b/src/components/uniforms-frontmatter/TextField.tsx @@ -6,7 +6,7 @@ import { LabelField } from './LabelField'; export type TextFieldProps = HTMLFieldProps< string, HTMLDivElement, - { inputRef?: Ref } + { inputRef?: Ref, description?: string } >; function Text({ @@ -40,6 +40,12 @@ function Text({ type={type} value={value ?? ''} /> + + { + props.description && ( + {props.description} + ) + } ); } diff --git a/src/components/uniforms-frontmatter/UnknownField.tsx b/src/components/uniforms-frontmatter/UnknownField.tsx index 2d12836d..046e6ec4 100644 --- a/src/components/uniforms-frontmatter/UnknownField.tsx +++ b/src/components/uniforms-frontmatter/UnknownField.tsx @@ -8,7 +8,7 @@ import { LocalizationKey } from '../../localization'; export type UnknownFieldProps = HTMLFieldProps< string, HTMLDivElement, - { inputRef?: Ref } + { inputRef?: Ref, description?: string } >; function UnknownField({ @@ -30,6 +30,12 @@ function UnknownField({
{l10n.t(LocalizationKey.fieldUnknown)}
+ + { + props.description && ( + {props.description} + ) + } ); } diff --git a/src/constants/DefaultFeatureFlags.ts b/src/constants/DefaultFeatureFlags.ts new file mode 100644 index 00000000..28e43bf7 --- /dev/null +++ b/src/constants/DefaultFeatureFlags.ts @@ -0,0 +1,12 @@ +import { FEATURE_FLAG } from './Features'; + +export const DEFAULT_PANEL_FEATURE_FLAGS = Object.values(FEATURE_FLAG.panel).filter( + (v) => v !== FEATURE_FLAG.panel.globalSettings +); + +export const DEFAULT_DASHBOARD_FEATURE_FLAGS = [ + FEATURE_FLAG.dashboard.data.view, + FEATURE_FLAG.dashboard.taxonomy.view, + FEATURE_FLAG.dashboard.snippets.view, + FEATURE_FLAG.dashboard.snippets.manage +]; diff --git a/src/constants/Features.ts b/src/constants/Features.ts index f6a9394a..d579911f 100644 --- a/src/constants/Features.ts +++ b/src/constants/Features.ts @@ -6,7 +6,8 @@ export const FEATURE_FLAG = { metadata: 'panel.metadata', recentlyModified: 'panel.recentlyModified', otherActions: 'panel.otherActions', - contentType: 'panel.contentType' + contentType: 'panel.contentType', + gitActions: 'panel.gitActions' }, dashboard: { snippets: { diff --git a/src/constants/GeneralCommands.ts b/src/constants/GeneralCommands.ts index efa20688..429f10a5 100644 --- a/src/constants/GeneralCommands.ts +++ b/src/constants/GeneralCommands.ts @@ -27,7 +27,8 @@ export const GeneralCommands = { logging: { info: 'logInfo', warn: 'logWarn', - error: 'logError' + error: 'logError', + verbose: 'logVerbose' }, runCommand: 'runCommand', getLocalization: 'getLocalization', diff --git a/src/constants/TelemetryEvent.ts b/src/constants/TelemetryEvent.ts index f717da6f..6047f5f8 100644 --- a/src/constants/TelemetryEvent.ts +++ b/src/constants/TelemetryEvent.ts @@ -47,6 +47,8 @@ export const TelemetryEvent = { webviewContentsView: 'webviewContentsView', webviewSnippetsView: 'webviewSnippetsView', webviewTaxonomyDashboard: 'webviewTaxonomyDashboard', + webviewSettings: 'webviewSettings', + webviewUnknown: 'webviewUnknown', // Git gitSync: 'gitSync', diff --git a/src/constants/settings.ts b/src/constants/settings.ts index f24b7263..c1424792 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -52,6 +52,7 @@ export const SETTING_PANEL_ACTIONS_DISABLED = 'panel.actions.disabled'; export const SETTING_PREVIEW_HOST = 'preview.host'; export const SETTING_PREVIEW_PATHNAME = 'preview.pathName'; +export const SETTING_PREVIEW_TRAILING_SLASH = 'preview.trailingSlash'; export const SETTING_CUSTOM_SCRIPTS = 'custom.scripts'; @@ -114,6 +115,8 @@ export const SETTING_SNIPPETS_WRAPPER = 'snippets.wrapper.enabled'; export const SETTING_WEBSITE_URL = 'website.host'; +export const SETTING_LOGGING = 'logging'; + /** * Sponsors only settings */ diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 73a2fe97..84b38185 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -76,6 +76,7 @@ export enum DashboardMessage { runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', showNotification = 'showNotification', + setTitle = 'setTitle', // Settings getSettings = 'getSettings', diff --git a/src/dashboardWebView/components/App.tsx b/src/dashboardWebView/components/App.tsx index 2a2b896c..89bc355f 100644 --- a/src/dashboardWebView/components/App.tsx +++ b/src/dashboardWebView/components/App.tsx @@ -10,7 +10,7 @@ import { Media } from './Media/Media'; import { DataView } from './DataView'; import { Snippets } from './SnippetsView/Snippets'; import { FEATURE_FLAG, GeneralCommands } from '../../constants'; -import { Messenger } from '@estruyf/vscode/dist/client'; +import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; import { TaxonomyView } from './TaxonomyView'; import { Route, Routes, useNavigate } from 'react-router-dom'; import { routePaths } from '..'; @@ -70,6 +70,11 @@ export const App: React.FunctionComponent = ({ } useEffect(() => { + messageHandler.send(GeneralCommands.toVSCode.logging.verbose, { + message: `Loaded with view ${view}`, + location: 'DASHBOARD' + }); + if (view && routePaths[view]) { navigate(routePaths[view]); return; @@ -78,6 +83,22 @@ export const App: React.FunctionComponent = ({ navigate(routePaths[view]); }, [view]); + useEffect(() => { + if (settings && Object.keys(settings).length > 0) { + messageHandler.send(GeneralCommands.toVSCode.logging.verbose, { + message: `Settings loaded`, + location: 'DASHBOARD' + }); + } + + if (pages) { + messageHandler.send(GeneralCommands.toVSCode.logging.verbose, { + message: `Pages loaded - ${pages.length} pages`, + location: 'DASHBOARD' + }); + } + }, [JSON.stringify(settings), JSON.stringify(pages)]); + useEffect(() => { checkDevMode(); }, []); @@ -100,10 +121,13 @@ export const App: React.FunctionComponent = ({ onError={(error: Error, componentStack: string, eventId: string) => { Messenger.send( GeneralCommands.toVSCode.logging.error, - `Event ID: ${eventId} + { + message: `Event ID: ${eventId} Message: ${error.message} -Stack: ${componentStack}` +Stack: ${componentStack}`, + location: 'DASHBOARD' + } ); }} > diff --git a/src/dashboardWebView/components/Common/TextField.tsx b/src/dashboardWebView/components/Common/TextField.tsx index bfaaec9a..8d9a90d7 100644 --- a/src/dashboardWebView/components/Common/TextField.tsx +++ b/src/dashboardWebView/components/Common/TextField.tsx @@ -5,6 +5,7 @@ export interface ITextFieldProps { name: string; value?: string; placeholder?: string; + description?: string; icon?: JSX.Element; disabled?: boolean; autoFocus?: boolean; @@ -18,6 +19,7 @@ export const TextField: React.FunctionComponent = ({ name, value, placeholder, + description, icon, autoFocus, multiline, @@ -27,52 +29,63 @@ export const TextField: React.FunctionComponent = ({ onReset }: React.PropsWithChildren) => { return ( -
- { - icon && ( -
- {icon} -
- ) - } + <> + +
+ { + icon && ( +
+ {icon} +
+ ) + } + + { + multiline ? ( +