Compare commits

...

30 Commits

Author SHA1 Message Date
Elio Struyf
7a2a0934c2 Merge pull request #150 from estruyf/dev
Merge for 5.1.1
2021-10-14 16:24:29 +02:00
Elio Struyf
d3eb7b223c 5.1.1 2021-10-14 16:23:51 +02:00
Elio Struyf
f74eec954f #149 - Fix keywords 2021-10-14 16:23:28 +02:00
Elio Struyf
864c4e7aa6 Merge pull request #148 from estruyf/dev
Merge for v5.1.0
2021-10-13 11:34:29 +02:00
Elio Struyf
5667906caf Update changelog for release 2021-10-13 10:46:06 +02:00
Elio Struyf
2fe7c524e7 #147 - Error boundary added for metadata fields 2021-10-13 09:05:32 +02:00
Elio Struyf
5cc83526ad Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2021-10-13 08:27:48 +02:00
Elio Struyf
76b5e99a08 File check 2021-10-13 08:27:18 +02:00
Elio Struyf
7d5505d421 Updated changelog 2021-10-12 20:34:15 +02:00
Elio Struyf
d97a11f8b5 #146 - Date parsing logic added with fallbacks 2021-10-12 20:34:06 +02:00
Elio Struyf
0590cec684 Add version and environment details 2021-10-12 19:49:34 +02:00
Elio Struyf
b4cdc4feb9 Version fixes 2021-10-12 15:52:54 +02:00
Elio Struyf
986fd95524 #145 - Moved folder registration settings 2021-10-12 15:22:43 +02:00
Elio Struyf
f51fec5fb9 Fix issue in sorting of taxonomy picker 2021-10-12 10:50:42 +02:00
Elio Struyf
8198ce2af3 Added VSCode webcomponents 2021-10-12 10:23:55 +02:00
Elio Struyf
defffc4c8e Remove unused settings 2021-10-12 09:12:18 +02:00
Elio Struyf
8f47cbfb0b #141 - Merge content creation logic to ArticleHelper 2021-10-12 09:11:47 +02:00
Elio Struyf
0be91c17d0 #141 - Added support for page bundles with templates 2021-10-11 21:40:05 +02:00
Elio Struyf
fae7ab8417 Date to string fix 2021-10-11 17:49:45 +02:00
Elio Struyf
df239f2cc0 #144 - Pass a default value when null 2021-10-11 11:10:47 +02:00
Elio Struyf
70099dc97f #144 - Fix date value issue 2021-10-11 11:08:45 +02:00
Elio Struyf
c11be0e3ec #143 - Fix for recent files unique values 2021-10-11 10:55:58 +02:00
Elio Struyf
f8b7870180 Updated changlog 2021-10-11 10:50:28 +02:00
Elio Struyf
c58a5c62d9 #142 - Fix for unknown tags 2021-10-11 10:49:46 +02:00
Elio Struyf
ce92444bf2 Updated dashboard icon 2021-10-11 10:39:50 +02:00
Elio Struyf
b2709ebffd #141 - Support opening of the page bundle folder 2021-10-11 10:21:40 +02:00
Elio Struyf
2b20cf9d24 Updated changelog 2021-10-11 09:21:56 +02:00
Elio Struyf
f4a499ad0f 5.1.0 2021-10-11 09:20:48 +02:00
Elio Struyf
4494b158c0 #141 - Page bundle functionality 2021-10-11 09:20:42 +02:00
Elio Struyf
3416e55264 Fix rending more hooks 2021-10-08 15:30:56 +02:00
34 changed files with 658 additions and 222 deletions

View File

@@ -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

View 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

View 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

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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();
}
}

View File

@@ -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 || `![${data.alt || data.caption || ""}](${data.image})`));
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 || `![${data.alt || data.caption || ""}](${imgPath})`));
}
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" />

View File

@@ -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);
}
/**

View File

@@ -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);

View File

@@ -4,6 +4,7 @@ export const DEFAULT_CONTENT_TYPE_NAME = 'default';
export const DEFAULT_CONTENT_TYPE: ContentType = {
"name": "default",
"pageBundle": false,
"fields": [
{
"title": "Title",

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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" />

View File

@@ -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

View File

@@ -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
View 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;
}
}
}

View File

@@ -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]);

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -22,6 +22,8 @@ export interface PanelSettings {
export interface ContentType {
name: string;
fields: Field[];
pageBundle?: boolean;
}
export interface Field {

View 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;
}
}

View File

@@ -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"}

View 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>
);
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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} />
);

View File

@@ -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}

View File

@@ -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>

View File

@@ -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`);

View File

@@ -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);