mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-09 10:22:55 +02:00
@@ -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.
|
||||
|
||||
Vendored
+6
-2
@@ -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"]
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+10
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Generated
+91
-108
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
];
|
||||
@@ -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: {
|
||||
|
||||
@@ -27,7 +27,8 @@ export const GeneralCommands = {
|
||||
logging: {
|
||||
info: 'logInfo',
|
||||
warn: 'logWarn',
|
||||
error: 'logError'
|
||||
error: 'logError',
|
||||
verbose: 'logVerbose'
|
||||
},
|
||||
runCommand: 'runCommand',
|
||||
getLocalization: 'getLocalization',
|
||||
|
||||
@@ -47,6 +47,8 @@ export const TelemetryEvent = {
|
||||
webviewContentsView: 'webviewContentsView',
|
||||
webviewSnippetsView: 'webviewSnippetsView',
|
||||
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
|
||||
webviewSettings: 'webviewSettings',
|
||||
webviewUnknown: 'webviewUnknown',
|
||||
|
||||
// Git
|
||||
gitSync: 'gitSync',
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -76,6 +76,7 @@ export enum DashboardMessage {
|
||||
runCustomScript = 'runCustomScript',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
showNotification = 'showNotification',
|
||||
setTitle = 'setTitle',
|
||||
|
||||
// Settings
|
||||
getSettings = 'getSettings',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+10
-7
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 };
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user