#193 - Data file dashboard added

This commit is contained in:
Elio Struyf
2022-01-13 20:18:32 +01:00
parent 9f7f803e25
commit b1674b4b84
32 changed files with 1076 additions and 165 deletions

View File

@@ -2,11 +2,15 @@
## [6.0.0] - 2022-01-xx
### ✨ New features
- [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files.
- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing.
### 🎨 Enhancements
- Added default field value for content type fields
- HMR support for panel webview development
- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing.
- [#198](https://github.com/estruyf/vscode-front-matter/issues/198): Additional media sort options (alt, caption, and size).
## [5.10.0] - 2022-01-10

489
package-lock.json generated
View File

@@ -29,6 +29,8 @@
"@vscode/codicons": "0.0.20",
"@vscode/webview-ui-toolkit": "^0.8.1",
"@webpack-cli/serve": "^1.6.0",
"ajv": "^8.8.2",
"array-move": "^4.0.0",
"autoprefixer": "^10.3.2",
"css-loader": "5.2.7",
"date-fns": "2.23.0",
@@ -47,17 +49,22 @@
"path-browserify": "^1.0.1",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"postcss-nested": "^5.0.6",
"react": "17.0.1",
"react-datepicker": "4.2.1",
"react-dom": "17.0.1",
"react-dropzone": "^11.3.4",
"react-sortable-hoc": "^2.0.0",
"recoil": "^0.4.1",
"rimraf": "^3.0.2",
"style-loader": "2.0.0",
"tailwindcss": "^2.2.7",
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"typescript": "^4.5.4",
"uniforms": "^3.7.0",
"uniforms-bridge-json-schema": "^3.7.0",
"uniforms-unstyled": "^3.7.0",
"url-join-ts": "^1.0.5",
"wc-react": "github:estruyf/wc-react",
"webpack": "^5.65.0",
@@ -218,6 +225,28 @@
"resolve": "~1.19.0"
}
},
"node_modules/@microsoft/tsdoc-config/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -905,14 +934,14 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
@@ -937,37 +966,6 @@
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
@@ -1038,6 +1036,18 @@
"integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
"dev": true
},
"node_modules/array-move": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
"integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -3470,6 +3480,15 @@
"node": ">= 0.10"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -3923,9 +3942,9 @@
"dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/json5": {
@@ -5554,22 +5573,22 @@
}
},
"node_modules/postcss-nested": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz",
"integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.4"
"postcss-selector-parser": "^6.0.6"
},
"engines": {
"node": ">=10.0"
"node": ">=12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.1.13"
"postcss": "^8.2.14"
}
},
"node_modules/postcss-selector-parser": {
@@ -5883,6 +5902,22 @@
"react": "^16.8.0 || ^17"
}
},
"node_modules/react-sortable-hoc": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz",
"integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.2.0",
"invariant": "^2.2.4",
"prop-types": "^15.5.7"
},
"peerDependencies": {
"prop-types": "^15.5.7",
"react": "^16.3.0 || ^17.0.0",
"react-dom": "^16.3.0 || ^17.0.0"
}
},
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -6204,6 +6239,37 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@@ -6878,6 +6944,25 @@
"node": ">=10.13.0"
}
},
"node_modules/tailwindcss/node_modules/postcss-nested": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz",
"integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.4"
},
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.1.13"
}
},
"node_modules/tailwindcss/node_modules/resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
@@ -7154,9 +7239,9 @@
}
},
"node_modules/typescript": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -7181,6 +7266,74 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/uniforms": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms/-/uniforms-3.7.0.tgz",
"integrity": "sha512-FnFXckM09QRWqpREJx9xRUeJJgKmToKW4emaKqwVq7/tiospEuEhSr9kE5uxgNwCfTPQBsX5Ymhzx/fMXEDYDQ==",
"dev": true,
"dependencies": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0"
},
"funding": {
"url": "https://github.com/vazco/uniforms?sponsor=1"
},
"peerDependencies": {
"react": "^17.0.0 || ^16.8.0"
}
},
"node_modules/uniforms-bridge-json-schema": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz",
"integrity": "sha512-VWM0tEwcfYXsXfMicxJXIOi0OQoQDcP+WqwDY/OueJpBy9Nw5nsupuS9uUg6ZUkG0m1aCc3znmsZFQXvAaFdTQ==",
"dev": true,
"dependencies": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0",
"uniforms": "^3.7.0"
},
"funding": {
"url": "https://github.com/vazco/uniforms?sponsor=1"
}
},
"node_modules/uniforms-bridge-json-schema/node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
},
"node_modules/uniforms-unstyled": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms-unstyled/-/uniforms-unstyled-3.7.0.tgz",
"integrity": "sha512-FIpYl9aPC39FTJIp9sZbUWu2C8cgvTX1wqRSGHvya2qgcjB0RIQ6/xM11DBD9I6V66fEGhE2sBcrNqL7geB0Zg==",
"dev": true,
"dependencies": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0",
"uniforms": "^3.7.0"
},
"funding": {
"url": "https://github.com/vazco/uniforms?sponsor=1"
},
"peerDependencies": {
"react": "^17.0.0 || ^16.8.0"
}
},
"node_modules/uniforms-unstyled/node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
},
"node_modules/uniforms/node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
},
"node_modules/unist-util-stringify-position": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz",
@@ -7604,22 +7757,6 @@
}
}
},
"node_modules/webpack-dev-server/node_modules/ajv": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack-dev-server/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
@@ -7638,12 +7775,6 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"node_modules/webpack-dev-server/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
@@ -7986,6 +8117,26 @@
"ajv": "~6.12.6",
"jju": "~1.4.0",
"resolve": "~1.19.0"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
}
},
"@nodelib/fs.scandir": {
@@ -8603,14 +8754,14 @@
}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
@@ -8621,35 +8772,8 @@
"dev": true,
"requires": {
"ajv": "^8.0.0"
},
"dependencies": {
"ajv": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
}
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
@@ -8702,6 +8826,12 @@
"integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
"dev": true
},
"array-move": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
"integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==",
"dev": true
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -10604,6 +10734,15 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -10914,9 +11053,9 @@
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"json5": {
@@ -12039,12 +12178,12 @@
}
},
"postcss-nested": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz",
"integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
"postcss-selector-parser": "^6.0.6"
}
},
"postcss-selector-parser": {
@@ -12281,6 +12420,17 @@
"warning": "^4.0.2"
}
},
"react-sortable-hoc": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz",
"integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.2.0",
"invariant": "^2.2.4",
"prop-types": "^15.5.7"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -12519,6 +12669,33 @@
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
}
},
"section-matter": {
@@ -13075,6 +13252,15 @@
"is-glob": "^4.0.1"
}
},
"postcss-nested": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz",
"integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
}
},
"resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
@@ -13267,9 +13453,9 @@
}
},
"typescript": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
"dev": true
},
"unbox-primitive": {
@@ -13284,6 +13470,65 @@
"which-boxed-primitive": "^1.0.2"
}
},
"uniforms": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms/-/uniforms-3.7.0.tgz",
"integrity": "sha512-FnFXckM09QRWqpREJx9xRUeJJgKmToKW4emaKqwVq7/tiospEuEhSr9kE5uxgNwCfTPQBsX5Ymhzx/fMXEDYDQ==",
"dev": true,
"requires": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"uniforms-bridge-json-schema": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz",
"integrity": "sha512-VWM0tEwcfYXsXfMicxJXIOi0OQoQDcP+WqwDY/OueJpBy9Nw5nsupuS9uUg6ZUkG0m1aCc3znmsZFQXvAaFdTQ==",
"dev": true,
"requires": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0",
"uniforms": "^3.7.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"uniforms-unstyled": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uniforms-unstyled/-/uniforms-unstyled-3.7.0.tgz",
"integrity": "sha512-FIpYl9aPC39FTJIp9sZbUWu2C8cgvTX1wqRSGHvya2qgcjB0RIQ6/xM11DBD9I6V66fEGhE2sBcrNqL7geB0Zg==",
"dev": true,
"requires": {
"invariant": "^2.0.0",
"lodash": "^4.0.0",
"tslib": "^2.2.0",
"uniforms": "^3.7.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"unist-util-stringify-position": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz",
@@ -13620,18 +13865,6 @@
"ws": "^8.1.0"
},
"dependencies": {
"ajv": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
"integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
@@ -13647,12 +13880,6 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",

View File

@@ -329,6 +329,48 @@
"markdownDescription": "Specify if you want to open the dashboard when you start VS Code. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.openonstart)",
"scope": "Dashboard"
},
"frontMatter.data.files": {
"type": "array",
"default": [],
"markdownDescription": "Specify the data files you want to use for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.data.files)",
"items": {
"type": "object",
"default": {},
"properties": {
"id": {
"type": "string",
"description": "Your unique ID you want to use for your data file."
},
"title": {
"type": "string",
"description": "Title you want to give to your data file."
},
"labelField": {
"type": "string",
"description": "The field you want to use as label for your data entries."
},
"file": {
"type": "string",
"description": "Path to the file to load. Only JSON files are supported."
},
"schema": {
"type": "object",
"default": {},
"description": "The JSON schema for your data which will be used to render the data form.",
"additionalProperties": true
}
},
"additionalProperties": false,
"required": [
"id",
"title",
"file",
"schema",
"labelField"
]
},
"scope": "Data"
},
"frontMatter.framework.id": {
"type": "string",
"default": "",
@@ -501,7 +543,9 @@
"default": "",
"description": "The ID of your taxonomy field"
},
"fields": { "$ref": "#contenttypefield" }
"fields": {
"$ref": "#contenttypefield"
}
},
"additionalProperties": false,
"required": [
@@ -864,6 +908,15 @@
"light": "/assets/icons/frontmatter-small-light.svg"
}
},
{
"command": "frontMatter.dashboard.data",
"title": "Open data dashboard",
"category": "Front matter",
"icon": {
"dark": "/assets/icons/frontmatter-small-dark.svg",
"light": "/assets/icons/frontmatter-small-light.svg"
}
},
{
"command": "frontMatter.dashboard.close",
"title": "Close dashboard",
@@ -1217,6 +1270,8 @@
"@vscode/codicons": "0.0.20",
"@vscode/webview-ui-toolkit": "^0.8.1",
"@webpack-cli/serve": "^1.6.0",
"ajv": "^8.8.2",
"array-move": "^4.0.0",
"autoprefixer": "^10.3.2",
"css-loader": "5.2.7",
"date-fns": "2.23.0",
@@ -1235,17 +1290,22 @@
"path-browserify": "^1.0.1",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"postcss-nested": "^5.0.6",
"react": "17.0.1",
"react-datepicker": "4.2.1",
"react-dom": "17.0.1",
"react-dropzone": "^11.3.4",
"react-sortable-hoc": "^2.0.0",
"recoil": "^0.4.1",
"rimraf": "^3.0.2",
"style-loader": "2.0.0",
"tailwindcss": "^2.2.7",
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"typescript": "^4.5.4",
"uniforms": "^3.7.0",
"uniforms-bridge-json-schema": "^3.7.0",
"uniforms-unstyled": "^3.7.0",
"url-join-ts": "^1.0.5",
"wc-react": "github:estruyf/wc-react",
"webpack": "^5.65.0",

View File

@@ -2,6 +2,7 @@ const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [
require('postcss-nested'),
tailwindcss('./tailwind.config.js'),
require('autoprefixer'),
],

View File

@@ -11,6 +11,7 @@ import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { MediaLibrary } from '../helpers/MediaLibrary';
import { DashboardListener, MediaListener, SettingsListener } from '../listeners';
import { DataListener } from '../listeners/DataListener';
export class Dashboard {
private static webview: WebviewPanel | null = null;
@@ -144,6 +145,7 @@ export class Dashboard {
MediaListener.process(msg);
PagesListener.process(msg);
SettingsListener.process(msg);
DataListener.process(msg);
});
}
@@ -195,7 +197,7 @@ export class Dashboard {
const csp = [
`default-src 'none';`,
`img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'`,
`script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`}`,
`script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`,
`style-src ${webView.cspSource} 'self' 'unsafe-inline'`,
`font-src ${webView.cspSource}`,
`connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`}`

View File

@@ -274,6 +274,19 @@ export class Folders {
path: Folders.absWsFolder(folder, wsFolder)
}));
}
/**
* Retrieve the absolute file path
* @param filePath
* @returns
*/
public static getAbsFilePath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
return absPath;
}
/**
* Update the folder settings

View File

@@ -29,6 +29,7 @@ export const COMMAND_NAME = {
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
dashboardMedia: getCommandName("dashboard.media"),
dashboardData: getCommandName("dashboard.data"),
dashboardClose: getCommandName("dashboard.close"),
promote: getCommandName("promoteSettings"),
insertImage: getCommandName("insertImage"),

View File

@@ -55,6 +55,8 @@ export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTINGS_DATA_FILES = "data.files";
export const SETTINGS_FRAMEWORK_ID = "framework.id";
export const SETTING_SITE_BASEURL = "site.baseURL";

View File

@@ -4,5 +4,6 @@ export enum DashboardCommand {
settings = "settings",
media = "media",
viewData = "viewData",
mediaUpdate = "mediaUpdate"
mediaUpdate = "mediaUpdate",
dataFileEntries = "dataFileEntries"
}

View File

@@ -21,4 +21,6 @@ export enum DashboardMessage {
setFramework = 'setFramework',
setState = 'setState',
runCustomScript = 'runCustomScript',
getDataEntries = 'getDataEntries',
putDataEntries = 'putDataEntries',
}

View File

@@ -1,15 +1,17 @@
import * as React from 'react';
export interface IButtonProps {
secondary?: boolean;
disabled?: boolean;
className?: string;
onClick: () => void;
}
export const Button: React.FunctionComponent<IButtonProps> = ({onClick, disabled, children}: React.PropsWithChildren<IButtonProps>) => {
export const Button: React.FunctionComponent<IButtonProps> = ({onClick, className, disabled, secondary, children}: React.PropsWithChildren<IButtonProps>) => {
return (
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500"
className={`${className || ""} inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 focus:outline-none disabled:bg-gray-500 ${secondary ? `bg-red-300 hover:bg-red-400` : `bg-teal-600 hover:bg-teal-700`}`}
onClick={onClick}
disabled={disabled}
>

View File

@@ -8,6 +8,7 @@ import { DashboardViewSelector } from '../state';
import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { NavigationType } from '../models';
import { DataView } from './DataView';
export interface IDashboardProps {
showWelcome: boolean;
@@ -30,15 +31,25 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
return <WelcomeScreen settings={settings} />;
}
if (view === NavigationType.Media) {
return (
<main className={`h-full w-full`}>
<Media />
</main>
);
}
if (view === NavigationType.Data) {
return (
<main className={`h-full w-full`}>
<DataView />
</main>
);
}
return (
<main className={`h-full w-full`}>
{
view === NavigationType.Media ? (
<Media />
) : (
<Contents pages={pages} loading={loading} />
)
}
<Contents pages={pages} loading={loading} />
</main>
);
};

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import Ajv from 'ajv';
import { useEffect, useState } from 'react';
import { JSONSchemaBridge } from 'uniforms-bridge-json-schema';
import { AutoFields, AutoForm, ErrorsField, SubmitField } from 'uniforms-unstyled';
import { ErrorBoundary } from '@sentry/react';
import { DataFormControls } from './DataFormControls';
export interface IDataFormProps {
schema: any;
model: any;
onSubmit: (model: any) => void;
onClear: () => void;
}
export const DataForm: React.FunctionComponent<IDataFormProps> = ({ schema, model, onSubmit, onClear }: React.PropsWithChildren<IDataFormProps>) => {
const [ bridge, setBridge ] = useState<JSONSchemaBridge | null>(null);
const ajv = new Ajv({ allErrors: true, useDefaults: true });
const jsonValidator = (schema: object) => {
const validator = ajv.compile(schema);
return (crntModel: object) => {
validator(crntModel);
return validator.errors?.length ? { details: validator.errors } : null;
};
}
useEffect(() => {
const schemaValidator = jsonValidator(schema);
const bridge = new JSONSchemaBridge(schema, schemaValidator);
setBridge(bridge);
}, [schema]);
if (!bridge) {
return null;
}
return (
<ErrorBoundary>
<div className='autoform'>
{
model ? (
<h2 className='text-gray-500 dark:text-whisper-900'>Modify the data</h2>
) : (
<h2 className='text-gray-500 dark:text-whisper-900'>Add new data</h2>
)
}
<AutoForm
schema={bridge}
model={model || {}}
onSubmit={onSubmit}
ref={form => form?.reset()}>
<AutoFields />
<div className={`errors`}>
<ErrorsField />
</div>
<DataFormControls model={model} onClear={onClear} />
</AutoForm>
</div>
</ErrorBoundary>
);
};

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { useForm } from 'uniforms';
import { SubmitField } from 'uniforms-unstyled';
import { Button } from '../Button';
export interface IDataFormControlsProps {
model: any | null;
onClear: () => void;
}
export const DataFormControls: React.FunctionComponent<IDataFormControlsProps> = ({ model, onClear }: React.PropsWithChildren<IDataFormControlsProps>) => {
const { formRef } = useForm();
return (
<div className='text-right'>
<SubmitField value={model ? `Update` : `Add`} />
<Button className='ml-4' secondary onClick={() => {
if (onClear) {
onClear();
}
formRef.reset();
}}>Cancel</Button>
</div>
);
};

View File

@@ -0,0 +1,202 @@
import * as React from 'react';
import { Header } from '../Header';
import { useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { DataForm } from './DataForm';
import { useCallback, useEffect, useState } from 'react';
import { DataFile } from '../../../models/DataFile';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { SponsorMsg } from '../SponsorMsg';
import { EventData } from '@estruyf/vscode';
import { DashboardCommand } from '../../DashboardCommand';
import { Button } from '../Button';
import { arrayMoveImmutable } from 'array-move';
import { EmptyView } from './EmptyView';
import { Container } from './SortableContainer';
import { SortableItem } from './SortableItem';
import { ChevronRightIcon } from '@heroicons/react/outline';
export interface IDataViewProps {}
export const DataView: React.FunctionComponent<IDataViewProps> = (props: React.PropsWithChildren<IDataViewProps>) => {
const [ selectedData, setSelectedData ] = useState<DataFile | null>(null);
const [ selectedIndex, setSelectedIndex ] = useState<number | null>(null);
const [ dataEntries, setDataEntries ] = useState<any[] | null>(null);
const settings = useRecoilValue(SettingsSelector);
const setSchema = (dataFile: DataFile) => {
setSelectedData(dataFile);
setSelectedIndex(null);
setDataEntries(null);
Messenger.send(DashboardMessage.getDataEntries, { ...dataFile });
};
const messageListener = (message: MessageEvent<EventData<any>>) => {
if (message.data.command === DashboardCommand.dataFileEntries) {
setDataEntries(message.data.data);
}
};
const deleteItem = useCallback((index: number) => {
const dataClone: any[] = Object.assign([], dataEntries);
if (!selectedData) {
return;
}
dataClone.splice(index, 1);
Messenger.send(DashboardMessage.putDataEntries, {
file: selectedData.file,
entries: dataClone
});
}, [selectedData, dataEntries]);
const onSubmit = useCallback((data: any) => {
const dataClone: any[] = Object.assign([], dataEntries);
if (selectedIndex !== null && selectedIndex !== undefined) {
dataClone[selectedIndex] = data;
} else {
dataClone.push(data);
}
if (!selectedData) {
return;
}
Messenger.send(DashboardMessage.putDataEntries, {
file: selectedData.file,
entries: dataClone
});
}, [selectedData, dataEntries, selectedIndex]);
const onSortEnd = useCallback(({ oldIndex, newIndex }: any) => {
if (!dataEntries || dataEntries.length === 0) {
return null;
}
if (selectedIndex !== null && selectedIndex !== undefined) {
setSelectedIndex(newIndex);
}
if (!selectedData) {
return;
}
const newEntries = arrayMoveImmutable(dataEntries, oldIndex, newIndex);
Messenger.send(DashboardMessage.putDataEntries, {
file: selectedData.file,
entries: newEntries
});
}, [selectedData, dataEntries, selectedIndex]);
useEffect(() => {
Messenger.listen(messageListener);
return () => {
Messenger.unlisten(messageListener);
}
}, []);
return (
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
<div className="relative w-full flex-grow max-w-7xl mx-auto border-b border-gray-200 dark:border-vulcan-300">
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside className={`flex flex-col flex-grow overflow-y-auto border-r border-gray-200 dark:border-vulcan-300 py-6 px-4`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Select your data type</h2>
<nav className={`flex-1 py-4 -mx-4 `}>
<div className={`divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>
{
(settings?.dataFiles && settings.dataFiles.length > 0) && (
settings.dataFiles.map((dataFile) => (
<button
key={dataFile.id}
type='button'
className={`px-4 py-2 flex items-center text-sm font-medium w-full text-left hover:bg-gray-200 dark:hover:bg-vulcan-400 hover:text-vulcan-500 dark:hover:text-whisper-500 ${selectedData?.id === dataFile.id ? 'bg-gray-300 dark:bg-vulcan-300 text-vulcan-500 dark:text-whisper-500' : 'text-gray-500 dark:text-whisper-900'}`}
onClick={() => setSchema(dataFile)}>
<ChevronRightIcon className='-ml-1 w-5 mr-2' />
<span>{dataFile.title}</span>
</button>
)
))
}
</div>
</nav>
</aside>
</div>
<section className={`pl-64 flex min-w-0 h-full`}>
{
selectedData ? (
<>
<div className={`w-1/3 py-6 px-4 flex-1 border-r border-gray-200 dark:border-vulcan-300`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Your {selectedData.title.toLowerCase()} data items</h2>
<div className='py-4'>
{
(dataEntries && dataEntries.length > 0) ? (
<>
<Container onSortEnd={onSortEnd} useDragHandle>
{
(dataEntries || []).map((dataEntry, idx) => (
<SortableItem
key={dataEntry[selectedData.labelField]}
value={dataEntry[selectedData.labelField]}
index={idx}
crntIndex={idx}
selectedIndex={selectedIndex}
onSelectedIndexChange={(index: number) => setSelectedIndex(index)}
onDeleteItem={deleteItem}
/>
))
}
</Container>
<Button
className='mt-4'
onClick={() => setSelectedIndex(null)}>
Add a new entry
</Button>
</>
) : (
<div className={`flex flex-col items-center justify-center`}>
<p className={`text-gray-500 dark:text-whisper-900`}>No {selectedData.title.toLowerCase()} data entries found</p>
</div>
)
}
</div>
</div>
<div className={`w-2/3 py-6 px-4`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Create or modify your {selectedData.title.toLowerCase()} data</h2>
{
selectedData ? (
<DataForm
schema={selectedData?.schema}
model={(dataEntries && selectedIndex !== null && selectedIndex !== undefined) ? dataEntries[selectedIndex] : null}
onSubmit={onSubmit}
onClear={() => setSelectedIndex(null)} />
) : (
<p>Select a data type to get started</p>
)
}
</div>
</>
) : (
<EmptyView />
)
}
</section>
</div>
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
</div>
);
};

View File

@@ -0,0 +1,13 @@
import { ExclamationCircleIcon } from '@heroicons/react/outline';
import * as React from 'react';
export interface IEmptyViewProps {}
export const EmptyView: React.FunctionComponent<IEmptyViewProps> = (props: React.PropsWithChildren<IEmptyViewProps>) => {
return (
<div className='flex flex-col items-center justify-center w-full'>
<ExclamationCircleIcon className={`w-1/12 text-gray-500 dark:text-whisper-900 opacity-90`} />
<h2 className={`text-xl text-gray-500 dark:text-whisper-900`}>Select your date type first</h2>
</div>
);
};

View File

@@ -0,0 +1,6 @@
import * as React from 'react';
import { SortableContainer } from 'react-sortable-hoc';
export interface ISortableContainerProps {}
export const Container = SortableContainer(({ children }: React.PropsWithChildren<ISortableContainerProps>) => ( <ul className={`-mx-4 divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>{children}</ul> ));

View File

@@ -0,0 +1,65 @@
import { PencilIcon, SelectorIcon, XIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { SortableHandle, SortableElement } from 'react-sortable-hoc';
import { Alert } from '../Modals/Alert';
export interface ISortableItemProps {
value: string;
index: number;
crntIndex: number;
selectedIndex: number | null;
onSelectedIndexChange: (index: number) => void;
onDeleteItem: (index: number) => void;
}
const DragHandle = SortableHandle(() => <SelectorIcon className={`w-6 h-6 cursor-move`} />);
export const SortableItem = SortableElement(({ value, selectedIndex, crntIndex, onSelectedIndexChange, onDeleteItem }: ISortableItemProps) => {
const [ showAlert, setShowAlert ] = React.useState(false);
const deleteItemConfirm = () => {
setShowAlert(true);
};
return (
<>
<li data-test={`${selectedIndex}-${crntIndex}`} className={`py-2 px-2 w-full flex justify-between content-center hover:bg-gray-200 dark:hover:bg-vulcan-400 ${selectedIndex === crntIndex ? `bg-gray-300 dark:bg-vulcan-300` : ``}`}>
<div className='flex items-center'>
<DragHandle />
<span>{value}</span>
</div>
<div className={`space-x-2 flex items-center`}>
<button
type='button'
className={`text-gray-500 dark:text-whisper-900 hover:text-gray-600 dark:hover:text-whisper-500`}
title={`Edit "${value}"`}
onClick={() => onSelectedIndexChange(crntIndex)}>
<PencilIcon className='w-4 h-4' />
<span className='sr-only'>Edit</span>
</button>
<button
type='button'
className={`text-gray-500 dark:text-whisper-900 hover:text-gray-600 dark:hover:text-whisper-500`}
title={`Delete "${value}"`}
onClick={() => deleteItemConfirm()}>
<XIcon className='w-4 h-4' />
<span className='sr-only'>Delete</span>
</button>
</div>
</li>
{
showAlert && (
<Alert
title={`Delete data entry`}
description={`Are you sure you want to delete the data entry?`}
okBtnText={`Delete`}
cancelBtnText={`Cancel`}
dismiss={() => setShowAlert(false)}
trigger={() => onDeleteItem(crntIndex)} />
)
}
</>
);
});

View File

@@ -0,0 +1 @@
export * from './DataView';

View File

@@ -13,11 +13,10 @@ import { useRecoilState, useResetRecoilState } from 'recoil';
import { CategoryAtom, DashboardViewAtom, SortingAtom, TagAtom } from '../../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { ClearFilters } from './ClearFilters';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import {PhotographIcon} from '@heroicons/react/outline';
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
import { ChoiceButton } from '../ChoiceButton';
import { MediaHeaderBottom } from '../Media/MediaHeaderBottom';
import { Tabs } from './Tabs';
export interface IHeaderProps {
settings: Settings | null;
@@ -55,19 +54,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
return (
<div className={`w-full sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
<div className="mb-0 border-b bg-gray-100 dark:bg-vulcan-500 border-gray-200 dark:border-vulcan-300 h-12">
<ul className="flex items-center justify-start h-full -mb-px" data-tabs-toggle="#myTabContent" role="tablist">
<li className="mr-2" role="presentation">
<button className={`flex items-center py-2 px-4 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 ${view === NavigationType.Contents ? "border-vulcan-500 text-vulcan-500 dark:border-whisper-500 dark:text-whisper-500" : "text-gray-500 dark:text-gray-400"}`} type="button" role="tab" aria-controls="profile" aria-selected="false" onClick={() => updateView(NavigationType.Contents)}>
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
</button>
</li>
<li className="mr-2" role="presentation">
<button className={`flex items-center py-2 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 ${view === NavigationType.Media ? "border-vulcan-500 text-vulcan-500 dark:border-whisper-500 dark:text-whisper-500" : "text-gray-500 dark:text-gray-400"}`} type="button" role="tab" aria-controls="dashboard" aria-selected="true" onClick={() => updateView(NavigationType.Media)}>
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</button>
</li>
</ul>
<div className="mb-0 border-b bg-gray-100 dark:bg-vulcan-500 border-gray-200 dark:border-vulcan-300">
<Tabs onNavigate={updateView} />
</div>
{

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { NavigationType } from '../../models';
import { DashboardViewAtom } from '../../state';
export interface ITabProps {
navigationType: NavigationType;
onNavigate: (navigationType: NavigationType) => void;
}
export const Tab: React.FunctionComponent<ITabProps> = ({navigationType, onNavigate, children}: React.PropsWithChildren<ITabProps>) => {
const view = useRecoilValue(DashboardViewAtom);
return (
<button
className={`h-full flex items-center py-2 px-4 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300 ${view === navigationType ? "border-vulcan-500 text-vulcan-500 dark:border-whisper-500 dark:text-whisper-500" : "text-gray-500 dark:text-gray-400"}`}
type="button"
role="tab"
aria-controls="profile"
aria-selected="false"
onClick={() => onNavigate(navigationType)}>
{children}
</button>
);
};

View File

@@ -0,0 +1,45 @@
import { DatabaseIcon, PhotographIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { NavigationType } from '../../models';
import { SettingsSelector } from '../../state';
import { Tab } from './Tab';
export interface ITabsProps {
onNavigate: (navigationType: NavigationType) => void;
}
export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.PropsWithChildren<ITabsProps>) => {
const settings = useRecoilValue(SettingsSelector);
return (
<ul className="flex items-center justify-start h-full" data-tabs-toggle="#myTabContent" role="tablist">
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Contents}
onNavigate={onNavigate}>
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
</Tab>
</li>
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Media}
onNavigate={onNavigate}>
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</Tab>
</li>
{
(settings?.dataFiles && settings.dataFiles.length > 0) && (
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Data}
onNavigate={onNavigate}>
<DatabaseIcon className={`h-6 w-auto mr-2`} /><span>Data</span>
</Tab>
</li>
)
}
</ul>
);
};

View File

@@ -26,6 +26,8 @@ export default function useMessages() {
setView(NavigationType.Media);
} else if (message.data.data?.type === NavigationType.Contents) {
setView(NavigationType.Contents);
} else if (message.data.data?.type === NavigationType.Data) {
setView(NavigationType.Data);
}
break;
case DashboardCommand.settings:

View File

@@ -1,4 +1,5 @@
export enum NavigationType {
Contents = "contents",
Media = "media"
Media = "media",
Data = "data",
}

View File

@@ -3,6 +3,7 @@ import { ContentFolder } from '../../models/ContentFolder';
import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models';
import { SortingOption } from './SortingOption';
import { DashboardViewType } from '.';
import { DataFile } from '../../models/DataFile';
export interface Settings {
beta: boolean;
@@ -24,6 +25,7 @@ export interface Settings {
customSorting: SortingSetting[] | undefined;
dashboardState: DashboardState;
scripts: CustomScript[];
dataFiles: DataFile[] | undefined;
}
export interface DashboardState {

View File

@@ -29,4 +29,72 @@
top: -1px;
left: 2px;
color: white;
}
.autoform {
@apply py-4;
h2 {
@apply text-sm mb-2;
}
form {
label {
@apply block;
@apply text-gray-500;
@apply my-2;
}
input {
@apply w-full text-vulcan-500;
&::placeholder {
@apply text-gray-500;
}
}
.errors {
ul {
@apply list-disc mt-4 pl-6 pr-4 py-4 border border-red-300 bg-red-50 bg-opacity-50 text-vulcan-500;
}
li {
@apply capitalize text-gray-900;
}
}
input[type="submit"] {
@apply w-auto mt-4 inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white bg-teal-600 cursor-pointer;
&:hover {
@apply bg-teal-700;
}
&:focus {
@apply outline-none;
}
&:disabled {
@apply bg-gray-500 opacity-50;
}
}
}
}
.vscode-dark .autoform {
form {
label {
@apply text-whisper-900;
}
input[type="submit"] {
@apply text-vulcan-500
}
.errors {
li {
@apply text-white;
}
}
}
}

View File

@@ -61,6 +61,10 @@ export async function activate(context: vscode.ExtensionContext) {
Dashboard.open({ type: "media" });
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardData, (data?: DashboardData) => {
Dashboard.open({ type: "data" });
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
Dashboard.close();
}));

View File

@@ -1,8 +1,9 @@
import { Folders } from "../commands/Folders";
import { Template } from "../commands/Template";
import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models";
import { DataFile } from "../models/DataFile";
import { Extension } from "./Extension";
import { FrameworkDetector } from "./FrameworkDetector";
import { Settings } from "./SettingsHelper";
@@ -44,7 +45,8 @@ export class DashboardSettings {
defaultSorting: Settings.get<string>(SETTINGS_MEDIA_SORTING_DEFAULT),
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace")
}
}
},
dataFiles: Settings.get<DataFile[]>(SETTINGS_DATA_FILES)
} as ISettings
}
}

View File

@@ -0,0 +1,61 @@
import { DataFile } from './../models/DataFile';
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { BaseListener } from "./BaseListener";
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { Folders } from '../commands/Folders';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { dirname } from 'path';
export class DataListener extends BaseListener {
public static process(msg: { command: DashboardMessage, data: any }) {
super.process(msg);
switch(msg.command) {
case (DashboardMessage.getDataEntries):
if (!(msg?.data as DataFile).file) {
this.sendMsg(DashboardCommand.dataFileEntries, []);
}
this.processDataFile(msg?.data);
break;
case (DashboardMessage.putDataEntries):
this.processDataUpdate(msg?.data);
break;
default:
return;
}
}
private static processDataUpdate(msgData: any) {
const { file, entries } = msgData as { file: string, entries: any[] };
const absPath = Folders.getAbsFilePath(file);
if (!existsSync(absPath)) {
const dirPath = dirname(absPath);
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
}
writeFileSync(absPath, JSON.stringify(entries, null, 2));
this.processDataFile(msgData);
}
private static async processDataFile(msgData: DataFile) {
const { file } = msgData;
const dataFile = this.getDataFile(file);
const jsonData = dataFile ? JSON.parse(dataFile) : [];
this.sendMsg(DashboardCommand.dataFileEntries, jsonData);
}
private static getDataFile(file: string) {
const absPath = Folders.getAbsFilePath(file);
if (existsSync(absPath)) {
return readFileSync(absPath, 'utf8');
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
export interface DashboardData {
type: "contents" | "media";
type: "contents" | "media" | "data";
data?: any;
}

7
src/models/DataFile.ts Normal file
View File

@@ -0,0 +1,7 @@
export interface DataFile {
id: string;
title: string;
file: string;
labelField: string;
schema: any;
}

View File

@@ -13,7 +13,8 @@
"sourceMap": true,
"rootDir": "src",
"strict": true,
"jsx": "react"
"jsx": "react",
"strictNullChecks": true
},
"exclude": [
"node_modules",