Merge pull request #817 from estruyf/dev

Merge for release 10.2.0
This commit is contained in:
Elio Struyf
2024-06-12 12:16:23 +02:00
committed by GitHub
102 changed files with 2081 additions and 1022 deletions
+5 -9
View File
@@ -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.
+6 -2
View File
@@ -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"]
}
]
+10
View File
@@ -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",
+31
View File
@@ -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
+3
View File
@@ -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.",
+91 -108
View File
@@ -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",
+45 -9
View File
@@ -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",
+4
View File
@@ -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.",
+43 -21
View File
@@ -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<TextEdit[]> {
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<ParsedFrontMatter | undefined> {
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<string>(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<string>(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,
+16 -1
View File
@@ -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) {
+11 -1
View File
@@ -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);
+50 -25
View File
@@ -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();
const logging = `# ${Extension.getInstance().displayName} - Diagnostics
Beta: \`${Extension.getInstance().isBetaVersion()}\`
Version: \`${Extension.getInstance().version}\`
## Project name
${projectName}
# Folders
## Workspace folder
${folders.map((f) => `- ${f.title}: "${f.path}"`).join('\n')}
\`${wsFolder ? wsFolder.fsPath : 'No workspace folder'}\`
# 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<string[]>(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(` | `)} |`;
}
}
+121 -56
View File
@@ -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<FolderInfo[] | null> {
Logger.verbose('Folders:getInfo:start');
const supportedFiles = Settings.get<string[]>(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<ContentFolder[]> {
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<I18nConfig[]>(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<ContentFolder | undefined> {
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<FolderInfo | undefined> {
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<string[]> {
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<string[]> {
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<Uri[]> {
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 [];
}
}
}
+60 -25
View File
@@ -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 {
<title>Front Matter Preview</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
<div id="app" data-type="preview" data-url="${urlJoin(
<div id="app" data-type="preview" data-url="${joinUrl(
localhostUrl.toString(),
slug || ''
)}" data-isProd="${isProd}" data-environment="${
@@ -208,7 +215,7 @@ export class Preview {
webView.webview.postMessage({
command: PreviewCommands.toWebview.updateUrl,
payload: urlJoin(localhost.toString(), slug || '')
payload: joinUrl(localhost.toString(), slug || '')
});
}
}
@@ -237,15 +244,13 @@ export class Preview {
let contentType: ContentType | undefined = undefined;
if (article?.data) {
contentType = ArticleHelper.getContentType(article);
contentType = await ArticleHelper.getContentType(article);
}
// Check if there is a pathname defined on content folder level
const folders = Folders.get();
const folders = await Folders.get();
if (folders.length > 0) {
const foldersWithPath = folders.filter((folder) => folder.previewPath);
for (const folder of foldersWithPath) {
for (const folder of folders) {
const folderPath = parseWinPath(folder.path);
if (filePath.startsWith(folderPath)) {
if (!selectedFolder || selectedFolder.path.length < folderPath.length) {
@@ -291,6 +296,11 @@ export class Preview {
slug = Article.getSlug();
}
const locale = await i18n.getLocale(filePath);
if (locale && locale.path === slug) {
slug = '';
}
if (pathname) {
// Known placeholders
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
@@ -310,10 +320,17 @@ export class Preview {
const folderPath = wsFolder ? parseWinPath(wsFolder.fsPath) : '';
const relativePath = filePath.replace(folderPath, '');
pathname = processPathPlaceholders(pathname, relativePath, filePath, selectedFolder);
pathname = processI18nPlaceholders(pathname, selectedFolder);
const file = parse(filePath);
if (file.name.toLowerCase() === 'index' && pathname.endsWith(slug)) {
slug = '';
if (file.name.toLowerCase() === 'index') {
const cleanPathName = pathname.endsWith('/')
? pathname.substring(0, pathname.length - 1)
: pathname;
if (cleanPathName.endsWith(slug) || !pathname || pathname === '/') {
slug = '';
}
}
}
@@ -321,11 +338,9 @@ export class Preview {
pathname = article?.data ? processFmPlaceholders(pathname, article?.data) : pathname;
try {
const articleDate = ArticleHelper.getDate(article);
slug = join(
format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string),
slug
);
const articleDate = await ArticleHelper.getDate(article);
pathname = processDateTimePlaceholders(pathname, dateFormat, articleDate);
slug = join(pathname, slug);
} catch (error) {
slug = join(pathname, slug);
}
@@ -339,6 +354,24 @@ export class Preview {
slug = slug.substring(0, slug.endsWith('_index') ? slug.length - 6 : slug.length - 5);
}
// Add the trailing slash
let trailingSlash = false;
if (settings.trailingSlash !== undefined) {
trailingSlash = settings.trailingSlash;
}
if (selectedFolder && selectedFolder.trailingSlash !== undefined) {
trailingSlash = selectedFolder.trailingSlash;
}
if (contentType && contentType.trailingSlash !== undefined) {
trailingSlash = contentType.trailingSlash;
}
if (trailingSlash && !slug.endsWith('/')) {
slug = `${slug}/`;
}
return slug;
}
@@ -375,10 +408,12 @@ export class Preview {
public static getSettings(): PreviewSettings {
const host = Settings.get<string>(SETTING_PREVIEW_HOST);
const pathname = Settings.get<string>(SETTING_PREVIEW_PATHNAME);
const trailingSlash = Settings.get<boolean>(SETTING_PREVIEW_TRAILING_SLASH);
return {
host,
pathname
pathname,
trailingSlash
};
}
+20 -1
View File
@@ -42,6 +42,25 @@ categories: []
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
// Initialize command
subscriptions.push(
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
await Project.init();
if (cb) {
cb();
}
})
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.initTemplate, () => Project.createSampleTemplate(true))
);
subscriptions.push(commands.registerCommand(COMMAND_NAME.registerFolder, Folders.register));
subscriptions.push(commands.registerCommand(COMMAND_NAME.unregisterFolder, Folders.unregister));
subscriptions.push(commands.registerCommand(COMMAND_NAME.createFolder, Folders.addMediaFolder));
subscriptions.push(commands.registerCommand(COMMAND_NAME.switchProject, Project.switchProject));
}
@@ -63,7 +82,7 @@ categories: []
await Settings.createTeamSettings();
// Add the default content type
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
if (sampleTemplate !== undefined) {
await Project.createSampleTemplate();
+25 -2
View File
@@ -1,14 +1,37 @@
import { TaxonomyHelper } from './../helpers/TaxonomyHelper';
import * as vscode from 'vscode';
import { TaxonomyType } from '../models';
import { EXTENSION_NAME } from '../constants';
import { ArticleHelper, FilesHelper } from '../helpers';
import { COMMAND_NAME, EXTENSION_NAME } from '../constants';
import { ArticleHelper, Extension, FilesHelper } from '../helpers';
import { FrontMatterParser } from '../parsers';
import { Notifications } from '../helpers/Notifications';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Settings {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
Settings.create(TaxonomyType.Tag);
})
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
Settings.create(TaxonomyType.Category);
})
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export)
);
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap));
}
/**
* Create a new taxonomy
*
+2 -2
View File
@@ -102,13 +102,13 @@ export class StatusListener {
* @param article
* @param collection
*/
private static verifyRequiredFields(
private static async verifyRequiredFields(
editor: vscode.TextEditor,
article: ParsedFrontMatter,
collection: vscode.DiagnosticCollection
) {
// Check for missing fields
const emptyFields = ContentType.findEmptyRequiredFields(article);
const emptyFields = await ContentType.findEmptyRequiredFields(article);
const fieldsToReport = [];
if (emptyFields && emptyFields.length > 0) {
+22 -3
View File
@@ -2,12 +2,13 @@ import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import {
COMMAND_NAME,
SETTING_CONTENT_DEFAULT_FILETYPE,
SETTING_TEMPLATES_FOLDER,
TelemetryEvent
} from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { ArticleHelper, Extension, Settings } from '../helpers';
import { Article, Folders } from '.';
import { Notifications } from '../helpers/Notifications';
import { Project } from './Project';
import { ContentType } from '../helpers/ContentType';
@@ -20,6 +21,24 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Template {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate)
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
const folderPath = Folders.getFolderPath(folder);
if (folderPath) {
Template.create(folderPath);
}
})
);
}
/**
* Generate a template
*/
@@ -176,7 +195,7 @@ export class Template {
newFilePath
);
const article = Article.updateDate(frontMatter);
const article = await Article.updateDate(frontMatter);
if (!article) {
return;
+10 -10
View File
@@ -48,10 +48,10 @@ export class i18n {
*
* @returns An array of I18nConfig settings.
*/
public static getAll() {
public static async getAll() {
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N) || [];
const folders = Folders.get();
const folders = await Folders.get();
if (folders) {
for (const folder of folders) {
if (folder.locales) {
@@ -77,7 +77,7 @@ export class i18n {
}
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder) {
pageFolder = await i18n.getPageFolder(filePath);
}
@@ -100,7 +100,7 @@ export class i18n {
return false;
}
const pageFolder = Folders.getPageFolderByFilePath(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder || !pageFolder.locale) {
return false;
}
@@ -119,7 +119,7 @@ export class i18n {
return false;
}
const pageFolder = Folders.getPageFolderByFilePath(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder || !pageFolder.defaultLocale) {
return false;
}
@@ -155,7 +155,7 @@ export class i18n {
return;
}
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
const fileInfo = await i18n.getFileInfo(filePath);
@@ -217,7 +217,7 @@ export class i18n {
};
} = {};
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
const fileInfo = await i18n.getFileInfo(filePath);
if (pageFolder && pageFolder.defaultLocale && pageFolder.localeSourcePath) {
@@ -272,7 +272,7 @@ export class i18n {
fileUri = Uri.file(fileUri);
}
const pageFolder = Folders.getPageFolderByFilePath(fileUri.fsPath);
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
if (!pageFolder || !pageFolder.localeSourcePath) {
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
return;
@@ -331,7 +331,7 @@ export class i18n {
return;
}
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
if (!contentType) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
return;
@@ -482,7 +482,7 @@ export class i18n {
* @returns A promise that resolves to the ContentFolder object representing the page folder, or undefined if not found.
*/
private static async getPageFolder(filePath: string): Promise<ContentFolder | undefined> {
const folders = Folders.get();
const folders = await Folders.get();
const localeFolders = folders?.filter((folder) => folder.defaultLocale);
if (!localeFolders) {
+1 -1
View File
@@ -12,7 +12,7 @@ export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({
alternative,
children
}: React.PropsWithChildren<IFeatureFlagProps>) => {
if (!features || (features.length > 0 && !features.includes(flag))) {
if (!features || features.length === 0 || (features.length > 0 && !features.includes(flag))) {
if (alternative) {
return alternative;
}
@@ -7,7 +7,7 @@ import { LabelField } from './LabelField';
export type BoolFieldProps = HTMLFieldProps<
boolean,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Bool({
@@ -37,6 +37,12 @@ function Bool({
/>
<span className="field__toggle__slider"></span>
</label>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
@@ -9,7 +9,7 @@ const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8);
export type DateFieldProps = HTMLFieldProps<
Date,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date }
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date, description?: string }
>;
function Date({
@@ -50,6 +50,12 @@ function Date({
type="datetime-local"
value={dateFormat(value) ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type LongTextFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLTextAreaElement> }
{ inputRef?: Ref<HTMLTextAreaElement>, description?: string }
>;
function LongText({
@@ -36,6 +36,12 @@ function LongText({
ref={inputRef}
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type NumFieldProps = HTMLFieldProps<
number,
HTMLDivElement,
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement> }
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Num({
@@ -47,6 +47,12 @@ function Num({
type="number"
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type TextFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Text({
@@ -40,6 +40,12 @@ function Text({
type={type}
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
@@ -8,7 +8,7 @@ import { LocalizationKey } from '../../localization';
export type UnknownFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function UnknownField({
@@ -30,6 +30,12 @@ function UnknownField({
<LabelField label={label} id={id} required={props.required} />
<div className={`text-[var(--vscode-errorForeground)]`}>{l10n.t(LocalizationKey.fieldUnknown)}</div>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}
+12
View File
@@ -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
];
+2 -1
View File
@@ -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: {
+2 -1
View File
@@ -27,7 +27,8 @@ export const GeneralCommands = {
logging: {
info: 'logInfo',
warn: 'logWarn',
error: 'logError'
error: 'logError',
verbose: 'logVerbose'
},
runCommand: 'runCommand',
getLocalization: 'getLocalization',
+2
View File
@@ -47,6 +47,8 @@ export const TelemetryEvent = {
webviewContentsView: 'webviewContentsView',
webviewSnippetsView: 'webviewSnippetsView',
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
webviewSettings: 'webviewSettings',
webviewUnknown: 'webviewUnknown',
// Git
gitSync: 'gitSync',
+3
View File
@@ -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
*/
+1
View File
@@ -76,6 +76,7 @@ export enum DashboardMessage {
runCustomScript = 'runCustomScript',
sendTelemetry = 'sendTelemetry',
showNotification = 'showNotification',
setTitle = 'setTitle',
// Settings
getSettings = 'getSettings',
+27 -3
View File
@@ -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<IAppProps> = ({
}
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<IAppProps> = ({
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<IAppProps> = ({
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'
}
);
}}
>
@@ -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<ITextFieldProps> = ({
name,
value,
placeholder,
description,
icon,
autoFocus,
multiline,
@@ -27,52 +29,63 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
onReset
}: React.PropsWithChildren<ITextFieldProps>) => {
return (
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
<>
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
{
multiline ? (
<textarea
rows={rows || 3}
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
) : (
<input
type="text"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
)
}
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
{
multiline ? (
<textarea
rows={rows || 3}
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
) : (
<input
type="text"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{description}
</p>
)
}
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
</>
);
};
@@ -8,9 +8,9 @@ import { Spinner } from '../Common/Spinner';
import { SponsorMsg } from '../Layout/SponsorMsg';
import usePages from '../../hooks/usePages';
import { useCallback, useEffect, useState } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
import { GeneralCommands, TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { FilesProvider } from '../../providers/FilesProvider';
import { Alert } from '../Modals/Alert';
@@ -59,10 +59,19 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
}
}, [pageItems, selectedItemAction]);
useEffect(() => {
messageHandler.send(GeneralCommands.toVSCode.logging.info, {
message: `Contents view loaded with ${pageItems.length} pages`,
location: 'DASHBOARD'
});
}, [JSON.stringify(pageItems)]);
useEffect(() => {
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewContentsView
});
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsContents));
}, []);
return (
@@ -25,7 +25,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
return (
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] rounded`}>
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] rounded-b`}>
{/* <ItemSelection filePath={filePath} show /> */}
<QuickAction
@@ -115,7 +115,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={onOpenFile}
className={`relative rounded h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
className={`relative rounded-t h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
>
{
imageHtml ?
@@ -17,10 +17,13 @@ import { Container } from './SortableContainer';
import { SortableItem } from './SortableItem';
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
import { DataType } from '../../../models/DataType';
import { TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
import { GeneralCommands, TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
import { NavigationItem } from '../Layout';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
import { MenuButton, MenuItem } from '../Menu';
import { Transition } from '@headlessui/react';
export interface IDataViewProps { }
@@ -125,10 +128,17 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
useEffect(() => {
Messenger.listen(messageListener);
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsData));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewDataView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Data view loaded',
location: 'DASHBOARD'
});
return () => {
Messenger.unlisten(messageListener);
};
@@ -161,38 +171,71 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
{settings?.dataFiles && settings.dataFiles.length > 0 ? (
<div className="relative w-full flex-grow mx-auto overflow-hidden">
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside
className={`flex flex-col flex-grow overflow-y-auto border-r py-6 px-4 overflow-auto border-[var(--frontmatter-border)]`}
>
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
</h2>
<nav className={`flex-1 py-4 -mx-4`}>
<div
className={`divide-y border-t border-b divide-[var(--frontmatter-border)] border-[var(--frontmatter-border)]`}
{dataFiles && dataFiles.length > 0 ? (
<div className={`relative w-full flex-grow mx-auto overflow-hidden`}>
{
!selectedData && (
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside
className={`flex flex-col flex-grow overflow-y-auto border-r py-6 px-4 overflow-auto border-[var(--frontmatter-border)]`}
>
{dataFiles &&
dataFiles.length > 0 &&
dataFiles.map((dataFile, idx) => (
<NavigationItem
key={`${dataFile.id}-${idx}`}
isSelected={selectedData?.id === dataFile.id}
onClick={() => setSchema(dataFile)}
>
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
<span>{dataFile.title}</span>
</NavigationItem>
))}
</div>
</nav>
</aside>
</div>
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
</h2>
<section className={`pl-64 flex min-w-0 h-full`}>
<nav className={`flex-1 py-4 -mx-4`}>
<div
className={`divide-y border-t border-b divide-[var(--frontmatter-border)] border-[var(--frontmatter-border)]`}
>
{dataFiles &&
dataFiles.length > 0 &&
dataFiles.map((dataFile, idx) => (
<NavigationItem
key={`${dataFile.id}-${idx}`}
onClick={() => setSchema(dataFile)}
>
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
<span>{dataFile.title}</span>
</NavigationItem>
))}
</div>
</nav>
</aside>
</div>
)
}
<Transition
as={`div`}
show={!!selectedData}
enter="transition ease transform"
enterFrom="opacity-0"
enterTo="opacity-100">
<div className={`w-full px-4 py-2 border-b border-[var(--frontmatter-border)]`}>
{selectedData && (
<DropdownMenu>
<MenuButton
label={l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
title={selectedData.title}
/>
<DropdownMenuContent>
{dataFiles.map((dataFile) => (
<MenuItem
key={dataFile.id}
title={dataFile.title}
value={dataFile}
isCurrent={selectedData.id === dataFile.id}
onClick={() => setSchema(dataFile)}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</Transition>
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : "pl-64"}`}>
{selectedData ? (
<>
{!selectedData.singleEntry && (
@@ -9,6 +9,7 @@ import { Tab } from './Tab';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { PageIcon } from '../../../panelWebView/components/Icons';
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
export interface ITabsProps {
onNavigate: (navigationType: NavigationType) => void;
@@ -37,7 +38,7 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsMedia)}</span>
</Tab>
</li>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.view}>
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.snippets.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Snippets} onNavigate={onNavigate}>
<ScissorsIcon className={`h-4 w-auto mr-2`} />
@@ -45,7 +46,7 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({
</Tab>
</li>
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.data.view}>
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.data.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Data} onNavigate={onNavigate}>
<CircleStackIcon className={`h-4 w-auto mr-2`} />
@@ -53,7 +54,7 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({
</Tab>
</li>
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Taxonomy} onNavigate={onNavigate}>
<TagIcon className={`h-4 w-auto mr-2`} />
@@ -36,7 +36,7 @@ export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = ({
return (
<footer
className={`w-full px-4 py-2 text-center space-x-8 flex items-center border-t ${isBacker ? 'justify-center' : 'justify-between'
} bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}
} bg-[var(--vscode-editor-background)] text-[var(--frontmatter-secondary-text)] border-[var(--frontmatter-border)]`}
>
{isBacker ? (
<span>
@@ -20,7 +20,7 @@ import { DashboardMessage } from '../../DashboardMessage';
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
import { FolderItem } from './FolderItem';
import useMedia from '../../hooks/useMedia';
import { STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
import { GeneralCommands, STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { basename, extname, join } from 'path';
@@ -150,9 +150,16 @@ export const Media: React.FunctionComponent<IMediaProps> = (
);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsMedia));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewMediaView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: `Media view loaded`,
location: 'DASHBOARD'
});
}, []);
const { getRootProps, isDragActive } = useDropzone({
@@ -6,7 +6,7 @@ import { ViewDataSelector } from '../../state';
import SnippetForm, { SnippetFormHandle } from '../SnippetsView/SnippetForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { SnippetSlideOver } from './SnippetSlideOver';
import { SlideOver } from '../Modals/SlideOver';
export interface IMediaSnippetFormProps {
media: MediaInfo;
@@ -32,7 +32,7 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
};
return (
<SnippetSlideOver
<SlideOver
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.metadata.title || media.filename)}
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.metadata.title || media.filename)}
isSaveDisabled={false}
@@ -48,6 +48,6 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
selection={viewData?.data?.selection}
onInsert={onInsert}
/>
</SnippetSlideOver>
</SlideOver>
);
};
@@ -5,7 +5,7 @@ import { Fragment, useRef } from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
export interface ISnippetSlideOverProps {
export interface ISlideOverProps {
title: string;
description: string;
okBtnText: string;
@@ -16,7 +16,7 @@ export interface ISnippetSlideOverProps {
trigger: () => void;
}
export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> = ({
export const SlideOver: React.FunctionComponent<ISlideOverProps> = ({
title,
description,
okBtnText,
@@ -25,7 +25,7 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
dismiss,
trigger,
children
}: React.PropsWithChildren<ISnippetSlideOverProps>) => {
}: React.PropsWithChildren<ISlideOverProps>) => {
const cancelButtonRef = useRef(null);
return (
@@ -46,11 +46,12 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
>
<div className="pointer-events-auto w-screen max-w-md">
<div className={`flex h-full flex-col overflow-y-scroll border-l pb-6 shadow-xl bg-[var(--vscode-sideBar-background)] border-[var(--frontmatter-border)]`}>
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)]">
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBarTitle-foreground)]">
<div className="flex items-start justify-between">
<Dialog.Title className={`text-lg font-medium text-[var(--vscode-editor-foreground)]`}>
<Dialog.Title className={`text-lg font-medium`}>
{title}
</Dialog.Title>
<div className="ml-3 flex h-7 items-center">
<button
type="button"
@@ -62,12 +63,14 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
</button>
</div>
</div>
<div className="mt-1">
<p className="text-sm">{description}</p>
</div>
</div>
<div className="relative flex-1 px-4 sm:px-6">
<div className="space-y-4">
<p className="text-sm">{description}</p>
<div>
{children}
</div>
@@ -8,11 +8,14 @@ import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
import { ContentFolders } from '../Configuration/Common/ContentFolders';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { COMMAND_NAME } from '../../../constants';
import { COMMAND_NAME, TelemetryEvent } from '../../../constants';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react';
import { CommonSettings } from './CommonSettings';
import { IntegrationsView } from './IntegrationsView';
import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
export interface ISettingsViewProps { }
@@ -20,6 +23,14 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
const [loading, setLoading] = React.useState<boolean>(false);
const settings = useRecoilValue(SettingsSelector);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.commonSettings));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSettings
});
}, []);
return (
<PageLayout>
{
@@ -0,0 +1,84 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { QuickAction } from '../Menu';
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import { useCallback } from 'react';
import { openFile } from '../../utils/MessageHandlers';
export interface IFooterActionsProps {
insertEnabled: boolean;
sourcePath?: string;
onEdit?: () => void;
onInsert: () => void;
onDelete?: () => void;
}
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
insertEnabled,
sourcePath,
onEdit,
onInsert,
onDelete,
}: React.PropsWithChildren<IFooterActionsProps>) => {
const showFile = useCallback(() => {
openFile(sourcePath);
}, [sourcePath]);
if (!onEdit && !onDelete && !sourcePath && !insertEnabled) {
return null;
}
return (
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] z-50`}>
{insertEnabled && (
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={onInsert}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.commonInsertSnippet)}</span>
</QuickAction>
)}
{
!sourcePath ? (
<>
{
onEdit && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={onEdit}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}</span>
</QuickAction>
)
}
{
onDelete && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}</span>
</QuickAction>
)
}
</>
) : (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={showFile}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}</span>
</QuickAction>
)
}
</div >
);
};
@@ -1,14 +1,8 @@
import * as l10n from '@vscode/l10n';
import { Messenger } from '@estruyf/vscode/dist/client';
import {
CodeBracketIcon,
DocumentTextIcon,
EllipsisHorizontalIcon,
EyeIcon,
PencilIcon,
PhotoIcon,
PlusIcon,
TrashIcon
} from '@heroicons/react/24/outline';
} from '@heroicons/react/24/solid';
import * as React from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
@@ -18,14 +12,14 @@ import { SnippetParser } from '../../../helpers/SnippetParser';
import { Snippet, Snippets } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { QuickAction } from '../Menu';
import { Alert } from '../Modals/Alert';
import { FormDialog } from '../Modals/FormDialog';
import { NewForm } from './NewForm';
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { openFile } from '../../utils';
import { FooterActions } from './FooterActions';
import { ItemMenu } from './ItemMenu';
import { SlideOver } from '../Modals/SlideOver';
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
export interface IItemProps {
snippetKey: string;
@@ -65,10 +59,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
setMediaSnippet(false);
};
const showFile = useCallback(() => {
openFile(snippet.sourcePath);
}, [snippet]);
const onOpenEdit = useCallback(() => {
setSnippetTitle(snippet.title || snippetKey);
setSnippetDescription(snippet.description);
@@ -162,95 +152,67 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return (
<>
<li className={`group relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border p-4 space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<CodeBracketIcon className={`w-64 h-64 opacity-5 text-[var(--frontmatter-text)]`} />
<li className={`group flex flex-col relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
<div className='p-4 grow space-y-2'>
<h2
className="font-bold flex items-center"
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
>
<CodeBracketIcon className="w-5 h-5 mr-2" aria-hidden={true} />
{snippet.title || snippetKey}
</h2>
<FeatureFlag
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
flag={FEATURE_FLAG.dashboard.snippets.manage}
alternative={
<ItemMenu
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onInsert={() => setShowInsertDialog(true)} />
}
>
<ItemMenu
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onEdit={onOpenEdit}
onInsert={() => setShowInsertDialog(true)}
onDelete={() => setShowAlert(true)} />
</FeatureFlag>
<div className='inline-block mr-1 mt-1 text-xs text-[var(--vscode-button-secondaryForeground)] bg-[var(--vscode-button-secondaryBackground)] border border-[var(--frontmatter-border)] rounded px-1 py-0.5'>
{
snippet.isMediaSnippet ? l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeContent) : l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeMedia)
}
</div>
<p className={`text-xs text-[var(--frontmatter-text)]`}>{snippet.description}</p>
</div>
<h2
className="mt-2 mb-2 font-bold flex items-center"
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
>
{snippet.isMediaSnippet ? (
<PhotoIcon className="w-5 h-5 mr-1" aria-hidden={true} />
) : (
<DocumentTextIcon className="w-5 h-5 mr-1" aria-hidden={true} />
)}
{snippet.title || snippetKey}
</h2>
<FeatureFlag
features={mode?.features || []}
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
flag={FEATURE_FLAG.dashboard.snippets.manage}
alternative={
insertToContent ? (
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
<div className="group-hover:hidden">
<EllipsisHorizontalIcon className="w-4 h-4" />
</div>
<div className="hidden group-hover:flex">
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
onClick={() => setShowInsertDialog(true)}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
</div>
</div>
) : undefined
<FooterActions
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onInsert={() => setShowInsertDialog(true)} />
}
>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
<div className="group-hover:hidden">
<EllipsisHorizontalIcon className="w-4 h-4" />
</div>
<div className="hidden group-hover:flex">
{insertToContent && !snippet.isMediaSnippet && (
<>
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={() => setShowInsertDialog(true)}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</>
)}
{!snippet.sourcePath ? (
<>
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
onClick={onOpenEdit}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
onClick={() => setShowAlert(true)}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</>
) : (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
onClick={showFile}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)}
</div>
</div>
</div>
<FooterActions
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onEdit={onOpenEdit}
onInsert={() => setShowInsertDialog(true)}
onDelete={() => setShowAlert(true)} />
</FeatureFlag>
<p className={`text-xs text-[var(--frontmatter-text)]`}>{snippet.description}</p>
</li>
{showInsertDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogTitle, snippet.title || snippetKey)}
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
description={snippet.description || l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
isSaveDisabled={!insertToContent}
trigger={insertToArticle}
dismiss={() => setShowInsertDialog(false)}
@@ -264,11 +226,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
filePath={viewData?.data?.filePath}
fieldInfo={viewData?.data?.snippetInfo?.fields}
selection={viewData?.data?.selection} />
</FormDialog>
</SlideOver>
)}
{showEditDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogTitle, snippet.title || snippetKey)}
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
isSaveDisabled={!snippetTitle || !snippetOriginalBody}
@@ -287,7 +249,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)}
/>
</FormDialog>
</SlideOver>
)}
{showAlert && (
@@ -0,0 +1,97 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { useCallback } from 'react';
import { LocalizationKey } from '../../../localization';
import { openFile } from '../../utils/MessageHandlers';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid';
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
export interface IItemMenuProps {
insertEnabled: boolean;
sourcePath?: string;
onEdit?: () => void;
onInsert: () => void;
onDelete?: () => void;
}
export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
insertEnabled,
sourcePath,
onEdit,
onInsert,
onDelete,
}: React.PropsWithChildren<IItemMenuProps>) => {
const showFile = useCallback(() => {
openFile(sourcePath);
}, [sourcePath]);
if (!onEdit && !onDelete && !sourcePath && !insertEnabled) {
return null;
}
return (
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-1 -mr-2 -mt-3 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
<div className="relative z-10 flex text-left">
<DropdownMenu>
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{
insertEnabled && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.commonInsertSnippet)}
onClick={onInsert}>
<PlusIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonInsertSnippet)}</span>
</DropdownMenuItem>
)
}
{
!sourcePath ? (
<>
{
onEdit && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
onClick={onEdit}>
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}</span>
</DropdownMenuItem>
)
}
{
onDelete && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
onClick={onDelete}
className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
<TrashIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}</span>
</DropdownMenuItem>
)
}
</>
) : (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
onClick={showFile}>
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}</span>
</DropdownMenuItem>
)
}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
);
};
@@ -155,8 +155,10 @@ ${snippetBody}
value
});
} catch (e) {
messageHandler.send(GeneralCommands.toVSCode.logging.error, `SnippetForm: ${(e as Error).message}`);
console.log('Error', (e as Error).message)
messageHandler.send(GeneralCommands.toVSCode.logging.error, {
message: `SnippetForm: ${(e as Error).message}`,
location: 'DASHBOARD'
});
}
}
}
@@ -197,7 +199,10 @@ ${snippetBody}
{field.title || field.name}
</label>
<div className="mt-1">
<SnippetInputField field={field} fieldInfo={fieldInfo} onValueChange={onTextChange} />
<SnippetInputField
field={field}
fieldInfo={fieldInfo}
onValueChange={onTextChange} />
</div>
</div>
)
@@ -27,31 +27,41 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
if (field.type === 'choice') {
return (
<div className="relative">
<select
name={field.name}
value={field.value || ''}
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
onChange={(e) => onValueChange(field, e.target.value)}
>
{(field.choices || [])?.map((option: string | Choice, index: number) =>
typeof option === 'string' ? (
<option key={index} value={option}>
{option}
</option>
) : (
<option key={index} value={option.id}>
{option.title}
</option>
)
)}
</select>
<>
<div className="relative">
<select
name={field.name}
value={field.value || ''}
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
onChange={(e) => onValueChange(field, e.target.value)}
>
{(field.choices || [])?.map((option: string | Choice, index: number) =>
typeof option === 'string' ? (
<option key={index} value={option}>
{option}
</option>
) : (
<option key={index} value={option.id}>
{option.title}
</option>
)
)}
</select>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
{
field.description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{field.description}
</p>
)
}
</>
);
}
@@ -60,6 +70,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
<TextField
name={field.name}
value={field.value || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
rows={4}
multiline
@@ -71,6 +82,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
<TextField
name={field.name}
value={field.value || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
/>
);
@@ -4,19 +4,21 @@ import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { FeatureFlag } from '../../../components/features/FeatureFlag';
import { FEATURE_FLAG, WEBSITE_LINKS } from '../../../constants';
import { FEATURE_FLAG, GeneralCommands, WEBSITE_LINKS } from '../../../constants';
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { FilterInput } from '../Header/FilterInput';
import { PageLayout } from '../Layout/PageLayout';
import { FormDialog } from '../Modals/FormDialog';
import { SponsorMsg } from '../Layout/SponsorMsg';
import { Item } from './Item';
import { NewForm } from './NewForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { List } from '../Media/List';
import { SlideOver } from '../Modals/SlideOver';
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
export interface ISnippetsProps { }
@@ -80,15 +82,24 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
};
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsSnippets));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSnippetsView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: `Snippets view loaded`,
location: 'DASHBOARD'
});
}, []);
return (
<PageLayout
header={
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.manage}>
<FeatureFlag
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
flag={FEATURE_FLAG.dashboard.snippets.manage}>
<div
className={`py-3 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
aria-label={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsAriaLabel)}
@@ -128,14 +139,13 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
)}
{(snippetKeys && snippetKeys.length > 0) ? (
<ul
role="list"
className={`grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}
<List
gap={4}
>
{snippetKeys.map((snippetKey: any, index: number) => (
<Item key={index} snippetKey={snippetKey} snippet={snippets[snippetKey]} />
))}
</ul>
</List>
) : (
<div className="w-full h-full flex items-center justify-center text-white">
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
@@ -157,7 +167,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
)}
{showCreateDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsFormDialogTitle)}
description={``}
isSaveDisabled={!snippetTitle || !snippetBody}
@@ -176,7 +186,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)}
/>
</FormDialog>
</SlideOver>
)}
</div>
@@ -3,7 +3,7 @@ import { ChevronRightIcon, ArrowDownTrayIcon } from '@heroicons/react/24/outline
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { TelemetryEvent } from '../../../constants';
import { GeneralCommands, TelemetryEvent } from '../../../constants';
import { TaxonomyData } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { Page, PageMappings } from '../../models';
@@ -53,9 +53,16 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
}, [settings?.tags, settings?.categories, settings?.customTaxonomy]);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsTaxonomies));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewTaxonomyDashboard
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Taxonomy view loaded',
location: 'DASHBOARD'
});
}, []);
return (
@@ -2,12 +2,24 @@ import { StopIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
export interface IUnknownViewProps { }
export const UnknownView: React.FunctionComponent<IUnknownViewProps> = (
_: React.PropsWithChildren<IUnknownViewProps>
) => {
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, "Unknown View");
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewUnknown
});
}, []);
return (
<div className={`w-full h-full flex items-center justify-center`}>
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
@@ -1,6 +1,6 @@
import { BookOpenIcon, HeartIcon, StarIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { DOCUMENTATION_LINK, GITHUB_LINK, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../../constants';
import { DOCUMENTATION_LINK, GITHUB_LINK, GeneralCommands, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
import { GitHubIcon } from '../../../panelWebView/components/Icons/GitHubIcon';
@@ -29,6 +29,13 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({
event: TelemetryEvent.webviewWelcomeScreen
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Welcome screen loaded',
location: "DASHBOARD"
});
Messenger.send(DashboardMessage.setTitle, "Welcome to Front Matter");
const crntState: any = Messenger.getState() || {};
Messenger.setState({
...crntState,
+7 -2
View File
@@ -11,7 +11,7 @@ import {
SearchReadyAtom,
ModeAtom
} from '../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { NavigationType } from '../models';
import { GeneralCommands } from '../../constants';
@@ -31,6 +31,11 @@ export default function useMessages() {
const messageListener = (event: MessageEvent<EventData<any>>) => {
const message = event.data;
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Message received: ${message.command}`,
location: 'DASHBOARD'
});
switch (message.command) {
case DashboardCommand.loading:
setLoading(message.payload);
@@ -82,7 +87,7 @@ export default function useMessages() {
return () => {
Messenger.unlisten(messageListener);
};
}, ['']);
}, []);
return {
loading,
+19 -110
View File
@@ -16,7 +16,6 @@ import { PagesListener } from './listeners/dashboard';
import { ModeSwitch } from './services/ModeSwitch';
import { PagesParser } from './services/PagesParser';
import { ContentType, Telemetry, Extension } from './helpers';
import { TaxonomyType } from './models';
import * as l10n from '@vscode/l10n';
import {
Backers,
@@ -46,6 +45,10 @@ export async function activate(context: vscode.ExtensionContext) {
const { subscriptions, extensionUri, extensionPath } = context;
const extension = Extension.getInstance(context);
Logger.info(`Activating ${EXTENSION_NAME} version ${Extension.getInstance().version}...`);
Logger.info(`Logging level: ${Logger.getLevel()}`);
// Set development context
if (!Extension.getInstance().isProductionMode) {
vscode.commands.executeCommand('setContext', CONTEXT.isDevelopment, true);
@@ -89,8 +92,13 @@ export async function activate(context: vscode.ExtensionContext) {
Dashboard.init();
Dashboard.registerCommands();
// Multilingual commands
i18n.register();
// Setting commands
Settings.registerCommands();
SettingsHelper.registerCommands();
if (!extension.getVersion().usedVersion) {
vscode.commands.executeCommand(COMMAND_NAME.dashboard);
}
@@ -125,101 +133,23 @@ export async function activate(context: vscode.ExtensionContext) {
}
);
const createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
Settings.create(TaxonomyType.Tag);
});
const createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
Settings.create(TaxonomyType.Category);
});
const exportTaxonomy = vscode.commands.registerCommand(
COMMAND_NAME.exportTaxonomy,
Settings.export
);
const remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap);
// Register all the article commands
Article.registerCommands(subscriptions);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.initTemplate, () =>
Project.createSampleTemplate(true)
)
);
// Register project folders
const registerFolder = vscode.commands.registerCommand(
COMMAND_NAME.registerFolder,
Folders.register
);
const unregisterFolder = vscode.commands.registerCommand(
COMMAND_NAME.unregisterFolder,
Folders.unregister
);
const createFolder = vscode.commands.registerCommand(
COMMAND_NAME.createFolder,
Folders.addMediaFolder
);
/**
* Template creation
*/
const createTemplate = vscode.commands.registerCommand(
COMMAND_NAME.createTemplate,
Template.generate
);
const createFromTemplate = vscode.commands.registerCommand(
COMMAND_NAME.createFromTemplate,
(folder: vscode.Uri) => {
const folderPath = Folders.getFolderPath(folder);
if (folderPath) {
Template.create(folderPath);
}
}
);
Template.registerCommands();
/**
* Content creation
*/
const createByContentType = vscode.commands.registerCommand(
COMMAND_NAME.createByContentType,
ContentType.createContent
);
const createByTemplate = vscode.commands.registerCommand(
COMMAND_NAME.createByTemplate,
Folders.create
);
const createContent = vscode.commands.registerCommand(COMMAND_NAME.createContent, Content.create);
ContentType.registerCommands();
Content.registerCommands();
Folders.registerCommands();
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate)
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.addMissingFields, ContentType.addMissingFields)
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.setContentType, ContentType.setContentType)
);
// Initialize command
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
await Project.init();
if (cb) {
cb();
}
})
);
// Settings promotion command
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote));
// Project commands
Project.registerCommands();
// Collapse all sections in the webview
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
@@ -236,7 +166,7 @@ export async function activate(context: vscode.ExtensionContext) {
-100
);
fmStatusBarItem.command = COMMAND_NAME.dashboard;
fmStatusBarItem.text = `$(fm-logo)`;
fmStatusBarItem.text = `$(fm-logo) ${extension.getVersion().installedVersion}`;
fmStatusBarItem.tooltip = EXTENSION_NAME;
fmStatusBarItem.show();
@@ -299,7 +229,7 @@ export async function activate(context: vscode.ExtensionContext) {
ModeSwitch.register();
// Diagnostics
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show));
Diagnostics.registerCommands();
// Git
GitListener.init();
@@ -311,29 +241,8 @@ export async function activate(context: vscode.ExtensionContext) {
// Cache commands
Cache.registerCommands();
// Project switching
Project.registerCommands();
// Subscribe all commands
subscriptions.push(
insertTags,
PanelView,
insertCategories,
createTag,
createCategory,
exportTaxonomy,
remap,
createFromTemplate,
createTemplate,
registerFolder,
unregisterFolder,
createContent,
createByContentType,
createByTemplate,
collapseAll,
createFolder,
fmStatusBarItem
);
subscriptions.push(insertTags, PanelView, insertCategories, collapseAll, fmStatusBarItem);
console.log(`𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲 𝖺𝖼𝗍𝗂𝗏𝖺𝗍𝖾𝖽! 𝖱𝖾𝖺𝖽𝗒 𝗍𝗈 𝗌𝗍𝖺𝗋𝗍 𝗐𝗋𝗂𝗍𝗂𝗇𝗀... 👩‍💻🧑‍💻👨‍💻`);
}
@@ -345,7 +254,7 @@ const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
};
const triggerPageUpdate = (location: string) => {
Logger.info(`Trigger page update: ${location}`);
Logger.verbose(`Trigger page update: ${location}`);
pageUpdateDebouncer(() => {
StatusListener.verify(collection);
}, 1000);
+18 -15
View File
@@ -359,7 +359,7 @@ export class ArticleHelper {
return false;
}
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
return !!contentType.pageBundle;
}
@@ -379,13 +379,14 @@ export class ArticleHelper {
/**
* Get date from front matter
*/
public static getDate(article: ParsedFrontMatter | null | undefined) {
public static async getDate(article: ParsedFrontMatter | null | undefined) {
if (!article || !article.data) {
return;
}
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
const dateField =
(await ArticleHelper.getPublishDateField(article)) || DefaultFields.PublishingDate;
if (typeof article.data[dateField] !== 'undefined') {
if (dateFormat && typeof dateFormat === 'string') {
@@ -404,12 +405,12 @@ export class ArticleHelper {
* @param article
* @returns
*/
public static getPublishDateField(article: ParsedFrontMatter | null) {
public static async getPublishDateField(article: ParsedFrontMatter | null) {
if (!article || !article.data) {
return;
}
const articleCt = ArticleHelper.getContentType(article);
const articleCt = await ArticleHelper.getContentType(article);
const pubDateField = articleCt.fields.find((f) => f.isPublishDate);
return (
@@ -424,12 +425,14 @@ export class ArticleHelper {
* @param article
* @returns
*/
public static getModifiedDateField(article: ParsedFrontMatter | null): Field | undefined {
public static async getModifiedDateField(
article: ParsedFrontMatter | null
): Promise<Field | undefined> {
if (!article || !article.data) {
return;
}
const articleCt = ArticleHelper.getContentType(article);
const articleCt = await ArticleHelper.getContentType(article);
const modDateField = articleCt.fields.find((f) => f.isModifiedDate);
return modDateField;
@@ -447,7 +450,7 @@ export class ArticleHelper {
* Retrieve the content type of the current file
* @param updatedMetadata
*/
public static getContentType(article: ParsedFrontMatter): IContentType {
public static async getContentType(article: ParsedFrontMatter): Promise<IContentType> {
const contentTypes = ArticleHelper.getContentTypes();
if (!contentTypes || !article.data) {
@@ -460,7 +463,7 @@ export class ArticleHelper {
if (article.data.type) {
contentType = contentTypes.find((ct) => ct.name === article.data.type);
} else if (!contentType && article.path) {
const pageFolder = Folders.getPageFolderByFilePath(article.path);
const pageFolder = await Folders.getPageFolderByFilePath(article.path);
if (pageFolder && pageFolder.contentTypes?.length === 1) {
const contentTypeName = pageFolder.contentTypes[0];
contentType = contentTypes.find((ct) => ct.name === contentTypeName);
@@ -488,8 +491,8 @@ export class ArticleHelper {
* Update all dates in the metadata
* @param metadata
*/
public static updateDates(article: ParsedFrontMatter) {
const contentType = ArticleHelper.getContentType(article);
public static async updateDates(article: ParsedFrontMatter) {
const contentType = await ArticleHelper.getContentType(article);
const dateFields = contentType.fields.filter((field) => field.type === 'datetime');
for (const dateField of dateFields) {
@@ -529,7 +532,7 @@ export class ArticleHelper {
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
let prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
prefix = ArticleHelper.getFilePrefix(prefix, folderPath, contentType);
prefix = await ArticleHelper.getFilePrefix(prefix, folderPath, contentType);
// Name of the file or folder to create
let sanitizedName = ArticleHelper.sanitize(titleValue);
@@ -588,18 +591,18 @@ export class ArticleHelper {
* @param contentType
* @returns
*/
public static getFilePrefix(
public static async getFilePrefix(
prefix: string | null | undefined,
filePath?: string,
contentType?: IContentType
): string | undefined {
): Promise<string | undefined> {
if (!prefix) {
prefix = undefined;
}
// Retrieve the file prefix from the folder
if (filePath) {
const filePrefixOnFolder = Folders.getFilePrefixByFolderPath(filePath);
const filePrefixOnFolder = await Folders.getFilePrefixBeFilePath(filePath);
if (typeof filePrefixOnFolder !== 'undefined') {
prefix = filePrefixOnFolder;
}
+69 -15
View File
@@ -3,12 +3,14 @@ import { PagesListener } from './../listeners/dashboard';
import {
ArticleHelper,
CustomScript,
Extension,
Logger,
Settings,
processArticlePlaceholdersFromData,
processTimePlaceholders
} from '.';
import {
COMMAND_NAME,
DefaultFieldValues,
EXTENSION_NAME,
FEATURE_FLAG,
@@ -40,6 +42,32 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class ContentType {
/**
* Registers the commands related to content types.
*
* @param subscriptions - The array of subscriptions to which the commands will be added.
*/
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(
commands.registerCommand(COMMAND_NAME.createByContentType, ContentType.createContent)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.addMissingFields, ContentType.addMissingFields)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.setContentType, ContentType.setContentType)
);
}
/**
* Retrieve the draft field
* @returns
@@ -58,8 +86,8 @@ export class ContentType {
* @param data
* @returns
*/
public static getDraftStatus(article: ParsedFrontMatter) {
const contentType = ArticleHelper.getContentType(article);
public static async getDraftStatus(article: ParsedFrontMatter) {
const contentType = await ArticleHelper.getContentType(article);
const draftSetting = ContentType.getDraftField();
const draftField = contentType.fields.find((f) => f.type === 'draft');
@@ -94,7 +122,8 @@ export class ContentType {
}
const contentTypes = ContentType.getAll();
const folders = Folders.get().filter((f) => !f.disableCreation);
let folders = await Folders.get();
folders = folders.filter((f) => !f.disableCreation);
const folder = folders.find((f) => f.path === selectedFolder.path);
if (!folder) {
@@ -251,7 +280,7 @@ export class ContentType {
pageBundle = pageBundleAnswer === l10n.t(LocalizationKey.commonYes);
}
const fields = ContentType.generateFields(content.data);
const fields = await ContentType.generateFields(content.data);
// Update the type field in the page
if (!overrideBool && editor) {
@@ -274,7 +303,7 @@ export class ContentType {
contentTypes.push(newContentType);
}
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
const configPath = await Settings.projectConfigPath();
const notificationAction = await Notifications.info(
@@ -314,14 +343,14 @@ export class ContentType {
return;
}
const contentType = ArticleHelper.getContentType(article);
const updatedFields = ContentType.generateFields(article.data, contentType.fields);
const contentType = await ArticleHelper.getContentType(article);
const updatedFields = await ContentType.generateFields(article.data, contentType.fields);
const contentTypes = ContentType.getAll() || [];
const index = contentTypes.findIndex((ct) => ct.name === contentType.name);
contentTypes[index].fields = updatedFields;
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
const configPath = await Settings.projectConfigPath();
const notificationAction = await Notifications.info(
@@ -531,8 +560,10 @@ export class ContentType {
/**
* Find the required fields
*/
public static findEmptyRequiredFields(article: ParsedFrontMatter): Field[][] | undefined {
const contentType = ArticleHelper.getContentType(article);
public static async findEmptyRequiredFields(
article: ParsedFrontMatter
): Promise<Field[][] | undefined> {
const contentType = await ArticleHelper.getContentType(article);
if (!contentType) {
return;
}
@@ -713,7 +744,7 @@ export class ContentType {
* @param fields
* @returns
*/
private static generateFields(data: any, fields: any[] = []) {
private static async generateFields(data: any, fields: any[] = []) {
for (const field in data) {
const fieldData = data[field];
@@ -751,15 +782,38 @@ export class ContentType {
fieldData.length > 0 &&
typeof fieldData[0] === 'object'
) {
const newFields = ContentType.generateFields(fieldData);
const newFields = await ContentType.generateFields(fieldData);
// Combine all the fields for the field group
const blockFields: Field[] = [];
for (const newField of newFields) {
for (const field of newField.fields) {
if (blockFields.some((f) => f.name === field.name)) {
continue;
}
blockFields.push(field);
}
}
// Generate a new field group
const fieldGroups = Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS) || [];
const fieldGroupName = `${field}_group`;
const newFieldGroup: FieldGroup = {
id: fieldGroupName,
fields: blockFields
};
fieldGroups.push(newFieldGroup);
await Settings.safeUpdate(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true);
fields.push({
title: field,
name: field,
type: 'block',
fields: newFields
fieldGroup: [fieldGroupName]
} as Field);
} else if (fieldData && fieldData instanceof Object) {
const newFields = ContentType.generateFields(fieldData);
const newFields = await ContentType.generateFields(fieldData);
fields.push({
title: field,
name: field,
@@ -950,7 +1004,7 @@ export class ContentType {
path: newFilePath
};
data = ArticleHelper.updateDates(article);
data = await ArticleHelper.updateDates(article);
if (isTypeSet) {
delete data.type;
+88 -76
View File
@@ -57,6 +57,7 @@ import { Settings } from './SettingsHelper';
import { parseWinPath } from './parseWinPath';
import { TaxonomyHelper } from './TaxonomyHelper';
import { ContentType } from './ContentType';
import { Logger } from './Logger';
export class DashboardSettings {
private static cachedSettings: ISettings | undefined = undefined;
@@ -86,88 +87,97 @@ export class DashboardSettings {
}
public static async getSettings() {
const ext = Extension.getInstance();
const wsFolder = Folders.getWorkspaceFolder();
const isInitialized = await Project.isInitialized();
const pagination = Settings.get<boolean | number>(SETTING_DASHBOARD_CONTENT_PAGINATION);
Logger.verbose('DashboardSettings:getSettings:start');
const settings = {
projects: Settings.getProjects(),
project: Settings.getProject(),
git: await GitListener.getSettings(),
beta: ext.isBetaVersion(),
wsFolder: wsFolder ? wsFolder.fsPath : '',
staticFolder: Folders.getStaticFolderRelativePath(),
initialized: isInitialized,
tags: (await TaxonomyHelper.get(TaxonomyType.Tag)) || [],
categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<DashboardViewType | undefined>(
ExtensionState.PagesView,
'workspace'
),
contentTypes: ContentType.getAll() || [],
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
contentFolders: Folders.get(),
filters:
Settings.get<(FilterType | { title: string; name: string })[]>(SETTING_CONTENT_FILTERS),
crntFramework: Settings.get<string>(SETTING_FRAMEWORK_ID),
framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [],
date: {
format: Settings.get<string>(SETTING_DATE_FORMAT) || ''
},
dashboardState: {
contents: {
sorting: await ext.getState<SortingOption | undefined>(
ExtensionState.Dashboard.Contents.Sorting,
'workspace'
),
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT),
tags: Settings.get<string>(SETTING_DASHBOARD_CONTENT_TAGS),
templatesEnabled: Settings.get<boolean>(SETTING_TEMPLATES_ENABLED),
pagination: pagination !== undefined ? pagination : true,
cardFields: {
state: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_STATE),
date: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_DATE),
title: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_TITLE),
description: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_DESCRIPTION)
try {
const ext = Extension.getInstance();
const wsFolder = Folders.getWorkspaceFolder();
const isInitialized = await Project.isInitialized();
const pagination = Settings.get<boolean | number>(SETTING_DASHBOARD_CONTENT_PAGINATION);
const settings = {
projects: Settings.getProjects(),
project: Settings.getProject(),
git: await GitListener.getSettings(),
beta: ext.isBetaVersion(),
wsFolder: wsFolder ? wsFolder.fsPath : '',
staticFolder: Folders.getStaticFolderRelativePath(),
initialized: isInitialized,
tags: (await TaxonomyHelper.get(TaxonomyType.Tag)) || [],
categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<DashboardViewType | undefined>(
ExtensionState.PagesView,
'workspace'
),
contentTypes: ContentType.getAll() || [],
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
contentFolders: await Folders.get(),
filters:
Settings.get<(FilterType | { title: string; name: string })[]>(SETTING_CONTENT_FILTERS),
crntFramework: Settings.get<string>(SETTING_FRAMEWORK_ID),
framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [],
date: {
format: Settings.get<string>(SETTING_DATE_FORMAT) || ''
},
dashboardState: {
contents: {
sorting: await ext.getState<SortingOption | undefined>(
ExtensionState.Dashboard.Contents.Sorting,
'workspace'
),
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT),
tags: Settings.get<string>(SETTING_DASHBOARD_CONTENT_TAGS),
templatesEnabled: Settings.get<boolean>(SETTING_TEMPLATES_ENABLED),
pagination: pagination !== undefined ? pagination : true,
cardFields: {
state: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_STATE),
date: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_DATE),
title: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_TITLE),
description: Settings.get<string>(SETTING_DASHBOARD_CONTENT_CARD_DESCRIPTION)
}
},
media: {
sorting: await ext.getState<SortingOption | undefined>(
ExtensionState.Dashboard.Media.Sorting,
'workspace'
),
defaultSorting: Settings.get<string>(SETTING_MEDIA_SORTING_DEFAULT),
selectedFolder: await ext.getState<string | undefined>(
ExtensionState.SelectedFolder,
'workspace'
),
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
},
welcome: {
contentFolders: await Folders.getContentFolders()
}
},
dataFiles: await this.getDataFiles(),
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
snippets: Settings.get<Snippets>(SETTING_CONTENT_SNIPPETS),
snippetsWrapper: Settings.get<boolean>(SETTING_SNIPPETS_WRAPPER),
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global'),
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL),
media: {
sorting: await ext.getState<SortingOption | undefined>(
ExtensionState.Dashboard.Media.Sorting,
'workspace'
),
defaultSorting: Settings.get<string>(SETTING_MEDIA_SORTING_DEFAULT),
selectedFolder: await ext.getState<string | undefined>(
ExtensionState.SelectedFolder,
'workspace'
),
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
contentTypes: Settings.get<MediaContentType[]>(SETTING_MEDIA_CONTENTTYPES) || [
DEFAULT_MEDIA_CONTENT_TYPE
]
},
welcome: {
contentFolders: await Folders.getContentFolders()
}
},
dataFiles: await this.getDataFiles(),
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
snippets: Settings.get<Snippets>(SETTING_CONTENT_SNIPPETS),
snippetsWrapper: Settings.get<boolean>(SETTING_SNIPPETS_WRAPPER),
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global'),
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL),
media: {
contentTypes: Settings.get<MediaContentType[]>(SETTING_MEDIA_CONTENTTYPES) || [
DEFAULT_MEDIA_CONTENT_TYPE
]
},
lastUpdated: new Date().getTime()
} as ISettings;
lastUpdated: new Date().getTime()
} as ISettings;
return settings;
Logger.verbose('DashboardSettings:getSettings:end');
return settings;
} catch (error) {
Logger.error(`DashboardSettings:getSettings:error ${(error as Error).message}`);
return {} as ISettings;
}
}
/**
@@ -175,6 +185,7 @@ export class DashboardSettings {
* @returns
*/
private static async getDataFiles(): Promise<DataFile[]> {
Logger.verbose('DashboardSettings:getDataFiles:start');
const wsPath = parseWinPath(Folders.getWorkspaceFolder()?.fsPath);
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
const folders = Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS);
@@ -220,6 +231,7 @@ export class DashboardSettings {
}
}
Logger.verbose('DashboardSettings:getDataFiles:end');
return clonedFiles;
}
}
+2 -2
View File
@@ -236,7 +236,7 @@ export class Extension {
}
);
await Settings.update(
await Settings.safeUpdate(
SETTING_TEMPLATES_ENABLED,
answer?.toLocaleLowerCase() === l10n.t(LocalizationKey.commonYes),
true
@@ -266,7 +266,7 @@ export class Extension {
return f;
});
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folders, true);
await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folders, true);
}
// The tags and categories settings need to be moved to the database
+1 -1
View File
@@ -244,7 +244,7 @@ export class FrameworkDetector {
});
}
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true);
} catch (e) {
Logger.error(
`Something failed while processing your Hexo configuration. ${(e as Error).message}`
+38 -7
View File
@@ -1,7 +1,10 @@
import { Extension } from './Extension';
import { commands, OutputChannel, window } from 'vscode';
import { format } from 'date-fns';
import { COMMAND_NAME } from '../constants';
import { COMMAND_NAME, SETTING_LOGGING } from '../constants';
import { Settings } from '.';
export type LoggerLocation = 'VSCODE' | 'DASHBOARD' | 'PANEL' | 'SETTING';
export class Logger {
private static instance: Logger;
@@ -15,6 +18,10 @@ export class Logger {
});
}
public static getLevel(): string {
return Settings.get(SETTING_LOGGING) || 'info';
}
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
@@ -22,19 +29,43 @@ export class Logger {
return Logger.instance;
}
public static info(message: string, type: 'INFO' | 'WARNING' | 'ERROR' = 'INFO'): void {
public static info(
message: string,
location: LoggerLocation = 'VSCODE',
type: 'VERBOSE' | 'INFO' | 'WARNING' | 'ERROR' = 'INFO'
): void {
if (!Logger.channel) {
Logger.getInstance();
}
Logger.channel?.appendLine(`["${type}" - ${format(new Date(), 'HH:MM:ss')}] ${message}`);
const loggingLevel = Logger.getLevel();
const logMessage = `["${type}" - ${format(new Date(), 'HH:mm:ss')}] ${location} | ${message}`;
if (loggingLevel === 'verbose') {
Logger.log(logMessage);
} else if (loggingLevel === 'info' && type !== 'VERBOSE') {
Logger.log(logMessage);
} else if (loggingLevel === 'warning' && (type === 'WARNING' || type === 'ERROR')) {
Logger.log(logMessage);
} else if (loggingLevel === 'error' && type === 'ERROR') {
Logger.log(logMessage);
}
}
public static warning(message: string): void {
Logger.info(message, 'WARNING');
public static warning(message: string, location: LoggerLocation = 'VSCODE'): void {
Logger.info(message, location, 'WARNING');
}
public static error(message: string): void {
Logger.info(message, 'ERROR');
public static error(message: string, location: LoggerLocation = 'VSCODE'): void {
Logger.info(message, location, 'ERROR');
}
public static verbose(message: string, location: LoggerLocation = 'VSCODE'): void {
Logger.info(message, location, 'VERBOSE');
}
private static log(message: string) {
Logger.channel?.appendLine(message);
}
}
+5 -3
View File
@@ -50,7 +50,7 @@ export class MediaHelpers {
) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Folders.getStaticFolderRelativePath();
const contentFolders = Folders.get();
const contentFolders = await Folders.get();
const viewData = Dashboard.viewData;
let selectedFolder = requestedFolder;
@@ -398,12 +398,14 @@ export class MediaHelpers {
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
const articleCt =
article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
article && article.data
? await ArticleHelper.getContentType(article)
: DEFAULT_CONTENT_TYPE;
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ''), relPath);
const fileDir = parseWinPath(dirname(filePath));
const imgDir = parseWinPath(dirname(absImgPath));
const contentFolders = Folders.get();
const contentFolders = await Folders.get();
// Check if relative paths need to be created for the media files
if (articleCt.pageBundle) {
+2 -3
View File
@@ -6,7 +6,7 @@ import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders';
import { Notifications } from './Notifications';
import { parseWinPath } from './parseWinPath';
import { LocalStore } from '../constants';
import { existsAsync, renameAsync } from '../utils';
import { existsAsync, isWindows, renameAsync } from '../utils';
import { existsSync, mkdirSync, renameSync } from 'fs';
import { lookup } from 'mime-types';
import * as l10n from '@vscode/l10n';
@@ -176,9 +176,8 @@ export class MediaLibrary {
public parsePath(path: string) {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
let absPath = path.replace(parseWinPath(wsFolder?.fsPath || ''), WORKSPACE_PLACEHOLDER);
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
return absPath.toLowerCase();
}
+28 -4
View File
@@ -18,7 +18,7 @@ export class Notifications {
* @returns
*/
public static info(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'INFO');
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'INFO');
if (this.shouldShow('INFO')) {
return window.showInformationMessage(`${EXTENSION_NAME}: ${message}`, ...items);
@@ -34,7 +34,7 @@ export class Notifications {
* @returns
*/
public static warning(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'WARNING');
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'WARNING');
if (this.shouldShow('WARNING')) {
return window.showWarningMessage(`${EXTENSION_NAME}: ${message}`, ...items);
@@ -43,6 +43,30 @@ export class Notifications {
return Promise.resolve(undefined);
}
/**
* Show a warning notification to the user with a link to the output channel
* @param message
* @param items
* @returns
*/
public static warningWithOutput(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'WARNING');
if (this.shouldShow('WARNING')) {
return window.showWarningMessage(
`${EXTENSION_NAME}: ${message} ${l10n.t(
LocalizationKey.notificationsOutputChannelDescription,
`[${l10n.t(LocalizationKey.notificationsOutputChannelLink)}](command:${
COMMAND_NAME.showOutputChannel
})`
)}`,
...items
);
}
return Promise.resolve(undefined);
}
/**
* Show an error notification to the user
* @param message
@@ -50,7 +74,7 @@ export class Notifications {
* @returns
*/
public static error(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'ERROR');
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'ERROR');
if (this.shouldShow('ERROR')) {
return window.showErrorMessage(`${EXTENSION_NAME}: ${message}`, ...items);
@@ -66,7 +90,7 @@ export class Notifications {
* @returns
*/
public static errorWithOutput(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'ERROR');
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'ERROR');
if (this.shouldShow('ERROR')) {
return window.showErrorMessage(
+58 -51
View File
@@ -4,7 +4,7 @@ import {
SETTING_WEBSITE_URL
} from './../constants/settings';
import { workspace } from 'vscode';
import { ContentType, Extension, Settings, TaxonomyHelper } from '.';
import { ContentType, Extension, Logger, Settings, TaxonomyHelper } from '.';
import { Dashboard } from '../commands/Dashboard';
import { Preview } from '../commands/Preview';
import { Project } from '../commands/Project';
@@ -48,56 +48,63 @@ import { Folders } from '../commands';
export class PanelSettings {
public static async get(): Promise<IPanelSettings> {
return {
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
git: await GitListener.getSettings(),
seo: {
title: (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1,
slug: (Settings.get(SETTING_SEO_SLUG_LENGTH) as number) || -1,
description: (Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number) || -1,
content: (Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number) || -1,
titleField: (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title,
descriptionField:
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description
},
slug: {
prefix: Settings.get(SETTING_SLUG_PREFIX) || '',
suffix: Settings.get(SETTING_SLUG_SUFFIX) || '',
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME)
},
date: {
format: Settings.get<string>(SETTING_DATE_FORMAT) || ''
},
tags: (await TaxonomyHelper.get(TaxonomyType.Tag)) || [],
categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(
(s) => (s.type === ScriptType.Content || !s.type) && !s.hidden
),
isInitialized: await Project.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: Settings.get(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT),
preview: Preview.getSettings(),
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
contentTypes: ContentType.getAll() || [],
dashboardViewData: Dashboard.viewData,
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
isBacker: await Extension.getInstance().getState<boolean | undefined>(
CONTEXT.backer,
'global'
),
framework: Settings.get<string>(SETTING_FRAMEWORK_ID),
commands: {
start: Settings.get<string>(SETTING_FRAMEWORK_START)
},
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
fieldGroups: Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS),
contentFolders: Folders.get(),
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL) || '',
disabledActions: Settings.get<PanelAction[]>(SETTING_PANEL_ACTIONS_DISABLED) || []
};
Logger.info('Getting panel settings');
try {
return {
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
git: await GitListener.getSettings(),
seo: {
title: (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1,
slug: (Settings.get(SETTING_SEO_SLUG_LENGTH) as number) || -1,
description: (Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number) || -1,
content: (Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number) || -1,
titleField: (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title,
descriptionField:
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description
},
slug: {
prefix: Settings.get(SETTING_SLUG_PREFIX) || '',
suffix: Settings.get(SETTING_SLUG_SUFFIX) || '',
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME)
},
date: {
format: Settings.get<string>(SETTING_DATE_FORMAT) || ''
},
tags: (await TaxonomyHelper.get(TaxonomyType.Tag)) || [],
categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(
(s) => (s.type === ScriptType.Content || !s.type) && !s.hidden
),
isInitialized: await Project.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: Settings.get(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT),
preview: Preview.getSettings(),
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
contentTypes: ContentType.getAll() || [],
dashboardViewData: Dashboard.viewData,
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
isBacker: await Extension.getInstance().getState<boolean | undefined>(
CONTEXT.backer,
'global'
),
framework: Settings.get<string>(SETTING_FRAMEWORK_ID),
commands: {
start: Settings.get<string>(SETTING_FRAMEWORK_START)
},
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
fieldGroups: Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS),
contentFolders: await Folders.get(),
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL) || '',
disabledActions: Settings.get<PanelAction[]>(SETTING_PANEL_ACTIONS_DISABLED) || []
};
} catch (error) {
Logger.error(`Error getting panel settings: ${(error as Error).message}`);
return {} as IPanelSettings;
}
}
/**
+12 -12
View File
@@ -131,7 +131,8 @@ export class Questions {
public static async SelectContentFolder(
showWarning: boolean = true
): Promise<FolderQuickPickItem | undefined> {
let folders = Folders.get().filter((f) => !f.disableCreation);
let folders = await Folders.get();
folders = folders.filter((f) => !f.disableCreation);
let selectedFolder: FolderQuickPickItem | undefined;
if (folders.length > 1) {
@@ -149,16 +150,13 @@ export class Questions {
} as FolderQuickPickItem;
});
selectedFolder = await window.showQuickPick(
folderOptions,
{
title: l10n.t(LocalizationKey.helpersQuestionsSelectContentFolderQuickPickTitle),
placeHolder: l10n.t(
LocalizationKey.helpersQuestionsSelectContentFolderQuickPickPlaceholder
),
ignoreFocusOut: true
}
);
selectedFolder = await window.showQuickPick(folderOptions, {
title: l10n.t(LocalizationKey.helpersQuestionsSelectContentFolderQuickPickTitle),
placeHolder: l10n.t(
LocalizationKey.helpersQuestionsSelectContentFolderQuickPickPlaceholder
),
ignoreFocusOut: true
});
} else if (folders.length === 1) {
selectedFolder = {
label: folders[0].title,
@@ -213,7 +211,9 @@ export class Questions {
}));
if (options.length === 0) {
Notifications.error(LocalizationKey.helpersQuestionsSelectContentTypeQuickPickErrorNoContentTypes);
Notifications.error(
LocalizationKey.helpersQuestionsSelectContentTypeQuickPickErrorNoContentTypes
);
return;
}
+85 -7
View File
@@ -44,7 +44,8 @@ import {
SETTING_PROJECTS,
SETTING_TAXONOMY_TAGS,
SETTING_TAXONOMY_CATEGORIES,
SETTING_CONTENT_FILTERS
SETTING_CONTENT_FILTERS,
SETTING_CONTENT_I18N
} from '../constants';
import { Folders } from '../commands/Folders';
import { join, basename, dirname, parse } from 'path';
@@ -77,6 +78,15 @@ export class Settings {
private static readConfigPromise: Promise<void> | undefined = undefined;
private static project: Project | undefined = undefined;
private static configDebouncer = debounceCallback();
private static hasExtendedConfig: boolean = false;
private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any;
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.promote, Settings.promote));
}
public static async init() {
const allCommands = await commands.getCommands(true);
@@ -250,6 +260,7 @@ export class Settings {
*/
public static async onConfigChange() {
const projectConfig = await Settings.projectConfigPath();
Logger.info(`Project config path: ${projectConfig}`);
workspace.onDidChangeConfiguration(() => {
Settings.triggerListeners();
@@ -366,12 +377,54 @@ export class Settings {
return setting;
}
/**
* Safely updates a setting value.
*
* @param name - The name of the setting.
* @param value - The new value for the setting.
* @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`.
* @returns A promise that resolves when the setting is updated.
*/
public static async safeUpdate<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
if (Settings.hasExtendedConfig) {
const configKey = `${CONFIG_KEY}.${name}`;
if (
Settings.extendedConfig.extended.includes(configKey) ||
Settings.extendedConfig.splitted.includes(configKey) ||
Settings.extendedConfig.dynamic
) {
Notifications.warningWithOutput(
l10n.t(LocalizationKey.helpersSettingsHelperSafeUpdateWarning, configKey)
);
Logger.info(`Updating setting: ${configKey}`, 'SETTING');
Logger.info(
`New value:
${JSON.stringify(value, null, 2)}`,
'SETTING'
);
return;
}
}
await Settings.update<T>(name, value, updateGlobal);
}
/**
* String update config setting
* @param name
* @param value
*/
public static async update<T>(name: string, value: T, updateGlobal: boolean = false) {
public static async update<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
const fmConfig = await Settings.projectConfigPath();
if (updateGlobal) {
@@ -415,7 +468,7 @@ export class Settings {
const workspaceSettingValue = Settings.hasWorkspaceSettings<ContentType[]>(name);
if (workspaceSettingValue) {
await Settings.update(name, undefined);
await Settings.safeUpdate(name, undefined);
}
// Make sure to reload the whole config + all the data files
@@ -507,7 +560,7 @@ export class Settings {
customTaxonomies[taxIdx].options.push(option);
customTaxonomies[taxIdx].options = [...new Set(customTaxonomies[taxIdx].options)];
customTaxonomies[taxIdx].options = customTaxonomies[taxIdx].options.sort().filter((o) => !!o);
await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
}
/**
@@ -524,7 +577,7 @@ export class Settings {
customTaxonomies[taxIdx].options = options;
}
await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
}
/**
@@ -540,8 +593,8 @@ export class Settings {
const setting = Settings.config.inspect(settingName);
if (setting && typeof setting.workspaceValue !== 'undefined') {
await Settings.update(settingName, setting.workspaceValue, true);
await Settings.update(settingName, undefined);
await Settings.safeUpdate(settingName, setting.workspaceValue, true);
await Settings.safeUpdate(settingName, undefined);
}
}
}
@@ -644,6 +697,13 @@ export class Settings {
*/
private static async readConfig() {
try {
Settings.hasExtendedConfig = false;
Settings.extendedConfig = {
extended: [],
splitted: [],
dynamic: false
};
const fmConfig = await Settings.projectConfigPath();
if (fmConfig && (await existsAsync(fmConfig))) {
const localConfig = await readFileAsync(fmConfig, 'utf8');
@@ -663,6 +723,8 @@ export class Settings {
);
if (configFiles.length === 0) {
Logger.info(`No ".frontmatter/config" config files found.`);
} else {
Settings.hasExtendedConfig = true;
}
// Sort the files by fsPath
@@ -699,6 +761,8 @@ export class Settings {
);
if (dynamicConfig) {
Settings.hasExtendedConfig = true;
Settings.extendedConfig.dynamic = true;
Settings.globalConfig = dynamicConfig;
Logger.info(`Dynamic config file loaded`);
}
@@ -730,11 +794,17 @@ export class Settings {
return;
}
Settings.hasExtendedConfig = true;
const originalConfig = Object.assign({}, Settings.globalConfig);
const extendsConfig: string[] = Settings.globalConfig[extendsConfigName];
for (const externalConfig of extendsConfig) {
if (externalConfig.endsWith(`.json`)) {
const config = await Settings.getExternalConfig(externalConfig);
// Store the config keys
const keys = Object.keys(config);
Settings.extendedConfig.extended.push(...keys);
await Settings.extendConfig(config, originalConfig);
}
}
@@ -769,6 +839,7 @@ export class Settings {
Settings.globalConfig = {};
}
Settings.extendedConfig.splitted.push(`${CONFIG_KEY}.${relSettingName}`);
Settings.updateGlobalConfigSetting(relSettingName, configJson, configFilePath, filePath);
} catch (e) {
Logger.error(`Error reading config file: ${configFile.fsPath}`);
@@ -922,6 +993,10 @@ export class Settings {
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_PROJECTS)) {
Settings.updateGlobalConfigArraySetting(SETTING_PROJECTS, 'name', configJson);
}
// i18n
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_I18N)) {
Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_I18N, 'locale', configJson);
}
// Snippets
else if (
Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SNIPPETS) &&
@@ -1138,6 +1213,9 @@ export class Settings {
*/
private static async reloadConfig(debounced: boolean = true) {
Logger.info(`Reloading config...`);
// Clear the folder cache as we need to see the latest folders
Folders.clearCached();
if (Settings.readConfigPromise === undefined) {
Settings.readConfigPromise = Settings.readConfig();
}
+5 -2
View File
@@ -74,7 +74,9 @@ export class TaxonomyHelper {
* @param options
*/
public static async get(type: TaxonomyType): Promise<string[] | undefined> {
Logger.verbose(`TaxonomyHelper:get:start:${type}`);
if (!TaxonomyHelper.db) {
Logger.info(`TaxonomyHelper:get:db-not-initialized`);
return;
}
@@ -84,6 +86,7 @@ export class TaxonomyHelper {
if (await TaxonomyHelper.db.exists(tagType)) {
taxonomy = await TaxonomyHelper.db.getObject<string[]>(tagType);
}
Logger.verbose(`TaxonomyHelper:get:end:${type}`);
return taxonomy;
}
@@ -344,7 +347,7 @@ export class TaxonomyHelper {
if (mdFile) {
try {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
let fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
@@ -479,7 +482,7 @@ export class TaxonomyHelper {
if (mdFile) {
try {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
let oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
let newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
+2
View File
@@ -32,6 +32,8 @@ export * from './isValidFile';
export * from './openFileInEditor';
export * from './parseWinPath';
export * from './processArticlePlaceholders';
export * from './processDateTimePlaceholders';
export * from './processFmPlaceholders';
export * from './processI18nPlaceholders';
export * from './processPathPlaceholders';
export * from './processTimePlaceholders';
+1 -1
View File
@@ -38,7 +38,7 @@ export const processArticlePlaceholdersFromPath = async (
}
if (value.includes('{{slug}}') && filePath) {
const contentType = article ? ArticleHelper.getContentType(article) : undefined;
const contentType = article ? await ArticleHelper.getContentType(article) : undefined;
if (contentType) {
const regex = new RegExp('{{slug}}', 'g');
value = value.replace(
@@ -0,0 +1,43 @@
import { format } from 'date-fns';
import { DateHelper } from './DateHelper';
/**
* Replace the datetime placeholders
* @param value
* @param dateFormat
* @param articleDate
* @returns
*/
export const processDateTimePlaceholders = (
value: string,
dateFormat?: string,
articleDate?: Date
) => {
if (value && typeof value === 'string') {
if (value.includes(`{{date|`)) {
const regex = /{{date\|[^}]*}}/g;
const matches = value.match(regex);
if (matches) {
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let dateFormat = placeholderParts[1].trim().replace('}}', '');
if (dateFormat) {
if (dateFormat && typeof dateFormat === 'string') {
value = value.replace(
match,
format(articleDate || new Date(), DateHelper.formatUpdate(dateFormat) as string)
);
} else {
value = value.replace(match, (articleDate || new Date()).toISOString());
}
}
}
}
}
}
}
return value;
};
+39
View File
@@ -0,0 +1,39 @@
import { ContentFolder } from '../models';
export const processI18nPlaceholders = (
value: string,
contentFolder: ContentFolder | null | undefined
) => {
// Example: {{locale}}
if (value && contentFolder?.locale) {
if (value.includes('{{locale}}')) {
const regex = new RegExp('{{locale}}', 'g');
value = value.replace(regex, contentFolder.locale);
}
// Example: {{locale | ignore:en}}
else if (value.includes('{{locale')) {
const regex = /{{locale[^}]*}}/g;
const matches = value.match(regex);
if (matches) {
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let ignore = placeholderParts[1].trim().replace('}}', '');
if (ignore.startsWith('ignore:')) {
ignore = ignore.replace('ignore:', '');
if (ignore.includes(contentFolder.locale)) {
value = value.replace(match, '');
} else {
value = value.replace(match, contentFolder.locale);
}
}
}
}
}
}
}
return value;
};
+11 -11
View File
@@ -4,10 +4,10 @@ import { DateHelper } from './DateHelper';
/**
* Replace the time placeholders
* @param value
* @param title
* @param dateFormat
* @returns
*/
export const processTimePlaceholders = (value: string, dateFormat?: string) => {
export const processTimePlaceholders = (value: string, dateFormat?: string, articleDate?: Date) => {
if (value && typeof value === 'string') {
if (value.includes('{{now}}')) {
const regex = new RegExp('{{now}}', 'g');
@@ -15,46 +15,46 @@ export const processTimePlaceholders = (value: string, dateFormat?: string) => {
if (dateFormat && typeof dateFormat === 'string') {
value = value.replace(
regex,
format(new Date(), DateHelper.formatUpdate(dateFormat) as string)
format(articleDate || new Date(), DateHelper.formatUpdate(dateFormat) as string)
);
} else {
value = value.replace(regex, new Date().toISOString());
value = value.replace(regex, (articleDate || new Date()).toISOString());
}
}
if (value.includes('{{year}}')) {
const regex = new RegExp('{{year}}', 'g');
value = value.replace(regex, format(new Date(), 'yyyy'));
value = value.replace(regex, format(articleDate || new Date(), 'yyyy'));
}
if (value.includes('{{month}}')) {
const regex = new RegExp('{{month}}', 'g');
value = value.replace(regex, format(new Date(), 'MM'));
value = value.replace(regex, format(articleDate || new Date(), 'MM'));
}
if (value.includes('{{day}}')) {
const regex = new RegExp('{{day}}', 'g');
value = value.replace(regex, format(new Date(), 'dd'));
value = value.replace(regex, format(articleDate || new Date(), 'dd'));
}
if (value.includes('{{hour12}}')) {
const regex = new RegExp('{{hour12}}', 'g');
value = value.replace(regex, format(new Date(), 'hh'));
value = value.replace(regex, format(articleDate || new Date(), 'hh'));
}
if (value.includes('{{hour24}}')) {
const regex = new RegExp('{{hour24}}', 'g');
value = value.replace(regex, format(new Date(), 'HH'));
value = value.replace(regex, format(articleDate || new Date(), 'HH'));
}
if (value.includes('{{ampm}}')) {
const regex = new RegExp('{{ampm}}', 'g');
value = value.replace(regex, format(new Date(), 'aaa'));
value = value.replace(regex, format(articleDate || new Date(), 'aaa'));
}
if (value.includes('{{minute}}')) {
const regex = new RegExp('{{minute}}', 'g');
value = value.replace(regex, format(new Date(), 'mm'));
value = value.replace(regex, format(articleDate || new Date(), 'mm'));
}
}
+6 -1
View File
@@ -13,7 +13,12 @@ export abstract class BaseListener {
* @param data
*/
public static sendMsg(command: DashboardCommand, payload: any) {
Logger.info(`Sending message to dashboard: ${command}`);
if (command === DashboardCommand.loading) {
const loadingType = payload ? `- ${JSON.stringify(payload)}` : '- Turn off';
Logger.verbose(`Sending message to dashboard: ${command} ${loadingType}`);
} else {
Logger.verbose(`Sending message to dashboard: ${command}`);
}
Dashboard.postWebviewMessage({
command,
@@ -20,6 +20,9 @@ export class DashboardListener extends BaseListener {
super.process(msg);
switch (msg.command) {
case DashboardMessage.setTitle:
Dashboard.setTitle(msg.payload);
break;
case DashboardMessage.getViewType:
if (Dashboard.viewData) {
this.sendMsg(DashboardCommand.viewData, Dashboard.viewData);
@@ -34,7 +34,7 @@ export class LocalizationListener extends BaseListener {
return;
}
const config = i18n.getAll();
const config = await i18n.getAll();
this.sendRequest(command as any, requestId, config);
}
}
+9 -9
View File
@@ -69,9 +69,9 @@ export class PagesListener extends BaseListener {
if (ArticleHelper.isSupportedFile(doc)) {
Logger.info(`File saved ${doc.uri.fsPath}`);
// Optimize the list of recently changed files
DataListener.getFoldersAndFiles();
DataListener.getFoldersAndFiles(doc.uri);
// Trigger the metadata update
this.watcherExec(doc.uri);
this.watcherExec(doc.uri, 'save');
}
});
}
@@ -80,7 +80,7 @@ export class PagesListener extends BaseListener {
* Start watching the folders in the current workspace for content changes
*/
public static async startWatchers() {
const folders = Folders.get();
const folders = await Folders.get();
if (!folders || folders.length === 0) {
return;
@@ -103,9 +103,9 @@ export class PagesListener extends BaseListener {
false,
false
);
watcher.onDidCreate(async (uri: Uri) => this.watcherExec(uri));
watcher.onDidChange(async (uri: Uri) => this.watcherExec(uri));
watcher.onDidDelete(async (uri: Uri) => this.watcherExec(uri));
watcher.onDidCreate(async (uri: Uri) => this.watcherExec(uri, 'create'));
watcher.onDidChange(async (uri: Uri) => this.watcherExec(uri, 'change'));
watcher.onDidDelete(async (uri: Uri) => this.watcherExec(uri, 'delete'));
this.watchers[folderUri.fsPath] = watcher;
}
}
@@ -124,7 +124,7 @@ export class PagesListener extends BaseListener {
if (!article) {
return;
}
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
Logger.info(`Deleting file: ${path}`);
@@ -162,10 +162,10 @@ export class PagesListener extends BaseListener {
* Watcher for processing page updates
* @param file
*/
private static async watcherExec(file: Uri) {
private static async watcherExec(file: Uri, type?: 'create' | 'change' | 'delete' | 'save') {
const progressFile = async (file: Uri) => {
const ext = Extension.getInstance();
Logger.info(`File watcher execution for: ${file.fsPath}`);
Logger.info(`File watcher execution for (${type}): ${file.fsPath}`);
const pageIdx = this.lastPages.findIndex((p) => p.fmFilePath === file.fsPath);
if (pageIdx !== -1) {
+12 -8
View File
@@ -11,7 +11,7 @@ import {
} from '../../constants';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
import { DashboardSettings, Extension, Notifications, Settings } from '../../helpers';
import { DashboardSettings, Extension, Logger, Notifications, Settings } from '../../helpers';
import { FrameworkDetector } from '../../helpers/FrameworkDetector';
import { Framework, Template, PostMessageData, StaticFolder, LoadingType } from '../../models';
import { BaseListener } from './BaseListener';
@@ -101,7 +101,7 @@ export class SettingsListener extends BaseListener {
if (typeof setting.name !== 'undefined' && typeof setting.value !== 'undefined') {
const value = Settings.get(setting.name);
if (value !== setting.value) {
await Settings.update(setting.name, setting.value, true);
await Settings.safeUpdate(setting.name, setting.value, true);
}
}
}
@@ -190,7 +190,7 @@ export class SettingsListener extends BaseListener {
*/
private static async update(data: { name: string; value: any; global?: boolean }) {
if (data.name) {
await Settings.update(data.name, data.value, data.global);
await Settings.safeUpdate(data.name, data.value, data.global);
this.getSettings(true);
}
}
@@ -199,7 +199,11 @@ export class SettingsListener extends BaseListener {
* Retrieve the settings for the dashboard
*/
public static async getSettings(clear: boolean = false) {
Logger.verbose(`SettingsListener:getSettings:start - clear: ${clear}`);
const settings = await DashboardSettings.get(clear);
Logger.verbose(
`SettingsListener:getSettings:end - setting keys: ${Object.keys(settings).length}`
);
this.sendMsg(DashboardCommand.settings, settings);
}
@@ -209,7 +213,7 @@ export class SettingsListener extends BaseListener {
* @param frameworkId
*/
public static async setFramework(frameworkId: string | null) {
await Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true);
await Settings.safeUpdate(SETTING_FRAMEWORK_ID, frameworkId, true);
if (frameworkId) {
const allFrameworks = FrameworkDetector.getAll();
@@ -218,16 +222,16 @@ export class SettingsListener extends BaseListener {
);
if (framework) {
if (framework.static && typeof framework.static === 'string') {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
}
if (framework.server) {
await Settings.update(SETTING_PREVIEW_HOST, framework.server, true);
await Settings.safeUpdate(SETTING_PREVIEW_HOST, framework.server, true);
}
await FrameworkDetector.checkDefaultSettings(framework);
} else {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, '', true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, '', true);
}
}
@@ -235,7 +239,7 @@ export class SettingsListener extends BaseListener {
}
public static async addAssetsFolder(assetFolder: string | StaticFolder) {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true);
SettingsListener.getSettings(true);
}
+2 -2
View File
@@ -71,7 +71,7 @@ export class SnippetListener extends BaseListener {
snippets[title] = snippetContent;
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippets, true);
SettingsListener.getSettings(true);
}
@@ -93,7 +93,7 @@ export class SnippetListener extends BaseListener {
return acc;
}, {} as Snippets);
await Settings.update(SETTING_CONTENT_SNIPPETS, snippetsToStore, true);
await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippetsToStore, true);
SettingsListener.getSettings(true);
}
+1 -1
View File
@@ -97,7 +97,7 @@ export class SsgListener extends BaseListener {
}
contentTypes.push(contentType);
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
SsgListener.sendRequest(command as any, requestId, {});
SettingsListener.getSettings(true);
+10 -7
View File
@@ -3,10 +3,10 @@ import { Dashboard } from '../../commands/Dashboard';
import { PanelProvider } from '../../panelWebView/PanelProvider';
import { ArticleHelper, Extension } from '../../helpers';
import { Logger } from '../../helpers/Logger';
import { commands, Uri, window, workspace } from 'vscode';
import { commands, Uri, window } from 'vscode';
import { PostMessageData } from '../../models';
import { Preview } from '../../commands';
import { urlJoin } from 'url-join-ts';
import { joinUrl } from '../../utils';
export abstract class BaseListener {
public static process(msg: PostMessageData) {
@@ -25,14 +25,17 @@ export abstract class BaseListener {
commands.executeCommand(command, args);
}
break;
case GeneralCommands.toVSCode.logging.verbose:
Logger.verbose(msg.payload.message, msg.payload.location);
break;
case GeneralCommands.toVSCode.logging.info:
Logger.info(msg.payload);
Logger.info(msg.payload.message, msg.payload.location);
break;
case GeneralCommands.toVSCode.logging.warn:
Logger.warning(msg.payload);
Logger.warning(msg.payload.message, msg.payload.location);
break;
case GeneralCommands.toVSCode.logging.error:
Logger.error(msg.payload);
Logger.error(msg.payload.message, msg.payload.location);
break;
}
}
@@ -43,7 +46,7 @@ export abstract class BaseListener {
* @param data
*/
public static sendMsg(command: string, payload: any) {
Logger.info(`Sending message to webview (panel&dashboard): ${command}`);
Logger.verbose(`Sending message to webview (panel&dashboard): ${command}`);
const extPath = Extension.getInstance().extensionPath;
const panel = PanelProvider.getInstance(extPath);
@@ -81,7 +84,7 @@ export abstract class BaseListener {
const slug = await Preview.getContentSlug(article, filePath);
const fullUrl = urlJoin(websiteUrl, slug);
const fullUrl = joinUrl(websiteUrl, slug);
commands.executeCommand('vscode.open', fullUrl);
}
}
+12 -1
View File
@@ -56,9 +56,19 @@ export class GitListener {
* requiresCommitMessage: string[]
* }>} The Git settings.
*/
public static async getSettings() {
public static async getSettings(): Promise<
| {
isGitRepo: boolean;
actions: boolean;
disabledBranches: string[];
requiresCommitMessage: string[];
}
| undefined
> {
Logger.verbose('GitListener:getSettings:start');
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
if (gitActions) {
Logger.verbose('GitListener:getSettings:end:enabled');
return {
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
actions: gitActions || false,
@@ -71,6 +81,7 @@ export class GitListener {
};
}
Logger.verbose('GitListener:getSettings:end:disabled');
return;
}
+71 -12
View File
@@ -5,7 +5,7 @@ import { Folders } from '../../commands/Folders';
import { Command } from '../../panelWebView/Command';
import { CommandToCode } from '../../panelWebView/CommandToCode';
import { BaseListener } from './BaseListener';
import { authentication, commands, window } from 'vscode';
import { Uri, authentication, commands, window } from 'vscode';
import {
ArticleHelper,
Extension,
@@ -14,7 +14,8 @@ import {
ContentType,
processArticlePlaceholdersFromData,
processTimePlaceholders,
processFmPlaceholders
processFmPlaceholders,
parseWinPath
} from '../../helpers';
import {
COMMAND_NAME,
@@ -29,7 +30,13 @@ import {
} from '../../constants';
import { Article, Preview } from '../../commands';
import { FrontMatterParser, ParsedFrontMatter } from '../../parsers';
import { Field, Mode, PostMessageData, ContentType as IContentType } from '../../models';
import {
Field,
Mode,
PostMessageData,
ContentType as IContentType,
FolderInfo
} from '../../models';
import { encodeEmoji, fieldWhenClause } from '../../utils';
import { PanelProvider } from '../../panelWebView/PanelProvider';
import { MessageHandlerData } from '@estruyf/vscode';
@@ -37,11 +44,13 @@ import { SponsorAi } from '../../services/SponsorAI';
import { Terminal } from '../../services';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import { parse } from 'path';
const FILE_LIMIT = 10;
export class DataListener extends BaseListener {
private static lastMetadataUpdate: any = {};
private static folderInfo: FolderInfo[] | null = null;
/**
* Process the messages for the dashboard views
@@ -158,7 +167,8 @@ export class DataListener extends BaseListener {
/**
* Retrieve the information about the registered folders and its files
*/
public static async getFoldersAndFiles() {
public static async getFoldersAndFiles(file?: Uri) {
Logger.verbose('DataListener:getFoldersAndFiles:start');
const mode = Settings.get<string | null>(SETTING_GLOBAL_ACTIVE_MODE);
const modes = Settings.get<Mode[]>(SETTING_GLOBAL_MODES);
@@ -175,9 +185,41 @@ export class DataListener extends BaseListener {
}
}
const folders = (await Folders.getInfo(FILE_LIMIT)) || null;
if (file && DataListener.folderInfo && DataListener.folderInfo.length > 0) {
Logger.verbose('DataListener:getFoldersAndFiles:updateFile');
const folderPath = parseWinPath(parse(file.fsPath).dir);
const folderInfo = DataListener.folderInfo.find((f) => parseWinPath(f.path) === folderPath);
if (folderInfo) {
// Check if file exists
let fileInfo = folderInfo.lastModified.find(
(f) => parseWinPath(f.filePath) === parseWinPath(file.fsPath)
);
this.sendMsg(Command.folderInfo, folders);
if (fileInfo) {
folderInfo.lastModified = folderInfo.lastModified.filter(
(f) => parseWinPath(f.filePath) !== parseWinPath(file.fsPath)
);
}
fileInfo = await Folders.getFileStats(file, folderInfo.path);
if (fileInfo) {
folderInfo.lastModified.unshift(fileInfo);
// Limit the amount of files
if (folderInfo.lastModified.length > FILE_LIMIT) {
folderInfo.lastModified = folderInfo.lastModified.slice(0, FILE_LIMIT);
}
Logger.verbose('DataListener:getFoldersAndFiles:end - with file update only');
this.sendMsg(Command.folderInfo, DataListener.folderInfo);
return;
}
}
}
Logger.verbose('DataListener:getFoldersAndFiles:end');
DataListener.folderInfo = (await Folders.getInfo(FILE_LIMIT)) || null;
this.sendMsg(Command.folderInfo, DataListener.folderInfo);
}
/**
@@ -196,7 +238,7 @@ export class DataListener extends BaseListener {
let articleDetails = null;
try {
if (filePath) {
if (filePath && !filePath.startsWith('extension-output-eliostruyf')) {
articleDetails = await ArticleHelper.getDetails(filePath);
if (!articleDetails || articleDetails === 'nodata') {
@@ -251,7 +293,7 @@ export class DataListener extends BaseListener {
if (keys.length > 0 && contentTypes && wsFolder) {
// Get the current content type
const contentType = ArticleHelper.getContentType({
const contentType = await ArticleHelper.getContentType({
content: '',
data: updatedMetadata,
path: filePath
@@ -303,6 +345,7 @@ export class DataListener extends BaseListener {
return;
}
let beforeValue: any;
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
const editor = window.activeTextEditor;
@@ -319,7 +362,7 @@ export class DataListener extends BaseListener {
return;
}
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
const sourceField = ContentType.findFieldByName(contentType.fields, field);
if (!value && field !== titleField && contentType.clearEmpty) {
@@ -339,7 +382,7 @@ export class DataListener extends BaseListener {
const fieldsWithEmojiEncoding = contentType.fields.filter((f) => f.encodeEmoji);
// Support multi-level fields
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
const parentObj = await DataListener.getParentObject(article.data, article, parents, blockData);
// Check multi-image fields
const multiImageFieldsArray = imageFields.find((f: Field[]) => {
@@ -393,6 +436,7 @@ export class DataListener extends BaseListener {
[field]: value
});
} else {
beforeValue = parentObj[field];
parentObj[field] = value;
}
}
@@ -438,6 +482,13 @@ export class DataListener extends BaseListener {
await ArticleHelper.updateByPath(filePath, article);
}
Logger.verbose(
`DataListener:updateMetadata: "${field}" - Before value: ${JSON.stringify(
beforeValue
)} - After value: ${JSON.stringify(value)}`,
'VSCODE'
);
this.pushMetadata(article.data);
}
@@ -449,7 +500,7 @@ export class DataListener extends BaseListener {
* @param blockData
* @returns
*/
public static getParentObject(
public static async getParentObject(
data: any,
article: ParsedFrontMatter,
parents: string[] | undefined,
@@ -457,7 +508,7 @@ export class DataListener extends BaseListener {
) {
let parentObj = data;
let allParents = Object.assign([], parents);
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
let selectedIndexes: number[] = [];
if (blockData?.selectedIndex) {
if (typeof blockData.selectedIndex === 'string') {
@@ -644,6 +695,7 @@ export class DataListener extends BaseListener {
let { field, value, data, contentType } = articleData;
value = value || '';
const valueBefore = value;
if (field) {
const crntFile = window.activeTextEditor?.document;
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
@@ -658,6 +710,13 @@ export class DataListener extends BaseListener {
);
}
Logger.verbose(
`DataListener:updatePlaceholder: "${field}" - Before value: ${JSON.stringify(
valueBefore
)} - After value: ${JSON.stringify(value)}`,
'VSCODE'
);
this.sendRequest(Command.updatePlaceholder, requestId, { field, value });
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ export class SettingsListener extends BaseListener {
* @param value
*/
private static async updateSetting(setting: string, value: any) {
await Settings.update(setting, value);
await Settings.safeUpdate(setting, value);
this.getSettings();
}
+9 -4
View File
@@ -134,7 +134,7 @@ export class TaxonomyListener extends BaseListener {
* @param tagType
* @param values
*/
private static updateTags(
private static async updateTags(
fieldName: string,
values: string[],
parents: string[],
@@ -148,7 +148,12 @@ export class TaxonomyListener extends BaseListener {
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
const parentObj = await DataListener.getParentObject(
article.data,
article,
parents,
blockData
);
if (renderAsString) {
if (values.length === 0) {
@@ -170,7 +175,7 @@ export class TaxonomyListener extends BaseListener {
* Update the tags in the current document
* @param data
*/
private static updateCustomTaxonomy(data: CustomTaxonomyData) {
private static async updateCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.name) {
return;
}
@@ -182,7 +187,7 @@ export class TaxonomyListener extends BaseListener {
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
const parentObj = DataListener.getParentObject(
const parentObj = await DataListener.getParentObject(
article.data,
article,
data.parents,
+12
View File
@@ -887,6 +887,14 @@ export enum LocalizationKey {
* Open
*/
dashboardPreviewButtonOpenTitle = 'dashboard.preview.button.open.title',
/**
* Content snippet
*/
dashboardSnippetsViewItemTypeContent = 'dashboard.snippetsView.item.type.content',
/**
* Media snippet
*/
dashboardSnippetsViewItemTypeMedia = 'dashboard.snippetsView.item.type.media',
/**
* Edit snippet
*/
@@ -2388,6 +2396,10 @@ export enum LocalizationKey {
* Settings have been refreshed.
*/
helpersSettingsHelperRefreshConfigSuccess = 'helpers.settingsHelper.refreshConfig.success',
/**
* 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.
*/
helpersSettingsHelperSafeUpdateWarning = 'helpers.settingsHelper.safeUpdate.warning',
/**
* Rename the {0}
*/
+3 -2
View File
@@ -7,11 +7,12 @@ export interface ContentFolder {
disableCreation?: boolean;
excludeSubdir?: boolean;
previewPath?: string;
trailingSlash?: boolean;
filePrefix?: string;
contentTypes?: string[];
originalPath?: string;
$schema?: string;
extended?: boolean;
$schema?: string; // Extended config
extended?: boolean; // Extended config
locale?: string;
localeTitle?: string;
+4
View File
@@ -59,6 +59,7 @@ export interface ContentType {
fileType?: 'md' | 'mdx' | string;
previewPath?: string | null;
trailingSlash?: boolean; // Add this property for trailing slash option
slugTemplate?: string;
pageBundle?: boolean;
defaultFileName?: string;
@@ -189,6 +190,7 @@ export interface Slug {
export interface FolderInfo {
title: string;
path: string;
files: number;
lastModified: FileInfo[];
locale?: string;
@@ -199,6 +201,7 @@ export interface FileInfo extends FileStat {
filePath: string;
fileName: string;
folderName: string | undefined;
folderPath: string;
}
export interface CustomScript {
@@ -227,6 +230,7 @@ export interface EnvironmentScript {
export interface PreviewSettings {
host: string | undefined;
pathname: string | undefined;
trailingSlash: boolean | undefined;
}
export interface CustomTaxonomy {
+4 -1
View File
@@ -29,6 +29,7 @@ import { Telemetry } from '../helpers/Telemetry';
import { GitListener, ModeListener } from '../listeners/general';
import { Folders } from '../commands';
import { basename } from 'path';
import { ignoreMsgCommand } from '../utils';
export class PanelProvider implements WebviewViewProvider, Disposable {
public static readonly viewType = 'frontMatter.explorer';
@@ -100,7 +101,9 @@ export class PanelProvider implements WebviewViewProvider, Disposable {
this.updateCurrentFile();
webviewView.webview.onDidReceiveMessage(async (msg) => {
Logger.info(`Receiving message from webview to panel: ${msg.command}`);
if (!ignoreMsgCommand(msg.command)) {
Logger.info(`Receiving message from panel: ${msg.command}`);
}
LocalizationListener.process(msg);
FieldsListener.process(msg);
+47 -25
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import { Actions } from './components/Actions';
import { BaseView } from './components/BaseView';
import { GlobalSettings } from './components/GlobalSettings';
import { OtherActions } from './components/OtherActions';
import { SeoStatus } from './components/SeoStatus';
@@ -18,6 +17,7 @@ import { usePrevious } from './hooks/usePrevious';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { InitializeAction } from './components/InitializeAction';
import { DEFAULT_PANEL_FEATURE_FLAGS } from '../constants/DefaultFeatureFlags';
export interface IViewPanelProps { }
@@ -38,16 +38,28 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
const prevMediaSelection = usePrevious(mediaSelecting);
const [scrollY, setScrollY] = useState(0);
const allPanelValues = useMemo(() => {
return Object.values(FEATURE_FLAG.panel).filter(v => v !== FEATURE_FLAG.panel.globalSettings)
}, [FEATURE_FLAG.panel]);
const scollListener = useCallback((e: Event) => {
if (!mediaSelecting) {
setScrollY(window.scrollY);
}
}, [mediaSelecting]);
const isSomethingShown = useMemo(() => {
const panelModeValues = (mode?.features || DEFAULT_PANEL_FEATURE_FLAGS).filter(v => v.startsWith('panel.'));
if (panelModeValues.length === 0) {
return false;
}
if (panelModeValues.includes(FEATURE_FLAG.panel.globalSettings) ||
panelModeValues.includes(FEATURE_FLAG.panel.actions) ||
panelModeValues.includes(FEATURE_FLAG.panel.recentlyModified) ||
panelModeValues.includes(FEATURE_FLAG.panel.otherActions) ||
panelModeValues.includes(FEATURE_FLAG.panel.gitActions)) {
return true;
}
}, [mode?.features]);
useEffect(() => {
if (prevMediaSelection && !mediaSelecting) {
setTimeout(() => {
@@ -86,13 +98,8 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
return <Spinner />;
}
if (!metadata) {
return <BaseView mode={mode} settings={settings} folderAndFiles={folderAndFiles} />;
}
return (
<div className="frontmatter">
<InitializeAction settings={settings} />
{
isDevMode && (
<div className="developer__bar">
@@ -112,18 +119,22 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
)
}
<InitializeAction settings={settings} />
<div className={`ext_actions`}>
<GitAction settings={settings} />
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.gitActions}>
<GitAction settings={settings} />
</FeatureFlag>
{!loading && (<CustomView metadata={metadata} />)}
<FeatureFlag features={mode?.features || [...allPanelValues]} flag={FEATURE_FLAG.panel.globalSettings}>
<GlobalSettings settings={settings} />
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.globalSettings}>
<GlobalSettings settings={settings} isBase={!metadata} />
</FeatureFlag>
{
!loading && settings && settings.seo && (
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.seo}>
!loading && metadata && settings && settings.seo && (
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.seo}>
<SeoStatus
seo={settings.seo}
data={metadata}
@@ -134,33 +145,44 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
)
}
{!loading && settings && metadata && (
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.actions}>
<Actions metadata={metadata} settings={settings} scripts={settings.scripts} />
{!loading && settings && (
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.actions}>
<Actions
metadata={metadata}
settings={settings}
scripts={metadata ? settings.scripts : settings.scripts.filter((s) => s.bulk && (s.type === 'content' || !s.type))} />
</FeatureFlag>
)}
{
!loading && (
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.metadata}>
!loading && metadata && (
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.metadata}>
<Metadata
settings={settings}
metadata={metadata}
focusElm={focusElm}
unsetFocus={unsetFocus}
features={mode?.features || []}
features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS}
/>
</FeatureFlag>
)
}
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
<FolderAndFiles data={folderAndFiles} />
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.recentlyModified}>
<FolderAndFiles data={folderAndFiles} isBase={!metadata} />
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.otherActions}>
<OtherActions settings={settings} isFile={true} />
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.otherActions}>
<OtherActions settings={settings} isFile={!!metadata} isBase={!metadata} />
</FeatureFlag>
{
!isSomethingShown && (
<div className={`base__empty`}>
{l10n.t(LocalizationKey.panelBaseViewEmpty)}
</div>
)
}
</div>
<SponsorMsg isBacker={settings?.isBacker} />
-95
View File
@@ -1,95 +0,0 @@
import * as React from 'react';
import { FolderInfo, Mode, PanelSettings } from '../../models';
import { GlobalSettings } from './GlobalSettings';
import { OtherActions } from './OtherActions';
import { FolderAndFiles } from './FolderAndFiles';
import { SponsorMsg } from './SponsorMsg';
import { FeatureFlag } from '../../components/features/FeatureFlag';
import { FEATURE_FLAG } from '../../constants/Features';
import { GitAction } from './Git/GitAction';
import { useMemo } from 'react';
import * as l10n from "@vscode/l10n"
import { LocalizationKey } from '../../localization';
import { InitializeAction } from './InitializeAction';
import { Actions } from './Actions';
import { CustomView } from './CustomView';
export interface IBaseViewProps {
settings: PanelSettings | undefined;
folderAndFiles: FolderInfo[] | undefined;
mode: Mode | undefined;
}
const BaseView: React.FunctionComponent<IBaseViewProps> = ({
settings,
folderAndFiles,
mode
}: React.PropsWithChildren<IBaseViewProps>) => {
const customActions: any[] = (settings?.scripts || []).filter(
(s) => s.bulk && (s.type === 'content' || !s.type)
);
const allPanelValues = useMemo(() => {
return Object.values(FEATURE_FLAG.panel).filter(v => v !== FEATURE_FLAG.panel.globalSettings)
}, [FEATURE_FLAG.panel]);
const isSomethingShown = useMemo(() => {
const panelModeValues = (mode?.features || []).filter(v => v.startsWith('panel.'));
if (panelModeValues.length === 0) {
return true;
}
if (panelModeValues.includes(FEATURE_FLAG.panel.globalSettings) ||
panelModeValues.includes(FEATURE_FLAG.panel.actions) ||
panelModeValues.includes(FEATURE_FLAG.panel.recentlyModified) ||
panelModeValues.includes(FEATURE_FLAG.panel.otherActions)) {
return true;
}
}, [mode?.features]);
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<InitializeAction settings={settings} />
{settings?.isInitialized && (
<>
<GitAction settings={settings} />
<CustomView />
<FeatureFlag features={mode?.features || [...allPanelValues]} flag={FEATURE_FLAG.panel.globalSettings}>
<GlobalSettings settings={settings} isBase />
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.actions}>
<Actions settings={settings} scripts={customActions} />
</FeatureFlag>
</>
)}
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
<FolderAndFiles data={folderAndFiles} isBase />
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.otherActions}>
<OtherActions settings={settings} isFile={false} isBase />
</FeatureFlag>
</div>
{
!isSomethingShown && (
<div className={`base__empty`}>
{l10n.t(LocalizationKey.panelBaseViewEmpty)}
</div>
)
}
<SponsorMsg isBacker={settings?.isBacker} />
</div>
);
};
BaseView.displayName = 'BaseView';
export { BaseView };
+2 -1
View File
@@ -12,6 +12,7 @@ import { FEATURE_FLAG, GeneralCommands } from '../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import { DEFAULT_PANEL_FEATURE_FLAGS } from '../../constants/DefaultFeatureFlags';
export interface IMetadata {
[prop: string]: string[] | string | null | IMetadata;
@@ -95,7 +96,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({
id={`tags`}
title={`${l10n.t(LocalizationKey.panelMetadataTitle)}${contentType?.name ? ` (${contentType?.name})` : ""}`}
className={`inherit z-20`}>
<FeatureFlag features={features || []} flag={FEATURE_FLAG.panel.contentType}>
<FeatureFlag features={features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.contentType}>
<ContentTypeValidator fields={contentType?.fields || []} metadata={metadata} />
</FeatureFlag>
+7 -2
View File
@@ -6,13 +6,13 @@ import { FolderInfo } from '../../models/PanelSettings';
import { Command } from '../Command';
import { CommandToCode } from '../CommandToCode';
import { TagType } from '../TagType';
import { Messenger } from '@estruyf/vscode/dist/client';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { useRecoilState } from 'recoil';
import { PanelSettingsAtom } from '../state';
export default function useMessages() {
const [metadata, setMetadata] = useState<any>({});
const [metadata, setMetadata] = useState<any>(undefined);
const [settings, setSettings] = useRecoilState(PanelSettingsAtom);
const [loading, setLoading] = useState<boolean>(false);
const [focusElm, setFocus] = useState<TagType | null>(null);
@@ -23,6 +23,11 @@ export default function useMessages() {
const messageListener = (event: MessageEvent<EventData<any>>) => {
const message = event.data;
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Message received: ${message.command}`,
location: 'PANEL'
});
switch (message.command) {
case Command.metadata:
setMetadata(message.payload);
+1 -1
View File
@@ -1142,7 +1142,7 @@ vscode-divider {
align-items: center;
justify-content: center;
height: 100%;
font-size: 2em;
font-size: 1.25rem;
opacity: 0.8;
text-align: center;
padding: 1rem 1.25rem;
-2
View File
@@ -25,8 +25,6 @@ export default class ContentProvider implements TextDocumentContentProvider {
outputType?: string,
column: ViewColumn = ViewColumn.Beside
) {
const apiData = JSON.stringify(data, null, 2);
const uri = Uri.parse(`${ContentProvider.scheme}:${title} output`);
const doc = await workspace.openTextDocument(uri);
+12 -4
View File
@@ -80,12 +80,20 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
const range = MarkdownFoldingProvider.getFrontMatterRange();
if (range) {
if (MarkdownFoldingProvider.decType !== null) {
MarkdownFoldingProvider.decType.dispose();
}
const start = MarkdownFoldingProvider.start;
const end = MarkdownFoldingProvider.end;
if (fmHighlight) {
MarkdownFoldingProvider.decType = new FrontMatterDecorationProvider().get();
if (
MarkdownFoldingProvider.decType !== null &&
(range.start.line !== start || range.end.line !== end)
) {
MarkdownFoldingProvider.decType.dispose();
}
MarkdownFoldingProvider.decType = !MarkdownFoldingProvider.decType
? new FrontMatterDecorationProvider().get()
: MarkdownFoldingProvider.decType;
window.activeTextEditor?.setDecorations(MarkdownFoldingProvider.decType, [range]);
}
}
+14 -5
View File
@@ -77,7 +77,9 @@ export class PagesParser {
* Parse all pages in the workspace
*/
public static async parsePages() {
Logger.verbose('PagesParser:parsePages:start');
i18n.clearFiles();
Folders.clearCached();
const ext = Extension.getInstance();
// Update the dashboard with the fresh data
@@ -91,6 +93,7 @@ export class PagesParser {
PagesParser.pagesStatusBar.show();
for (const folder of folderInfo) {
Logger.verbose(`PagesParser:parsePages: Parsing folder ${folder.title} - ${folder.files}`);
for (const file of folder.lastModified) {
if (isValidFile(file.fileName)) {
try {
@@ -113,7 +116,7 @@ export class PagesParser {
continue;
}
Logger.error(`PagesParser::parsePages: ${file.filePath} - ${error.message}`);
Logger.error(`PagesParser:parsePages: ${file.filePath} - ${error.message}`);
Notifications.error(
l10n.t(
LocalizationKey.servicesPagesParserParsePagesFileError,
@@ -121,6 +124,8 @@ export class PagesParser {
)
);
}
} else {
Logger.info(`PagesParser:parsePages: Skipping file ${file.filePath}`);
}
}
}
@@ -133,6 +138,9 @@ export class PagesParser {
this.initialized = true;
PagesParser.allPages = [...pages];
PagesParser.pagesStatusBar.hide();
Folders.clearCached();
Logger.verbose('PagesParser:parsePages:end');
}
/**
@@ -180,9 +188,10 @@ export class PagesParser {
const descriptionField =
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
const dateField =
(await ArticleHelper.getPublishDateField(article)) || DefaultFields.PublishingDate;
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
let dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
const ctDateField = ContentType.findFieldByName(contentType.fields, dateField);
if (ctDateField && ctDateField.dateFormat) {
@@ -193,7 +202,7 @@ export class PagesParser {
? DateHelper.tryParse(article?.data[dateField], dateFormat)
: undefined;
const modifiedField = ArticleHelper.getModifiedDateField(article);
const modifiedField = await ArticleHelper.getModifiedDateField(article);
const modifiedFieldValue =
modifiedField?.name && article?.data[modifiedField.name]
? DateHelper.tryParse(article?.data[modifiedField.name])?.getTime()
@@ -226,7 +235,7 @@ export class PagesParser {
fmRelFileWsPath: FilesHelper.absToRelPath(filePath),
fmRelFilePath: parseWinPath(filePath).replace(wsFolder?.fsPath || '', ''),
fmFileName: fileName,
fmDraft: ContentType.getDraftStatus(article),
fmDraft: await ContentType.getDraftStatus(article),
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
+12
View File
@@ -0,0 +1,12 @@
import { GeneralCommands } from '../constants';
export const ignoreMsgCommand = (command: string) => {
const toIgnore = [
GeneralCommands.toVSCode.logging.verbose,
GeneralCommands.toVSCode.logging.info,
GeneralCommands.toVSCode.logging.warn,
GeneralCommands.toVSCode.logging.error
];
return toIgnore.includes(command);
};
+4
View File
@@ -6,11 +6,15 @@ export * from './fetchWithTimeout';
export * from './fieldWhenClause';
export * from './flattenObjectKeys';
export * from './getLocalizationFile';
export * from './ignoreMsgCommand';
export * from './isWindows';
export * from './joinUrl';
export * from './mkdirAsync';
export * from './readFileAsync';
export * from './readdirAsync';
export * from './renameAsync';
export * from './rmdirAsync';
export * from './sentryInit';
export * from './sortPages';
export * from './unlinkAsync';
export * from './writeFileAsync';
+1
View File
@@ -0,0 +1 @@
export const isWindows = () => process.platform === 'win32';

Some files were not shown because too many files have changed in this diff Show More