diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a4a46d..0517f558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index a6f80078..b8acd9f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5dd18699..5daf1bcd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/postcss.config.js b/postcss.config.js index f8e23b97..a44e167f 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -2,6 +2,7 @@ const tailwindcss = require('tailwindcss'); module.exports = { plugins: [ + require('postcss-nested'), tailwindcss('./tailwind.config.js'), require('autoprefixer'), ], diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 5ae6603a..23e4900c 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -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}`}` diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index f81e9e57..77e03034 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -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 diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 0afeff0d..561a0541 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -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"), diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 3826ec3d..166fa47e 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -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"; diff --git a/src/dashboardWebView/DashboardCommand.ts b/src/dashboardWebView/DashboardCommand.ts index a44b26a0..eea46870 100644 --- a/src/dashboardWebView/DashboardCommand.ts +++ b/src/dashboardWebView/DashboardCommand.ts @@ -4,5 +4,6 @@ export enum DashboardCommand { settings = "settings", media = "media", viewData = "viewData", - mediaUpdate = "mediaUpdate" + mediaUpdate = "mediaUpdate", + dataFileEntries = "dataFileEntries" } \ No newline at end of file diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 23dc81de..bc064e91 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -21,4 +21,6 @@ export enum DashboardMessage { setFramework = 'setFramework', setState = 'setState', runCustomScript = 'runCustomScript', + getDataEntries = 'getDataEntries', + putDataEntries = 'putDataEntries', } \ No newline at end of file diff --git a/src/dashboardWebView/components/Button.tsx b/src/dashboardWebView/components/Button.tsx index 8c7602f6..5ee9e5fe 100644 --- a/src/dashboardWebView/components/Button.tsx +++ b/src/dashboardWebView/components/Button.tsx @@ -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 = ({onClick, disabled, children}: React.PropsWithChildren) => { +export const Button: React.FunctionComponent = ({onClick, className, disabled, secondary, children}: React.PropsWithChildren) => { return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx new file mode 100644 index 00000000..628d0cc6 --- /dev/null +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -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 = (props: React.PropsWithChildren) => { + const [ selectedData, setSelectedData ] = useState(null); + const [ selectedIndex, setSelectedIndex ] = useState(null); + const [ dataEntries, setDataEntries ] = useState(null); + const settings = useRecoilValue(SettingsSelector); + + const setSchema = (dataFile: DataFile) => { + setSelectedData(dataFile); + setSelectedIndex(null); + setDataEntries(null); + + Messenger.send(DashboardMessage.getDataEntries, { ...dataFile }); + }; + + const messageListener = (message: MessageEvent>) => { + 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 ( +
+
+ +
+ +
+ + + +
+ +
+ { + selectedData ? ( + <> +
+

Your {selectedData.title.toLowerCase()} data items

+ +
+ { + (dataEntries && dataEntries.length > 0) ? ( + <> + + { + (dataEntries || []).map((dataEntry, idx) => ( + setSelectedIndex(index)} + onDeleteItem={deleteItem} + /> + )) + } + + + + ) : ( +
+

No {selectedData.title.toLowerCase()} data entries found

+
+ ) + } +
+
+
+

Create or modify your {selectedData.title.toLowerCase()} data

+ { + selectedData ? ( + setSelectedIndex(null)} /> + ) : ( +

Select a data type to get started

+ ) + } +
+ + ) : ( + + ) + } +
+
+ + +
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/EmptyView.tsx b/src/dashboardWebView/components/DataView/EmptyView.tsx new file mode 100644 index 00000000..975aa98a --- /dev/null +++ b/src/dashboardWebView/components/DataView/EmptyView.tsx @@ -0,0 +1,13 @@ +import { ExclamationCircleIcon } from '@heroicons/react/outline'; +import * as React from 'react'; + +export interface IEmptyViewProps {} + +export const EmptyView: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( +
+ +

Select your date type first

+
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/SortableContainer.tsx b/src/dashboardWebView/components/DataView/SortableContainer.tsx new file mode 100644 index 00000000..1282a5d5 --- /dev/null +++ b/src/dashboardWebView/components/DataView/SortableContainer.tsx @@ -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) => (
    {children}
)); \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/SortableItem.tsx b/src/dashboardWebView/components/DataView/SortableItem.tsx new file mode 100644 index 00000000..5e51c88a --- /dev/null +++ b/src/dashboardWebView/components/DataView/SortableItem.tsx @@ -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(() => ); + +export const SortableItem = SortableElement(({ value, selectedIndex, crntIndex, onSelectedIndexChange, onDeleteItem }: ISortableItemProps) => { + const [ showAlert, setShowAlert ] = React.useState(false); + + const deleteItemConfirm = () => { + setShowAlert(true); + }; + + return ( + <> +
  • +
    + + {value} +
    + +
    + + +
    +
  • + + { + showAlert && ( + setShowAlert(false)} + trigger={() => onDeleteItem(crntIndex)} /> + ) + } + + ); +}); \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/index.ts b/src/dashboardWebView/components/DataView/index.ts new file mode 100644 index 00000000..59faaae1 --- /dev/null +++ b/src/dashboardWebView/components/DataView/index.ts @@ -0,0 +1 @@ +export * from './DataView'; diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 028f5f2d..b9d3e804 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -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 = ({totalPages, folde return (
    -
    -
      -
    • - -
    • -
    • - -
    • -
    +
    +
    { diff --git a/src/dashboardWebView/components/Header/Tab.tsx b/src/dashboardWebView/components/Header/Tab.tsx new file mode 100644 index 00000000..1bfdcfcf --- /dev/null +++ b/src/dashboardWebView/components/Header/Tab.tsx @@ -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 = ({navigationType, onNavigate, children}: React.PropsWithChildren) => { + const view = useRecoilValue(DashboardViewAtom); + + return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Tabs.tsx b/src/dashboardWebView/components/Header/Tabs.tsx new file mode 100644 index 00000000..35e664f4 --- /dev/null +++ b/src/dashboardWebView/components/Header/Tabs.tsx @@ -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 = ({ onNavigate }: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsSelector); + + return ( +
      +
    • + + Contents + +
    • +
    • + + Media + +
    • + { + (settings?.dataFiles && settings.dataFiles.length > 0) && ( +
    • + + Data + +
    • + ) + } +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/hooks/useMessages.tsx b/src/dashboardWebView/hooks/useMessages.tsx index fe696d37..fec94a32 100644 --- a/src/dashboardWebView/hooks/useMessages.tsx +++ b/src/dashboardWebView/hooks/useMessages.tsx @@ -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: diff --git a/src/dashboardWebView/models/NavigationType.ts b/src/dashboardWebView/models/NavigationType.ts index da02c4f4..eba82bf9 100644 --- a/src/dashboardWebView/models/NavigationType.ts +++ b/src/dashboardWebView/models/NavigationType.ts @@ -1,4 +1,5 @@ export enum NavigationType { Contents = "contents", - Media = "media" + Media = "media", + Data = "data", } \ No newline at end of file diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 596b8f44..c8cfaa04 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -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 { diff --git a/src/dashboardWebView/styles.css b/src/dashboardWebView/styles.css index fe174556..be67906d 100644 --- a/src/dashboardWebView/styles.css +++ b/src/dashboardWebView/styles.css @@ -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; + } + } + } } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 5b97b065..f3357687 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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(); })); diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 35bce516..1c2e2080 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -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(SETTINGS_MEDIA_SORTING_DEFAULT), selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace") } - } + }, + dataFiles: Settings.get(SETTINGS_DATA_FILES) } as ISettings } } \ No newline at end of file diff --git a/src/listeners/DataListener.ts b/src/listeners/DataListener.ts new file mode 100644 index 00000000..a3b41696 --- /dev/null +++ b/src/listeners/DataListener.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/models/DashboardData.ts b/src/models/DashboardData.ts index 27318fea..df0f8f93 100644 --- a/src/models/DashboardData.ts +++ b/src/models/DashboardData.ts @@ -1,4 +1,4 @@ export interface DashboardData { - type: "contents" | "media"; + type: "contents" | "media" | "data"; data?: any; } \ No newline at end of file diff --git a/src/models/DataFile.ts b/src/models/DataFile.ts new file mode 100644 index 00000000..dc073595 --- /dev/null +++ b/src/models/DataFile.ts @@ -0,0 +1,7 @@ +export interface DataFile { + id: string; + title: string; + file: string; + labelField: string; + schema: any; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c16f7952..47ede041 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "sourceMap": true, "rootDir": "src", "strict": true, - "jsx": "react" + "jsx": "react", + "strictNullChecks": true }, "exclude": [ "node_modules",