forked from iarv/vscode-front-matter
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a2a0934c2 | ||
|
|
d3eb7b223c | ||
|
|
f74eec954f | ||
|
|
864c4e7aa6 | ||
|
|
5667906caf | ||
|
|
2fe7c524e7 | ||
|
|
5cc83526ad | ||
|
|
76b5e99a08 | ||
|
|
7d5505d421 | ||
|
|
d97a11f8b5 | ||
|
|
0590cec684 | ||
|
|
b4cdc4feb9 | ||
|
|
986fd95524 | ||
|
|
f51fec5fb9 | ||
|
|
8198ce2af3 | ||
|
|
defffc4c8e | ||
|
|
8f47cbfb0b | ||
|
|
0be91c17d0 | ||
|
|
fae7ab8417 | ||
|
|
df239f2cc0 | ||
|
|
70099dc97f | ||
|
|
c11be0e3ec | ||
|
|
f8b7870180 | ||
|
|
c58a5c62d9 | ||
|
|
ce92444bf2 | ||
|
|
b2709ebffd | ||
|
|
2b20cf9d24 | ||
|
|
f4a499ad0f | ||
|
|
4494b158c0 | ||
|
|
3416e55264 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,5 +1,25 @@
|
||||
# Change Log
|
||||
|
||||
## [5.1.1] - 2021-10-14
|
||||
|
||||
- [#149](https://github.com/estruyf/vscode-front-matter/issues/149): Fix panel rendering when incorrect type for keywords is provided
|
||||
|
||||
## [5.1.0] - 2021-10-13
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#141](https://github.com/estruyf/vscode-front-matter/issues/141): Allow content creation for page bundles or single files
|
||||
- [#145](https://github.com/estruyf/vscode-front-matter/issues/145): Moved folder registration settings to `frontmatter.json` file
|
||||
- [#147](https://github.com/estruyf/vscode-front-matter/issues/147): Error boundary added for metadata fields
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- Rendered more hooks than during the previous render in `FileList`
|
||||
- [#142](https://github.com/estruyf/vscode-front-matter/issues/142): Fix for unknown tags where it throws an error
|
||||
- [#143](https://github.com/estruyf/vscode-front-matter/issues/143): Fix for duplicate values in the file list
|
||||
- [#144](https://github.com/estruyf/vscode-front-matter/issues/144): Fix for `toISOString` does not exist on object
|
||||
- [#146](https://github.com/estruyf/vscode-front-matter/issues/146): Date parsing logic added with fallbacks
|
||||
|
||||
## [5.0.0] - 2021-10-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.0.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
12
assets/icons/frontmatter-short-dark.svg
Normal file
12
assets/icons/frontmatter-short-dark.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#ffffff" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<path fill="#ffffff" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#ffffff" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
12
assets/icons/frontmatter-short-light.svg
Normal file
12
assets/icons/frontmatter-short-light.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#424242" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<path fill="#424242" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#424242" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
|
||||
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
|
||||
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
|
||||
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
|
||||
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -437,6 +437,25 @@ input:checked + .field__toggle__slider:before {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__error {
|
||||
color: var(--vscode-errorForeground);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metadata_field__error button {
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.metadata_field__error button:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.metadata_field__input, .metadata_field__input:focus,
|
||||
.metadata_field__textarea, .metadata_field__textarea:focus {
|
||||
outline: none;
|
||||
|
||||
178
package-lock.json
generated
178
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -236,6 +236,60 @@
|
||||
"integrity": "sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg==",
|
||||
"dev": true
|
||||
},
|
||||
"@microsoft/fast-element": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.6.0.tgz",
|
||||
"integrity": "sha512-ePTcBuCA99n7He0BYLIzFr5YOHYPSiBLJeDbDsyyQ5JUs4oXaZD0v54Pq0GAtSZuagnCGTDqcxEcBPUhTqLmQw==",
|
||||
"dev": true
|
||||
},
|
||||
"@microsoft/fast-foundation": {
|
||||
"version": "1.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-1.24.8.tgz",
|
||||
"integrity": "sha512-n4O9jPh8BBliF/Yl9FAVhrSoopsRCnva2L432s/fHwLelY9WUeswjO3DidVBFbzXD5u/gzC4LGWJScNe/ZGU4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/fast-element": "^1.4.0",
|
||||
"@microsoft/fast-web-utilities": "^4.8.0",
|
||||
"@microsoft/tsdoc-config": "^0.13.4",
|
||||
"tabbable": "^5.2.0",
|
||||
"tslib": "^1.13.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/fast-web-utilities": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-4.8.1.tgz",
|
||||
"integrity": "sha512-P3xeyUwQ9nPkFrgAdmkOzaXxIq8YqMU5K+LXcoHgJddJCBCKfGWW9OZQOTigLddItTyVyfO8qsJpDQb1TskKHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exenv-es6": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@microsoft/tsdoc": {
|
||||
"version": "0.12.24",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz",
|
||||
"integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==",
|
||||
"dev": true
|
||||
},
|
||||
"@microsoft/tsdoc-config": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.13.9.tgz",
|
||||
"integrity": "sha512-VqqZn+rT9f6XujFPFR2aN9XKF/fuir/IzKVzoxI0vXIzxysp4ee6S2jCakmlGFHEasibifFTsJr7IYmRPxfzYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/tsdoc": "0.12.24",
|
||||
"ajv": "~6.12.6",
|
||||
"jju": "~1.4.0",
|
||||
"resolve": "~1.19.0"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -269,92 +323,92 @@
|
||||
"dev": true
|
||||
},
|
||||
"@sentry/browser": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz",
|
||||
"integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.3.tgz",
|
||||
"integrity": "sha512-jwlpsk2/u1cofvfYsjmqcnx50JJtf/T6HTgdW+ih8+rqWC5ABEZf4IiB/H+KAyjJ3wVzCOugMq5irL83XDCfqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/core": "6.13.2",
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/utils": "6.13.2",
|
||||
"@sentry/core": "6.13.3",
|
||||
"@sentry/types": "6.13.3",
|
||||
"@sentry/utils": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz",
|
||||
"integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.3.tgz",
|
||||
"integrity": "sha512-obm3SjgCk8A7nB37b2AU1eq1q7gMoJRrGMv9VRIyfcG0Wlz/5lJ9O3ohUk+YZaaVfZMxXn6hFtsBiOWmlv7IIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/hub": "6.13.2",
|
||||
"@sentry/minimal": "6.13.2",
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/utils": "6.13.2",
|
||||
"@sentry/hub": "6.13.3",
|
||||
"@sentry/minimal": "6.13.3",
|
||||
"@sentry/types": "6.13.3",
|
||||
"@sentry/utils": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/hub": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz",
|
||||
"integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.3.tgz",
|
||||
"integrity": "sha512-eYppBVqvhs5cvm33snW2sxfcw6G20/74RbBn+E4WDo15hozis89kU7ZCJDOPkXuag3v1h9igns/kM6PNBb41dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/utils": "6.13.2",
|
||||
"@sentry/types": "6.13.3",
|
||||
"@sentry/utils": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/minimal": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz",
|
||||
"integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.3.tgz",
|
||||
"integrity": "sha512-63MlYYRni3fs5Bh8XBAfVZ+ctDdWg0fapSTP1ydIC37fKvbE+5zhyUqwrEKBIiclEApg1VKX7bkKxVdu/vsFdw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/hub": "6.13.2",
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/hub": "6.13.3",
|
||||
"@sentry/types": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/react": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.13.2.tgz",
|
||||
"integrity": "sha512-aLkWyn697LTcmK1PPnUg5UJcyBUPoI68motqgBY53SIYDAwOeYNUQt2aanDuOTY5aE2PdnJwU48klA8vuYkoRQ==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.13.3.tgz",
|
||||
"integrity": "sha512-fdfmD9XNpGDwdkeLyd+iq+kqtNeghpH3wiez2rD81ZBvrn70uKaO2/yYDE71AXC6fUOwQuJmdfAuqBcNJkYIEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/browser": "6.13.2",
|
||||
"@sentry/minimal": "6.13.2",
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/utils": "6.13.2",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@sentry/minimal": "6.13.3",
|
||||
"@sentry/types": "6.13.3",
|
||||
"@sentry/utils": "6.13.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/tracing": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.13.2.tgz",
|
||||
"integrity": "sha512-bHJz+C/nd6biWTNcYAu91JeRilsvVgaye4POkdzWSmD0XoLWHVMrpCQobGpXe7onkp2noU3YQjhqgtBqPHtnpw==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.13.3.tgz",
|
||||
"integrity": "sha512-yyOFIhqlprPM0g4f35Icear3eZk2mwyYcGEzljJfY2iU6pJwj1lzia5PfSwiCW7jFGMmlBJNhOAIpfhlliZi8Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/hub": "6.13.2",
|
||||
"@sentry/minimal": "6.13.2",
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/utils": "6.13.2",
|
||||
"@sentry/hub": "6.13.3",
|
||||
"@sentry/minimal": "6.13.3",
|
||||
"@sentry/types": "6.13.3",
|
||||
"@sentry/utils": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz",
|
||||
"integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.3.tgz",
|
||||
"integrity": "sha512-Vrz5CdhaTRSvCQjSyIFIaV9PodjAVFkzJkTRxyY7P77RcegMsRSsG1yzlvCtA99zG9+e6MfoJOgbOCwuZids5A==",
|
||||
"dev": true
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "6.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz",
|
||||
"integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==",
|
||||
"version": "6.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.3.tgz",
|
||||
"integrity": "sha512-zYFuFH3MaYtBZTeJ4Yajg7pDf0pM3MWs3+9k5my9Fd+eqNcl7dYQYJbT9gyC0HXK1QI4CAMNNlHNl4YXhF91ag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sentry/types": "6.13.2",
|
||||
"@sentry/types": "6.13.3",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
@@ -586,6 +640,16 @@
|
||||
"integrity": "sha512-LlO6K7nzrIWDCZN1Zi6J6ibxrpMibSAct+zNjAwpkNkwup6cJLx5diYvsOJODMPWOuQlBO21qkxtdkSRzW6+Jw==",
|
||||
"dev": true
|
||||
},
|
||||
"@vscode/webview-ui-toolkit": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-0.8.1.tgz",
|
||||
"integrity": "sha512-SOgeaZ+/6yFDXLsgH+JvKcs4E0ThvuohNhkr9mvnggjl+OfFxc+Yqkyf5B4B5kuu3EppOCzEMiUwPC8APuLEGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/fast-element": "^1.2.0",
|
||||
"@microsoft/fast-foundation": "^1.24.7"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
@@ -2264,6 +2328,12 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"exenv-es6": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.0.0.tgz",
|
||||
"integrity": "sha512-fcG/TX8Ruv9Ma6PBaiNsUrHRJzVzuFMP6LtPn/9iqR+nr9mcLeEOGzXQGLC5CVQSXGE98HtzW2mTZkrCA3XrDg==",
|
||||
"dev": true
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
@@ -3512,6 +3582,12 @@
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||
"dev": true
|
||||
},
|
||||
"jju": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
|
||||
"integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -3645,6 +3721,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.topath": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
|
||||
@@ -5774,6 +5856,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabbable": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.2.1.tgz",
|
||||
"integrity": "sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ==",
|
||||
"dev": true
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.7.tgz",
|
||||
|
||||
14
package.json
14
package.json
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter",
|
||||
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.1",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -314,6 +314,11 @@
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pageBundle": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specify if you want to create a folder when creating new content."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -325,6 +330,7 @@
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
@@ -658,8 +664,8 @@
|
||||
"@headlessui/react": "^1.4.1",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@types/glob": "7.1.3",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
@@ -671,6 +677,7 @@
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "1.51.0",
|
||||
"@vscode/codicons": "0.0.20",
|
||||
"@vscode/webview-ui-toolkit": "^0.8.1",
|
||||
"autoprefixer": "^10.3.2",
|
||||
"css-loader": "5.2.7",
|
||||
"date-fns": "2.23.0",
|
||||
@@ -681,6 +688,7 @@
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"image-size": "^1.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"mdast-util-from-markdown": "1.0.0",
|
||||
"node-json-db": "^1.3.0",
|
||||
|
||||
@@ -239,13 +239,13 @@ export class Article {
|
||||
/**
|
||||
* Format the date to the defined format
|
||||
*/
|
||||
public static formatDate(dateValue: Date) {
|
||||
public static formatDate(dateValue: Date): string {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
|
||||
if (dateFormat && typeof dateFormat === "string") {
|
||||
return format(dateValue, dateFormat);
|
||||
} else {
|
||||
return dateValue.toISOString();
|
||||
return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME } from '../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { basename, dirname, extname, join } from "path";
|
||||
import { basename, dirname, extname, join, parse } from "path";
|
||||
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
|
||||
import { Settings as SettingsHelper } from '../helpers';
|
||||
@@ -14,7 +14,6 @@ import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Settings } from '../dashboardWebView/models/Settings';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { ViewType } from '../dashboardWebView/state';
|
||||
import { EditorHelper, WebviewHelper } from '@estruyf/vscode';
|
||||
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
|
||||
@@ -24,6 +23,7 @@ import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
import imageSize from 'image-size';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -97,8 +97,8 @@ export class Dashboard {
|
||||
Dashboard.isDisposed = false;
|
||||
|
||||
Dashboard.webview.iconPath = {
|
||||
dark: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter-dark.svg')),
|
||||
light: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter.svg'))
|
||||
dark: Uri.file(join(extensionUri.fsPath, 'assets/icons/frontmatter-short-dark.svg')),
|
||||
light: Uri.file(join(extensionUri.fsPath, 'assets/icons/frontmatter-short-light.svg'))
|
||||
};
|
||||
|
||||
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
|
||||
@@ -220,11 +220,28 @@ export class Dashboard {
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
|
||||
if (data?.position) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const editor = window.activeTextEditor;
|
||||
const line = data.position.line;
|
||||
const character = data.position.character;
|
||||
if (line) {
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
let imgPath = data.image;
|
||||
const filePath = data.file;
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
|
||||
|
||||
const imgDir = dirname(absImgPath);
|
||||
const fileDir = dirname(filePath);
|
||||
|
||||
if (imgDir === fileDir) {
|
||||
imgPath = join('/', basename(imgPath));
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
}
|
||||
}
|
||||
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
} else {
|
||||
@@ -272,16 +289,25 @@ export class Dashboard {
|
||||
/**
|
||||
* Retrieve all media files
|
||||
*/
|
||||
private static async getMedia(page: number = 0, selectedFolder: string = '') {
|
||||
private static async getMedia(page: number = 0, requestedFolder: string = '') {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
const contentFolders = Folders.get();
|
||||
const viewData = Dashboard.viewData;
|
||||
let selectedFolder = requestedFolder;
|
||||
|
||||
// If the static folder is not set, retreive the last opened location
|
||||
if (!selectedFolder) {
|
||||
const stateValue = await Extension.getInstance().getState<string | undefined>(ExtensionState.SelectedFolder);
|
||||
if (stateValue && existsSync(stateValue)) {
|
||||
selectedFolder = stateValue;
|
||||
|
||||
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
|
||||
// Support for page bundles
|
||||
if (viewData?.data?.filePath && viewData?.data?.filePath.endsWith('index.md')) {
|
||||
const folderPath = parse(viewData.data.filePath).dir;
|
||||
selectedFolder = folderPath;
|
||||
} else if (stateValue && existsSync(stateValue)) {
|
||||
selectedFolder = stateValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +405,7 @@ export class Dashboard {
|
||||
}
|
||||
|
||||
// Store the last opened folder
|
||||
await Extension.getInstance().setState(ExtensionState.SelectedFolder, selectedFolder);
|
||||
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder);
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.media,
|
||||
@@ -421,7 +447,7 @@ export class Dashboard {
|
||||
fmFilePath: file.filePath,
|
||||
fmFileName: file.fileName,
|
||||
fmDraft: article?.data.draft ? "Draft" : "Published",
|
||||
fmYear: article?.data[dateField] ? parseJSON(article?.data[dateField]).getFullYear() : null,
|
||||
fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null,
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
@@ -432,7 +458,7 @@ export class Dashboard {
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
|
||||
if (article?.data[previewField] && wsFolder) {
|
||||
let fieldValue = article?.data[previewField];
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
@@ -596,7 +622,9 @@ export class Dashboard {
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
|
||||
const version = Extension.getInstance().getVersion();
|
||||
const ext = Extension.getInstance();
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
@@ -608,7 +636,7 @@ export class Dashboard {
|
||||
<title>Front Matter Dashboard</title>
|
||||
</head>
|
||||
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden" class="bg-gray-100 text-vulcan-500 dark:bg-vulcan-500 dark:text-whisper-500">
|
||||
<div id="app" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></div>
|
||||
<div id="app" data-environment="${isBeta ? "BETA" : "main"}" data-version="${version.usedVersion}" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></div>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
|
||||
|
||||
@@ -274,7 +274,11 @@ export class Folders {
|
||||
*/
|
||||
private static async update(folders: ContentFolder[]) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folders.map(folder => ({ title: folder.title, path: Folders.relWsFolder(folder, wsFolder) })));
|
||||
let folderDetails = folders.map(folder => ({
|
||||
title: folder.title,
|
||||
path: Folders.relWsFolder(folder, wsFolder)
|
||||
}));
|
||||
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,14 +3,14 @@ import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
|
||||
import { format } from 'date-fns';
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { ArticleHelper, Settings } from '../helpers';
|
||||
import { Article } from '.';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { CONTEXT } from '../constants';
|
||||
import { Project } from './Project';
|
||||
import { Folders } from './Folders';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { ContentType as IContentType } from '../models';
|
||||
|
||||
export class Template {
|
||||
|
||||
@@ -95,7 +95,7 @@ export class Template {
|
||||
*/
|
||||
public static async create(folderPath: string) {
|
||||
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
|
||||
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentTypes = ContentType.getAll();
|
||||
|
||||
if (!folderPath) {
|
||||
Notifications.warning(`Incorrect project folder path retrieved.`);
|
||||
@@ -133,16 +133,14 @@ export class Template {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileExt = path.parse(selectedTemplate).ext;
|
||||
const sanitizedName = sanitize(titleValue.toLowerCase().replace(/ /g, "-"));
|
||||
let newFileName = `${sanitizedName}${fileExt}`;
|
||||
if (prefix && typeof prefix === "string") {
|
||||
newFileName = `${format(new Date(), prefix)}-${newFileName}`;
|
||||
const templateData = ArticleHelper.getFrontMatterByPath(template.fsPath);
|
||||
let contentType: IContentType | undefined;
|
||||
if (templateData && templateData.data && templateData.data.type) {
|
||||
contentType = contentTypes?.find(t => t.name === templateData.data.type);
|
||||
}
|
||||
|
||||
const newFilePath = path.join(folderPath, newFileName);
|
||||
if (fs.existsSync(newFilePath)) {
|
||||
Notifications.warning(`File already exists, please remove it before creating a new one with the same title.`);
|
||||
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue);
|
||||
if (!newFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,7 +160,7 @@ export class Template {
|
||||
fmData.title = titleValue;
|
||||
}
|
||||
if (typeof fmData.slug !== "undefined") {
|
||||
fmData.slug = sanitizedName;
|
||||
fmData.slug = ArticleHelper.sanitize(titleValue);
|
||||
}
|
||||
|
||||
frontMatter = Article.updateDate(frontMatter);
|
||||
|
||||
@@ -4,6 +4,7 @@ export const DEFAULT_CONTENT_TYPE_NAME = 'default';
|
||||
|
||||
export const DEFAULT_CONTENT_TYPE: ContentType = {
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { format, parseJSON } from 'date-fns';
|
||||
import { format } from 'date-fns';
|
||||
import * as React from 'react';
|
||||
import { DateHelper } from '../../helpers/DateHelper';
|
||||
|
||||
export interface IDateFieldProps {
|
||||
value: Date | string;
|
||||
@@ -10,9 +11,9 @@ export const DateField: React.FunctionComponent<IDateFieldProps> = ({value}: Rea
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
const parsedValue = typeof value === 'string' ? parseJSON(value) : value;
|
||||
const dateString = format(parsedValue, 'yyyy-MM-dd');
|
||||
setDateValue(dateString);
|
||||
const parsedValue = typeof value === 'string' ? DateHelper.tryParse(value) : value;
|
||||
const dateString = parsedValue ? format(parsedValue, 'yyyy-MM-dd') : parsedValue;
|
||||
setDateValue(dateString || "");
|
||||
} catch (e) {
|
||||
// Date is invalid
|
||||
}
|
||||
|
||||
@@ -7,10 +7,17 @@ import { Integrations } from "@sentry/tracing";
|
||||
import { SENTRY_LINK } from "../constants";
|
||||
import './styles.css';
|
||||
|
||||
const elm = document.querySelector("#app");
|
||||
const welcome = elm?.getAttribute("data-showWelcome");
|
||||
const version = elm?.getAttribute("data-version");
|
||||
const environment = elm?.getAttribute("data-environment");
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_LINK,
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
tracesSampleRate: 0, // No performance tracing required
|
||||
release: version || "",
|
||||
environment: environment || ""
|
||||
});
|
||||
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
@@ -18,7 +25,4 @@ declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
setState: (data: T) => void;
|
||||
postMessage: (msg: unknown) => void;
|
||||
};
|
||||
|
||||
const elm = document.querySelector("#app");
|
||||
const welcome = elm?.getAttribute("data-showWelcome");
|
||||
render(<RecoilRoot><Dashboard showWelcome={!!welcome} /></RecoilRoot>, elm);
|
||||
@@ -6,7 +6,7 @@ export interface Page {
|
||||
fmFileName: string;
|
||||
fmModified: number;
|
||||
fmDraft: "Draft" | "Published",
|
||||
fmYear: number | null;
|
||||
fmYear: number | null | undefined;
|
||||
|
||||
title: string;
|
||||
slug: string;
|
||||
|
||||
@@ -633,7 +633,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
|
||||
const version = Extension.getInstance().getVersion();
|
||||
const ext = Extension.getInstance();
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
@@ -648,7 +650,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
<title>Front Matter</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="app" data-environment="${isBeta ? "BETA" : "main"}" data-version="${version.usedVersion}" ></div>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`panel-${version.installedVersion}`}" alt="Daily usage" />
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType';
|
||||
import { ContentType } from './../models/PanelSettings';
|
||||
import * as vscode from 'vscode';
|
||||
import * as matter from "gray-matter";
|
||||
import * as fs from "fs";
|
||||
import { DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES } from '../constants';
|
||||
import { DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
|
||||
import { DumpOptions } from 'js-yaml';
|
||||
import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine';
|
||||
import { Settings } from '.';
|
||||
import { parse } from 'date-fns';
|
||||
import { format, parse } from 'date-fns';
|
||||
import { Notifications } from './Notifications';
|
||||
import { Article } from '../commands';
|
||||
import { basename } from 'path';
|
||||
import { basename, join } from 'path';
|
||||
import { EditorHelper } from '@estruyf/vscode';
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { ContentType } from '../models';
|
||||
|
||||
export class ArticleHelper {
|
||||
|
||||
@@ -164,6 +166,57 @@ export class ArticleHelper {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the value
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
public static sanitize(value: string): string {
|
||||
return sanitize(value.toLowerCase().replace(/ /g, "-"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the file or folder for the new content
|
||||
* @param contentType
|
||||
* @param folderPath
|
||||
* @param titleValue
|
||||
* @returns The new file path
|
||||
*/
|
||||
public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string): string | undefined {
|
||||
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
|
||||
// Name of the file or folder to create
|
||||
const sanitizedName = ArticleHelper.sanitize(titleValue);
|
||||
let newFilePath: string | undefined;
|
||||
|
||||
// Create a folder with the `index.md` file
|
||||
if (contentType?.pageBundle) {
|
||||
const newFolder = join(folderPath, sanitizedName);
|
||||
if (existsSync(newFolder)) {
|
||||
Notifications.error(`A page bundle with the name ${sanitizedName} already exists in ${folderPath}`);
|
||||
return;
|
||||
} else {
|
||||
mkdirSync(newFolder);
|
||||
newFilePath = join(newFolder, `index.md`);
|
||||
}
|
||||
} else {
|
||||
let newFileName = `${sanitizedName}.md`;
|
||||
|
||||
if (prefix && typeof prefix === "string") {
|
||||
newFileName = `${format(new Date(), prefix)}-${newFileName}`;
|
||||
}
|
||||
|
||||
newFilePath = join(folderPath, newFileName);
|
||||
|
||||
if (existsSync(newFilePath)) {
|
||||
Notifications.warning(`Content with the title already exists. Please specify a new title.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return newFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a markdown file and its front matter
|
||||
* @param fileContents
|
||||
|
||||
@@ -4,10 +4,9 @@ import { ContentType as IContentType } from '../models';
|
||||
import { Uri, workspace, window } from 'vscode';
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Questions } from "./Questions";
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { format } from "date-fns";
|
||||
import { join } from "path";
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType";
|
||||
|
||||
@@ -51,27 +50,18 @@ export class ContentType {
|
||||
}
|
||||
|
||||
private static async create(contentType: IContentType, folderPath: string) {
|
||||
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
|
||||
const titleValue = await Questions.ContentTitle();
|
||||
if (!titleValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedName = sanitize(titleValue.toLowerCase().replace(/ /g, "-"));
|
||||
let newFileName = `${sanitizedName}.md`;
|
||||
|
||||
if (prefix && typeof prefix === "string") {
|
||||
newFileName = `${format(new Date(), prefix)}-${newFileName}`;
|
||||
}
|
||||
|
||||
const newFilePath = join(folderPath, newFileName);
|
||||
if (existsSync(newFilePath)) {
|
||||
Notifications.warning(`Content with the title already exists. Please specify a new title.`);
|
||||
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue);
|
||||
if (!newFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data: any = {};
|
||||
let data: any = {};
|
||||
|
||||
for (const field of contentType.fields) {
|
||||
if (field.name === "title") {
|
||||
@@ -81,6 +71,8 @@ export class ContentType {
|
||||
}
|
||||
}
|
||||
|
||||
data = ArticleHelper.updateDates(Object.assign({}, data));
|
||||
|
||||
if (contentType.name !== DEFAULT_CONTENT_TYPE_NAME) {
|
||||
data['type'] = contentType.name;
|
||||
}
|
||||
|
||||
64
src/helpers/DateHelper.ts
Normal file
64
src/helpers/DateHelper.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { parse, parseISO, parseJSON } from "date-fns";
|
||||
|
||||
|
||||
export class DateHelper {
|
||||
|
||||
public static tryParse(date: any, format?: string): Date | null {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (date instanceof Date) {
|
||||
return date;
|
||||
}
|
||||
|
||||
if (typeof date === 'string') {
|
||||
const jsonParsed = DateHelper.tryParseJson(date);
|
||||
if (DateHelper.isValid(jsonParsed)) {
|
||||
return jsonParsed;
|
||||
}
|
||||
|
||||
const isoParsed = DateHelper.tryParseIso(date);
|
||||
if (DateHelper.isValid(isoParsed)) {
|
||||
return isoParsed;
|
||||
}
|
||||
|
||||
if (format) {
|
||||
const formatParsed = DateHelper.tryFormatParse(date, format);
|
||||
if (DateHelper.isValid(formatParsed)) {
|
||||
return formatParsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static isValid(date: any): boolean {
|
||||
return !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
public static tryFormatParse(date: string, format: string): Date | null {
|
||||
try {
|
||||
return parse(date, format, new Date());
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static tryParseJson(date: string): Date | null {
|
||||
try {
|
||||
return parseJSON(date);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static tryParseIso(date: string): Date | null {
|
||||
try {
|
||||
return parseISO(date);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,10 @@ export class Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!versionInfo.usedVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split semantic version
|
||||
const version = versionInfo.usedVersion.split('.');
|
||||
const major = parseInt(version[0]);
|
||||
|
||||
@@ -14,11 +14,15 @@ interface MediaRecord {
|
||||
}
|
||||
|
||||
export class MediaLibrary {
|
||||
private db: JsonDB;
|
||||
private db: JsonDB | undefined;
|
||||
private static instance: MediaLibrary;
|
||||
|
||||
private constructor() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (!wsFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.db = new JsonDB(join(parseWinPath(wsFolder?.fsPath || ""), LocalStore.rootFolder, LocalStore.contentFolder, LocalStore.mediaDatabaseFile), true, false, '/');
|
||||
|
||||
workspace.onDidRenameFiles(e => {
|
||||
@@ -46,7 +50,7 @@ export class MediaLibrary {
|
||||
public get(id: string): MediaRecord | undefined {
|
||||
try {
|
||||
const fileId = this.parsePath(id);
|
||||
if (this.db.exists(fileId)) {
|
||||
if (this.db?.exists(fileId)) {
|
||||
return this.db.getData(fileId);
|
||||
}
|
||||
return undefined;
|
||||
@@ -57,16 +61,16 @@ export class MediaLibrary {
|
||||
|
||||
public set(id: string, metadata: any): void {
|
||||
const fileId = this.parsePath(id);
|
||||
this.db.push(fileId, metadata, true);
|
||||
this.db?.push(fileId, metadata, true);
|
||||
}
|
||||
|
||||
public rename(oldId: string, newId: string): void {
|
||||
const fileId = this.parsePath(oldId);
|
||||
const newFileId = this.parsePath(newId);
|
||||
const data = this.db.getData(fileId);
|
||||
const data = this.db?.getData(fileId);
|
||||
if (data) {
|
||||
this.db.delete(fileId);
|
||||
this.db.push(newFileId, data, true);
|
||||
this.db?.delete(fileId);
|
||||
this.db?.push(newFileId, data, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,16 @@ import { Notifications } from './Notifications';
|
||||
|
||||
export class Questions {
|
||||
|
||||
/**
|
||||
* Yes/No question
|
||||
* @param placeholder
|
||||
* @returns
|
||||
*/
|
||||
public static async yesOrNo(placeholder: string) {
|
||||
const answer = await window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: placeholder, ignoreFocusOut: false });
|
||||
return answer === "yes";
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the content to create
|
||||
* @param showWarning
|
||||
|
||||
@@ -136,7 +136,7 @@ export class Settings {
|
||||
Settings.globalConfig = JSON.parse(localConfig);
|
||||
Settings.globalConfig[`${CONFIG_KEY}.${name}`] = value;
|
||||
writeFileSync(fmConfig, JSON.stringify(Settings.globalConfig, null, 2), 'utf8');
|
||||
|
||||
|
||||
const workspaceSettingValue = Settings.hasWorkspaceSettings<ContentType[]>(name);
|
||||
if (workspaceSettingValue) {
|
||||
await Settings.update(name, undefined);
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface PanelSettings {
|
||||
export interface ContentType {
|
||||
name: string;
|
||||
fields: Field[];
|
||||
|
||||
pageBundle?: boolean;
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
|
||||
49
src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx
Normal file
49
src/panelWebView/components/ErrorBoundary/FieldBoundary.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IFieldBoundaryProps {
|
||||
fieldName: string;
|
||||
}
|
||||
|
||||
export interface IFieldBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
export default class FieldBoundary extends React.Component<IFieldBoundaryProps, IFieldBoundaryState> {
|
||||
|
||||
constructor(props: IFieldBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
public static getDerivedStateFromError(error: any) {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: any, errorInfo: any) {
|
||||
Sentry.captureMessage(`Field boundary: ${error?.message || error}`);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IFieldBoundaryProps> {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<span style={{ lineHeight: "16px"}}>{this.props.fieldName}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
<div className={`metadata_field__error`}>
|
||||
<span>Error loading field</span>
|
||||
|
||||
<button onClick={() => this.setState({ hasError: false })}>Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children as any;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { VsLabel } from '../VscodeComponents';
|
||||
import { ClockIcon } from '@heroicons/react/outline';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import { forwardRef } from 'react';
|
||||
import { DateHelper } from '../../../helpers/DateHelper';
|
||||
|
||||
export interface IDateTimeFieldProps {
|
||||
label: string;
|
||||
@@ -22,7 +23,7 @@ const CustomInput = forwardRef<HTMLInputElement, InputProps>(({ value, onClick }
|
||||
});
|
||||
|
||||
export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({label, date, format, onChange}: React.PropsWithChildren<IDateTimeFieldProps>) => {
|
||||
const [ dateValue, setDateValue ] = React.useState<Date | null>(date);
|
||||
const [ dateValue, setDateValue ] = React.useState<Date | null>(null);
|
||||
|
||||
const onDateChange = (date: Date) => {
|
||||
setDateValue(date);
|
||||
@@ -30,7 +31,10 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({lab
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (dateValue?.toISOString() !== date?.toISOString()) {
|
||||
const crntValue = DateHelper.tryParse(date, format);
|
||||
const stateValue = DateHelper.tryParse(dateValue, format);
|
||||
|
||||
if (crntValue?.toISOString() !== stateValue?.toISOString()) {
|
||||
setDateValue(date);
|
||||
}
|
||||
}, [ date ]);
|
||||
@@ -45,7 +49,7 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({lab
|
||||
|
||||
<div className={`metadata_field__datetime`}>
|
||||
<DatePicker
|
||||
selected={dateValue as Date}
|
||||
selected={dateValue as Date || new Date()}
|
||||
onChange={onDateChange}
|
||||
timeInputLabel="Time:"
|
||||
dateFormat={format || "MM/dd/yyyy HH:mm"}
|
||||
|
||||
32
src/panelWebView/components/FileItem.tsx
Normal file
32
src/panelWebView/components/FileItem.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { FileIcon } from './Icons/FileIcon';
|
||||
import { MarkdownIcon } from './Icons/MarkdownIcon';
|
||||
|
||||
export interface IFileItemProps {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export const FileItem: React.FunctionComponent<IFileItemProps> = ({ name, path }: React.PropsWithChildren<IFileItemProps>) => {
|
||||
|
||||
const openFile = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openInEditor, path);
|
||||
};
|
||||
|
||||
return (
|
||||
<li className={`file_list__items__item`}
|
||||
onClick={openFile}>
|
||||
{
|
||||
(name.endsWith('.md') || name.endsWith('.mdx')) ? (
|
||||
<MarkdownIcon />
|
||||
) : (
|
||||
<FileIcon />
|
||||
)
|
||||
}
|
||||
|
||||
<span>{name}</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { FileInfo } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { FileIcon } from './Icons/FileIcon';
|
||||
import { MarkdownIcon } from './Icons/MarkdownIcon';
|
||||
import { FileItem } from './FileItem';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
|
||||
export interface IFileListProps {
|
||||
@@ -13,10 +10,6 @@ export interface IFileListProps {
|
||||
}
|
||||
|
||||
export const FileList: React.FunctionComponent<IFileListProps> = ({files, folderName, totalFiles}: React.PropsWithChildren<IFileListProps>) => {
|
||||
|
||||
const openFile = (filePath: string) => {
|
||||
MessageHelper.sendMessage(CommandToCode.openInEditor, filePath);
|
||||
};
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
return null;
|
||||
@@ -28,20 +21,8 @@ export const FileList: React.FunctionComponent<IFileListProps> = ({files, folder
|
||||
|
||||
<ul className="file_list__items">
|
||||
{
|
||||
files.map(file => (
|
||||
<li key={file.fileName}
|
||||
className={`file_list__items__item`}
|
||||
onClick={() => openFile(file.filePath)}>
|
||||
{
|
||||
(file.fileName.endsWith('.md') || file.fileName.endsWith('.mdx')) ? (
|
||||
<MarkdownIcon />
|
||||
) : (
|
||||
<FileIcon />
|
||||
)
|
||||
}
|
||||
|
||||
<span>{file.fileName}</span>
|
||||
</li>
|
||||
(files && files.length > 0) && files.map(file => (
|
||||
<FileItem key={file.filePath} name={file.fileName} path={file.filePath} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
@@ -48,11 +48,11 @@ export const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({s
|
||||
<Collapsible id={`${isBase ? "base_" : ""}settings`} className={`base__actions`} title="Global settings">
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>Modified date</VsLabel>
|
||||
<VsCheckbox label="Auto-update modified date" checked={modifiedDateUpdate} onClick={onDateCheck} />
|
||||
<VsCheckbox checked={modifiedDateUpdate} onClick={onDateCheck}>Auto-update modified date</VsCheckbox>
|
||||
</div>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>Front Matter highlight</VsLabel>
|
||||
<VsCheckbox label="Highlight Front Matter" checked={fmHighlighting} onClick={onHighlightCheck} />
|
||||
<VsCheckbox checked={fmHighlighting} onClick={onHighlightCheck}>Highlight Front Matter</VsCheckbox>
|
||||
</div>
|
||||
<div className={`base__action`}>
|
||||
<VsLabel>Local preview</VsLabel>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Toggle } from './Fields/Toggle';
|
||||
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { TagIcon } from './Icons/TagIcon';
|
||||
import { TagPicker } from './TagPicker';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { DateTimeField } from './Fields/DateTimeField';
|
||||
import { TextField } from './Fields/TextField';
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
@@ -17,6 +16,8 @@ import { ListUnorderedIcon } from './Icons/ListUnorderedIcon';
|
||||
import { NumberField } from './Fields/NumberField';
|
||||
import { ChoiceField } from './Fields/ChoiceField';
|
||||
import useContentType from '../../hooks/useContentType';
|
||||
import { DateHelper } from '../../helpers/DateHelper';
|
||||
import FieldBoundary from './ErrorBoundary/FieldBoundary';
|
||||
|
||||
export interface IMetadataProps {
|
||||
settings: PanelSettings | undefined;
|
||||
@@ -39,11 +40,9 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
});
|
||||
};
|
||||
|
||||
const getDate = (date: string | Date) => {
|
||||
if (typeof date === 'string') {
|
||||
return parseJSON(date);
|
||||
}
|
||||
return date;
|
||||
const getDate = (date: string | Date): Date | null => {
|
||||
const parsedDate = DateHelper.tryParse(date, settings?.date?.format);
|
||||
return parsedDate || date as Date | null;
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
@@ -64,20 +63,23 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
const dateValue = metadata[field.name] ? getDate(metadata[field.name] as string) : null;
|
||||
|
||||
return (
|
||||
<DateTimeField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
date={dateValue}
|
||||
format={settings?.date?.format}
|
||||
onChange={(date => sendUpdate(field.name, date))} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<DateTimeField
|
||||
label={field.title || field.name}
|
||||
date={dateValue}
|
||||
format={settings?.date?.format}
|
||||
onChange={(date => sendUpdate(field.name, date))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'boolean') {
|
||||
return (
|
||||
<Toggle
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
checked={!!metadata[field.name] as any}
|
||||
onChanged={(checked) => sendUpdate(field.name, checked)} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<Toggle
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
checked={!!metadata[field.name] as any}
|
||||
onChanged={(checked) => sendUpdate(field.name, checked)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'string') {
|
||||
const textValue = metadata[field.name];
|
||||
@@ -90,14 +92,15 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
singleLine={field.single}
|
||||
limit={limit}
|
||||
rows={3}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
value={textValue as string || null} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<TextField
|
||||
label={field.title || field.name}
|
||||
singleLine={field.single}
|
||||
limit={limit}
|
||||
rows={3}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
value={textValue as string || null} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'number') {
|
||||
const fieldValue = metadata[field.name];
|
||||
@@ -107,61 +110,67 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
}
|
||||
|
||||
return (
|
||||
<NumberField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
value={nrValue} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<NumberField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
value={nrValue} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'image') {
|
||||
return (
|
||||
<PreviewImageField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
fieldName={field.name}
|
||||
filePath={metadata.filePath as string}
|
||||
value={metadata[field.name] as PreviewImageValue | PreviewImageValue[] | null}
|
||||
multiple={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<PreviewImageField
|
||||
label={field.title || field.name}
|
||||
fieldName={field.name}
|
||||
filePath={metadata.filePath as string}
|
||||
value={metadata[field.name] as PreviewImageValue | PreviewImageValue[] | null}
|
||||
multiple={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'choice') {
|
||||
const choices = field.choices || [];
|
||||
const choiceValue = metadata[field.name];
|
||||
|
||||
return (
|
||||
<ChoiceField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
selected={choiceValue as string}
|
||||
choices={choices}
|
||||
multiSelect={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<ChoiceField
|
||||
label={field.title || field.name}
|
||||
selected={choiceValue as string}
|
||||
choices={choices}
|
||||
multiSelect={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'tags') {
|
||||
return (
|
||||
<TagPicker
|
||||
key={field.name}
|
||||
type={TagType.tags}
|
||||
label={field.title || field.name}
|
||||
icon={<TagIcon />}
|
||||
crntSelected={metadata[field.name] as string[] || []}
|
||||
options={settings?.tags || []}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.tags}
|
||||
unsetFocus={unsetFocus} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<TagPicker
|
||||
type={TagType.tags}
|
||||
label={field.title || field.name}
|
||||
icon={<TagIcon />}
|
||||
crntSelected={metadata[field.name] as string[] || []}
|
||||
options={settings?.tags || []}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.tags}
|
||||
unsetFocus={unsetFocus} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'categories') {
|
||||
return (
|
||||
<TagPicker
|
||||
key={field.name}
|
||||
type={TagType.categories}
|
||||
label={field.title || field.name}
|
||||
icon={<ListUnorderedIcon />}
|
||||
crntSelected={metadata.categories as string[] || []}
|
||||
options={settings.categories}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.categories}
|
||||
unsetFocus={unsetFocus} />
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<TagPicker
|
||||
type={TagType.categories}
|
||||
label={field.title || field.name}
|
||||
icon={<ListUnorderedIcon />}
|
||||
crntSelected={metadata.categories as string[] || []}
|
||||
options={settings.categories}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.categories}
|
||||
unsetFocus={unsetFocus} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
@@ -177,14 +186,17 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
}
|
||||
|
||||
{
|
||||
<TagPicker type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={metadata.keywords as string[] || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable />
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={metadata.keywords as string[] || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable />
|
||||
</FieldBoundary>
|
||||
}
|
||||
</Collapsible>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,22 @@ export interface ISeoKeywordsProps {
|
||||
|
||||
export const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...data}: React.PropsWithChildren<ISeoKeywordsProps>) => {
|
||||
|
||||
const validateKeywords = () => {
|
||||
if (!keywords) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof keywords === 'string') {
|
||||
return [keywords];
|
||||
}
|
||||
|
||||
if (Array.isArray(keywords)) {
|
||||
return keywords;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!keywords || keywords.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -31,7 +47,7 @@ export const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keyword
|
||||
</VsTableHeader>
|
||||
<VsTableBody slot="body">
|
||||
{
|
||||
keywords.map((keyword, index) => {
|
||||
validateKeywords().map((keyword, index) => {
|
||||
return (
|
||||
<SeoKeywordInfo key={index} keyword={keyword} {...data} />
|
||||
);
|
||||
|
||||
@@ -83,7 +83,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
|
||||
if (selectedItem) {
|
||||
let value = selectedItem || "";
|
||||
|
||||
const item = options.find(o => o.toLowerCase() === selectedItem.toLowerCase());
|
||||
const item = options.find(o => o?.toLowerCase() === selectedItem?.toLowerCase());
|
||||
if (item) {
|
||||
value = item;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
|
||||
</Downshift>
|
||||
|
||||
<Tags
|
||||
values={(selected || []).sort((a: string, b: string) => a.toLowerCase() < b.toLowerCase() ? -1 : 1 )}
|
||||
values={(selected || []).sort((a: string, b: string) => a?.toLowerCase() < b?.toLowerCase() ? -1 : 1 )}
|
||||
onRemove={onRemove}
|
||||
onCreate={onCreate}
|
||||
options={options}
|
||||
|
||||
@@ -16,17 +16,24 @@ export const Tags: React.FunctionComponent<ITagsProps> = (props: React.PropsWith
|
||||
|
||||
const knownTags = values.filter(v => options.includes(v));
|
||||
const unknownTags = values.filter(v => !options.includes(v));
|
||||
|
||||
const generateKey = (tag: string, idx: number) => {
|
||||
if (tag) {
|
||||
return `${tag.replace(/ /g, "_")}-${idx}`;
|
||||
}
|
||||
return `tag-${idx}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`article__tags__items`}>
|
||||
{
|
||||
knownTags.map(t => (
|
||||
<Tag key={t.replace(/ /g, "_")} value={t} className={`article__tags__items__pill_exists`} onRemove={onRemove} title={`Remove ${t}`} />
|
||||
knownTags.map((t, idx) => (
|
||||
<Tag key={generateKey(t, idx)} value={t} className={`article__tags__items__pill_exists`} onRemove={onRemove} title={`Remove ${t}`} />
|
||||
))
|
||||
}
|
||||
{
|
||||
unknownTags.map(t => (
|
||||
<Tag key={t.replace(/ /g, "_")} value={t} className={`article__tags__items__pill_notexists`} onRemove={onRemove} onCreate={onCreate} title={`Be aware, this tag "${t}" is not saved in your settings. Once removed, it will be gone forever.`} disableConfigurable={disableConfigurable} />
|
||||
unknownTags.map((t, idx) => (
|
||||
<Tag key={generateKey(t, idx)} value={t} className={`article__tags__items__pill_notexists`} onRemove={onRemove} onCreate={onCreate} title={`Be aware, this tag "${t}" is not saved in your settings. Once removed, it will be gone forever.`} disableConfigurable={disableConfigurable} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {wrapWc} from 'wc-react';
|
||||
|
||||
// @bendera/vscode-webview-elements
|
||||
export const VsTable = wrapWc(`vscode-table`);
|
||||
export const VsTableHeader = wrapWc(`vscode-table-header`);
|
||||
export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`);
|
||||
@@ -7,5 +8,7 @@ export const VsTableBody = wrapWc(`vscode-table-body`);
|
||||
export const VsTableRow = wrapWc(`vscode-table-row`);
|
||||
export const VsTableCell = wrapWc(`vscode-table-cell`);
|
||||
export const VsCollapsible = wrapWc(`vscode-collapsible`);
|
||||
export const VsCheckbox = wrapWc(`vscode-checkbox`);
|
||||
export const VsLabel = wrapWc(`vscode-label`);
|
||||
export const VsLabel = wrapWc(`vscode-label`);
|
||||
|
||||
// @vscode/webview-ui-toolkit
|
||||
export const VsCheckbox = wrapWc(`vscode-checkbox`);
|
||||
@@ -13,13 +13,20 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-body';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-row';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-table-cell';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-collapsible';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-checkbox';
|
||||
import '@bendera/vscode-webview-elements/dist/vscode-label';
|
||||
|
||||
import '@vscode/webview-ui-toolkit/dist/esm/checkbox';
|
||||
|
||||
const elm = document.querySelector("#app");
|
||||
const version = elm?.getAttribute("data-version");
|
||||
const environment = elm?.getAttribute("data-environment");
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_LINK,
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
tracesSampleRate: 0, // No performance tracing required
|
||||
release: version || "",
|
||||
environment: environment || ""
|
||||
});
|
||||
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
@@ -28,5 +35,4 @@ declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
postMessage: (msg: unknown) => void;
|
||||
};
|
||||
|
||||
const elm = document.querySelector("#app");
|
||||
render(<ViewPanel />, elm);
|
||||
|
||||
Reference in New Issue
Block a user