diff --git a/CHANGELOG.md b/CHANGELOG.md index 9213bbc8..fc9b3093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ # Change Log -## [1.19.0] - 2020-07-20 +## [2.0.0] - 2020-07-23 +- Redesigned sidebar panel - Sidebar background styling match the VSCode defined sidebar color +- Added support for `mdx` files +- Added support for `enter` press in the combobox +- [#41](https://github.com/estruyf/vscode-front-matter/issues/41): Word count implementation + extra details +- [#40](https://github.com/estruyf/vscode-front-matter/issues/40): Added checks for the keyword usage in title, description, slug, and content ## [1.18.0] - 2020-07-20 diff --git a/README.md b/README.md index dc3a9a57..dc6b5ab6 100644 --- a/README.md +++ b/README.md @@ -26,23 +26,72 @@ The extension will automatically verify if your title and description are SEO co > If you see something missing in your article creation flow, please feel free to reach out. -## FrontMatter Panel (introduced in 1.10.0) +**Version 2** -In version `1.10.0` of this extension, the FrontMatter panel got introduced. This panel allows you to perform most of the extension actions by just a click on the button. +In version v2.0.0 we released the newly redesigned sidebar panel with improved SEO support. This extension makes it the only extension to manage your Markdown pages for your static sites in Visual Studio Code. -![FrontMatter Panel](./assets/frontmatter-panel.png) +

Table of Contents

+ +
+ Table of Contents +
    +
  1. The panel
  2. +
  3. Custom actions
  4. +
  5. Create articles from templates
  6. +
  7. Syntax highlighting for Hugo Shortcodes
  8. +
  9. Available commands
  10. +
  11. Extension settings
  12. +
  13. Feedback / issues / ideas
  14. +
+
+ +## The panel + +The Front Matter panel allows you to perform most of the extension actions by just a click on the button and it shows the SEO statuses of your title, description, and more. Initially, this panel has been created to make it easier to add tags and categories to your articles as the current VSCode multi-select is not optimal to use. To leverage most of the capabilities of the extension. SEO information and everyday actions like slug optimization, updating the date, and publish/drafting the article. +The panel consists of the following sections: + +**SEO Status** + +

+ SEO article status +

+ +**Actions** + +

+ Actions +

+ +**Metadata: Keywords, Tags, Categories** + +

+ Article metadata +

+ > **Info**: By default, the tags/categories picker allows you to insert existing and none tags/categories. When you enter a none existing tag/category, the panel shows an add `+` icon in front of that button. This functionality allows you to store this tag/category in your settings. If you want to disable this feature, you can do that by setting the `frontMatter.panel.freeform` setting to `false`. +**Other actions** + +At the bottom of the panel you can find the following actions: + +

+ Other actions +

+ +## Custom actions + Since version `1.15.0`, the extension allows you to create your own custom actions, by running Node.js scripts from your project. In order to use this functionality, you will need to configure the [`frontMatter.custom.scripts`](#frontmattercustomscripts) setting for your project. Once a custom action has been configured, it will appear on the Front Matter panel. -![](./assets/custom-actions.png) +

+ Custom action +

The current workspace-, file-path, and front matter data will be passed as an argument. In your script fetch these arguments as follows: @@ -62,7 +111,9 @@ if (arguments && arguments.length > 0) { The output of the script will be passed as a notification, and it allows you to copy the output. -![](./assets/custom-action-notification.png) +

+ Custom action notification +

## Creating articles from templates @@ -76,7 +127,9 @@ When adding files in the folder, you'll be able to run the `Front Matter: New ar ## Syntax highlighting for Hugo Shortcodes -![Shortcode syntax highlighting](./assets/syntax-highlighting.png) +

+ Shortcode syntax highlighting +

## Available commands: @@ -84,11 +137,13 @@ When adding files in the folder, you'll be able to run the `Front Matter: New ar Creates a new and allows you to include it into your post automatically -![Create tag or category](./assets/create-tag-category.gif) +

+ Create tag or category +

**Front Matter: Insert ** -Inserts a selected into the front matter of your article/post/... - When using this command, the FrontMatter panel opens and focuses on the specified type. +Inserts a selected into the front matter of your article/post/... - When using this command, the Front Matter panel opens and focuses on the specified type. > **Info**: This experience changed in version `1.11.0`. @@ -119,6 +174,7 @@ Update the `lastmod` (last modified) property of the current article/post/... to This command generates a clean slug for your article. It removes known stop words, punctuations, and special characters. Example: + ``` title: Just a sample page with a title slug: sample-page-title @@ -128,6 +184,13 @@ You can also specify a prefix and suffix, which can be added to the slug if you > **Info**: At the moment, the extension only supports English stopwords. +### Usage + +- Start by opening the command prompt: + - Windows: ⇧+ctrl+P + - Mac: ⇧+⌘+P +- Use one of the commands from above + ## Where is the data stored? The tags and categories are stored in the project VSCode user settings. You can find them back under: `.vscode/settings.json`. @@ -139,7 +202,7 @@ The tags and categories are stored in the project VSCode user settings. You can } ``` -## Additional extension settings +## Extension settings The extension has more settings that allow you to configure it to your needs further. Here is a list of settings that you can set: @@ -161,6 +224,17 @@ Specifies the optimal description length for SEO (set to `-1` to turn it off). D "frontMatter.taxonomy.seoDescriptionLength": 160 } ``` + +### `frontMatter.taxonomy.seoContentLength` + +Specifies the optimal minimum length for your articles. Between 1,760 words – 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off). + +```json +{ + "frontMatter.taxonomy.seoContentLength": 1760 +} +``` + ### `frontMatter.taxonomy.seoDescriptionLength` Specifies the name of the SEO description field for your page. Default is `description`. @@ -237,13 +311,6 @@ Allows you to specify a title and script path (starting relative from the root o > **Important**: When the command execution would fail when it cannot find the `node` command. You are able to specify your path to the node app. This is for instance required when using `nvm`. -## Usage - -- Start by opening the command prompt: - - Windows: ⇧+ctrl+P - - Mac: ⇧+⌘+P -- Use one of the commands from above - ## Feedback / issues / ideas Please submit them via creating an issue in the project repository: [issue list](https://github.com/estruyf/vscode-front-matter/issues). diff --git a/assets/media/styles.css b/assets/media/styles.css index 3c7b83a3..db5aaaef 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -21,6 +21,12 @@ } } +.collapsible__body, +.ext_settings { + padding: 1rem 1.25rem; + box-sizing: border-box; +} + #app, .frontmatter { height: 100%; } @@ -63,7 +69,8 @@ } .frontmatter { - padding: var(--input-margin-vertical) 0; + padding-top: 0; + padding-bottom: var(--input-margin-vertical); display: flex; flex-direction: column; justify-content: space-between; @@ -85,26 +92,28 @@ align-items: center; } -.section h3 i { +.section { + box-sizing: border-box; + position: relative; +} + +.section h3 svg { margin-right: 0.5rem; } -.seo__status__details { - margin-bottom: 2rem; +.seo__status__details, .seo__status__keywords { + margin-bottom: 1rem; +} + +.collapsible__body h4 { + text-align: center; + font-weight: bold; } .not-valid { color: var(--vscode-errorForeground); } -.article__actions { - margin-bottom: 2rem; -} - -.article__action { - margin-bottom: 1rem; -} - .article__tags { margin-bottom: 1rem; } @@ -138,9 +147,15 @@ .article__tags__input button { position: absolute; + bottom: 1px; top: 1px; right: 1px; width: 30px; + padding-bottom: 2px; + padding-top: 2px; + display: inline-flex; + align-items: center; + justify-content: center; } .article__tags ul { @@ -177,23 +192,23 @@ margin-right: .5rem; } -.article__tags__items__item_add { - display: inline-flex; - align-items: center; - width: auto; +.article__tags__items__item { + display: inline-block; + margin-bottom: .5rem; + margin-right: .5rem; } +.article__tags__items__item_add, .article__tags__items__item_delete { - display: inline-flex; - align-items: center; + display: inline-block; width: auto; } -.article__tags__items__item_delete i { - margin-left: 0.5rem; +.article__tags__items__item svg { + display: inline; + vertical-align: bottom; } - .article__tags__items__item_delete span { margin-left: .5rem; } @@ -225,6 +240,13 @@ filter: contrast(60%); } +.article__actions > * + * { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.seo__status__details ul > * + *, .ext_settings > * + * { --tw-space-y-reverse: 0; margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); @@ -236,17 +258,52 @@ align-items: center; } -.ext_link_block i { +.ext_link_block svg { margin-right: .5rem; } +.ext_link_block button, .ext_link_block a { - color: var(--vscode-textLink-foreground); + align-items: center; + color: var(--vscode-button-secondaryForeground); + background-color: var(--vscode-button-secondaryBackground); + border: 0px; + border-radius: 0px; + box-sizing: border-box; + cursor: pointer; + display: inline-flex; + font-size: var(--vscode-font-size); + font-weight: var(--vscode-font-weight); + line-height: 26px; + padding: 0px 14px; + user-select: none; + text-decoration: none; + width: auto; } .ext_link_block a:hover, .ext_link_block a:active, .ext_link_block a:focus, .ext_link_block a:visited { - color: var(--vscode-textLink-activeForeground); + color: var(--vscode-button-secondaryForeground); +} + +.table__cell { + overflow: hidden; +} + +.table__title { + text-transform: capitalize; +} + +.table__cell__validation { + text-align: center; +} + +.table__cell__validation .valid { + color: #46EC86; +} + +.table__cell__validation .warning { + color: #E6AF2E; } \ No newline at end of file diff --git a/assets/media/vscode.css b/assets/media/vscode.css index 8c491f11..b47e4a1d 100644 --- a/assets/media/vscode.css +++ b/assets/media/vscode.css @@ -1,5 +1,5 @@ :root { - --container-paddding: 20px; + --container-padding: 20px; --input-padding-vertical: 6px; --input-padding-horizontal: 4px; --input-margin-vertical: 4px; @@ -11,7 +11,6 @@ html, body { } body { - padding: 0 var(--container-paddding); color: var(--vscode-foreground); font-size: var(--vscode-font-size); font-weight: var(--vscode-font-weight); @@ -21,7 +20,7 @@ body { ol, ul { - padding-left: var(--container-paddding); + padding-left: var(--container-padding); } *:focus { @@ -51,6 +50,7 @@ button { outline-offset: 2px !important; color: var(--vscode-button-foreground); background: var(--vscode-button-background); + box-sizing: border-box; } button:hover { diff --git a/assets/v2.0.0/actions.png b/assets/v2.0.0/actions.png new file mode 100644 index 00000000..86f19488 Binary files /dev/null and b/assets/v2.0.0/actions.png differ diff --git a/assets/v2.0.0/custom-action.png b/assets/v2.0.0/custom-action.png new file mode 100644 index 00000000..baf4383d Binary files /dev/null and b/assets/v2.0.0/custom-action.png differ diff --git a/assets/v2.0.0/metadata.png b/assets/v2.0.0/metadata.png new file mode 100644 index 00000000..6cee6692 Binary files /dev/null and b/assets/v2.0.0/metadata.png differ diff --git a/assets/v2.0.0/other-actions.png b/assets/v2.0.0/other-actions.png new file mode 100644 index 00000000..11d9bd20 Binary files /dev/null and b/assets/v2.0.0/other-actions.png differ diff --git a/assets/v2.0.0/seo.png b/assets/v2.0.0/seo.png new file mode 100644 index 00000000..caefadb0 Binary files /dev/null and b/assets/v2.0.0/seo.png differ diff --git a/package-lock.json b/package-lock.json index bd0eda2c..e38511d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-front-matter", - "version": "1.18.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -48,170 +48,24 @@ "lit-element": "^2.5.1" } }, - "@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true - }, "@iarna/toml": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.3.tgz", "integrity": "sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg==", "dev": true }, - "@material-ui/core": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.2.tgz", - "integrity": "sha512-Q1npB8V73IC+eV2X6as+g71MpEGQwqKHUI2iujY62npk35V8nMx/bUXAHjv5kKG1BZ8s8XUWoG6s/VkjYPjjQA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.12.1", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "@types/react-transition-group": "^4.2.0", - "clsx": "^1.0.4", - "hoist-non-react-statics": "^3.3.2", - "popper.js": "1.16.1-lts", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0", - "react-transition-group": "^4.4.0" - }, - "dependencies": { - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - } - } - }, - "@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4" - } - }, - "@material-ui/lab": { - "version": "4.0.0-alpha.60", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz", - "integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - }, - "@material-ui/styles": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", - "integrity": "sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.8.0", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "csstype": "^2.5.2", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.5.1", - "jss-plugin-camel-case": "^10.5.1", - "jss-plugin-default-unit": "^10.5.1", - "jss-plugin-global": "^10.5.1", - "jss-plugin-nested": "^10.5.1", - "jss-plugin-props-sort": "^10.5.1", - "jss-plugin-rule-value-function": "^10.5.1", - "jss-plugin-vendor-prefixer": "^10.5.1", - "prop-types": "^15.7.2" - }, - "dependencies": { - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - }, - "csstype": { - "version": "2.6.17", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", - "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", - "dev": true - } - } - }, - "@material-ui/system": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", - "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "csstype": "^2.5.2", - "prop-types": "^15.7.2" - }, - "dependencies": { - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - }, - "csstype": { - "version": "2.6.17", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", - "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", - "dev": true - } - } - }, - "@material-ui/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", - "dev": true - }, - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/debug": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.6.tgz", + "integrity": "sha512-7fDOJFA/x8B+sO1901BmHlf5dE1cxBU8mRXj8QOEDnn16hhGJv/IHxJtZhvsabZsIMn0eLIyeOKAeqSNJJYTpA==", + "dev": true + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -240,6 +94,15 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/mdast": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.7.tgz", + "integrity": "sha512-YwR7OK8aPmaBvMMUi+pZXBNoW2unbVbfok4YRqGMJBe1dpDlzpRkJrYEYmvjxgs5JhuQmKfDexrN98u941Zasg==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -283,15 +146,6 @@ "@types/react": "*" } }, - "@types/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-KibDWL6nshuOJ0fu8ll7QnV/LVTo3PzQ9aCPnRUYPfX7eZohHwLIdNHj7pftanREzHNP4/nJa8oeM73uSiavMQ==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -321,6 +175,12 @@ } } }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, "@types/vscode": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.51.0.tgz", @@ -1054,6 +914,24 @@ } } }, + "character-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.0.tgz", + "integrity": "sha512-oHqMj3eAuJ77/P5PaIRcqk+C3hdfNwyCD2DAUcD5gyXkegAuF2USC40CEqPscDk4I8FRGMTojGJQkXDsN5QlJA==", + "dev": true + }, + "character-entities-legacy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-2.0.0.tgz", + "integrity": "sha512-YwaEtEvWLpFa6Wh3uVLrvirA/ahr9fki/NUd/Bd4OR6EdJ8D22hovYQEOUCBfQfcqnC4IAMGMsHXY1eXgL4ZZA==", + "dev": true + }, + "character-reference-invalid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.0.tgz", + "integrity": "sha512-pE3Z15lLRxDzWJy7bBHBopRwfI20sbrMVLQTC7xsPglCHf4Wv1e167OgYAFP78co2XlhojDyAqA+IAJse27//g==", + "dev": true + }, "chokidar": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", @@ -1136,12 +1014,6 @@ } } }, - "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "dev": true - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1336,16 +1208,6 @@ "nth-check": "~1.0.1" } }, - "css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", @@ -1370,6 +1232,15 @@ "integrity": "sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==", "dev": true }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -1476,16 +1347,6 @@ "utila": "~0.4" } }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -2329,15 +2190,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "requires": { - "react-is": "^16.7.0" - } - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -2526,12 +2378,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "dev": true - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2620,6 +2466,22 @@ } } }, + "is-alphabetical": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.0.tgz", + "integrity": "sha512-5OV8Toyq3oh4eq6sbWTYzlGdnMT/DPI5I0zxUBxjiigQsZycpkKF3kskkao3JyYGuYDHvhgJF+DrjMQp9SX86w==", + "dev": true + }, + "is-alphanumerical": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.0.tgz", + "integrity": "sha512-t+2GlJ+hO9yagJ+jU3+HSh80VKvz/3cG2cxbGGm4S0hjKuhWQXgPVUVOZz3tqZzMjhmphZ+1TIJTlRZRoe6GCQ==", + "dev": true, + "requires": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2677,6 +2539,12 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, + "is-decimal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.0.tgz", + "integrity": "sha512-QfrfjQV0LjoWQ1K1XSoEZkTAzSa14RKVMa5zg3SdAfzEmQzRM4+tbSFWb78creCeA9rNBzaZal92opi1TwPWZw==", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -2723,10 +2591,10 @@ "is-extglob": "^2.1.1" } }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=", + "is-hexadecimal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.0.tgz", + "integrity": "sha512-vGOtYkiaxwIiR0+Ng/zNId+ZZehGfINwTzdrDqc6iubbnQWhnPuYymOzOKUDqa2cSl59yHnEh2h6MvRLQsyNug==", "dev": true }, "is-number": { @@ -2837,92 +2705,6 @@ } } }, - "jss": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.7.1.tgz", - "integrity": "sha512-5QN8JSVZR6cxpZNeGfzIjqPEP+ZJwJJfZbXmeABNdxiExyO+eJJDy6WDtqTf8SDKnbL5kZllEpAP71E/Lt7PXg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-camel-case": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.7.1.tgz", - "integrity": "sha512-+ioIyWvmAfgDCWXsQcW1NMnLBvRinOVFkSYJUgewQ6TynOcSj5F1bSU23B7z0p1iqK0PPHIU62xY1iNJD33WGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.7.1" - } - }, - "jss-plugin-default-unit": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.7.1.tgz", - "integrity": "sha512-tW+dfYVNARBQb/ONzBwd8uyImigyzMiAEDai+AbH5rcHg5h3TtqhAkxx06iuZiT/dZUiFdSKlbe3q9jZGAPIwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.7.1" - } - }, - "jss-plugin-global": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.7.1.tgz", - "integrity": "sha512-FbxCnu44IkK/bw8X3CwZKmcAnJqjAb9LujlAc/aP0bMSdVa3/MugKQRyeQSu00uGL44feJJDoeXXiHOakBr/Zw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.7.1" - } - }, - "jss-plugin-nested": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.7.1.tgz", - "integrity": "sha512-RNbICk7FlYKaJyv9tkMl7s6FFfeLA3ubNIFKvPqaWtADK0KUaPsPXVYBkAu4x1ItgsWx67xvReMrkcKA0jSXfA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.7.1", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-props-sort": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.7.1.tgz", - "integrity": "sha512-eyd5FhA+J0QrpqXxO7YNF/HMSXXl4pB0EmUdY4vSJI4QG22F59vQ6AHtP6fSwhmBdQ98Qd9gjfO+RMxcE39P1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.7.1" - } - }, - "jss-plugin-rule-value-function": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.7.1.tgz", - "integrity": "sha512-fGAAImlbaHD3fXAHI3ooX6aRESOl5iBt3LjpVjxs9II5u9tzam7pqFUmgTcrip9VpRqYHn8J3gA7kCtm8xKwHg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.7.1", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-vendor-prefixer": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.7.1.tgz", - "integrity": "sha512-1UHFmBn7hZNsHXTkLLOL8abRl8vi+D1EVzWD4WmLFj55vawHZfnH1oEz6TUf5Y61XHv0smdHabdXds6BgOXe3A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.7.1" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3040,6 +2822,30 @@ "safe-buffer": "^5.1.2" } }, + "mdast-util-from-markdown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.0.tgz", + "integrity": "sha512-uj2G60sb7z1PNOeElFwCC9b/Se/lFXuLhVKFOAY2EHz/VvgbupTQRNXPoZl7rGpXYL6BNZgcgaybrlSWbo7n/g==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "parse-entities": "^3.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "dev": true + }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -3050,6 +2856,222 @@ "readable-stream": "^2.0.1" } }, + "micromark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.1.tgz", + "integrity": "sha512-DqC0I0V6D3wy/XmJTK1Tn+JPlMk7phhtDQ7ZJIZgAzHTyeVXMDdOaHQ2WQ7i5z3Bh4JK66nLBmjtgMrWwe8eMw==", + "dev": true, + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "parse-entities": "^3.0.0" + } + }, + "micromark-core-commonmark": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.0.tgz", + "integrity": "sha512-y9g7zymcKRBHM/aNBekstvs/Grpf+y4OEBULUTYvGZcusnp+JeOxmilJY4GMpo2/xY7iHQL9fjz5pD9pSAud9A==", + "dev": true, + "requires": { + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "parse-entities": "^3.0.0" + } + }, + "micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.0.tgz", + "integrity": "sha512-XWEucVZb+qBCe2jmlOnWr6sWSY6NHx+wtpgYFsm4G+dufOf6tTQRRo0bdO7XSlGPu5fyjpJenth6Ksnc5Mwfww==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.0.tgz", + "integrity": "sha512-flvC7Gx0dWVWorXuBl09Cr3wB5FTuYec8pMGVySIp2ZlqTcIjN/lFohZcP0EG//krTptm34kozHk7aK/CleCfA==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.0.0.tgz", + "integrity": "sha512-VdfDsHtUn/ocN2hGBkMunHHWcaN33llgwU0bmw2LA0tY1JvVkjHGvdiQSIk0pS3XeGCJLT6syS5i8y+1xbwDnQ==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.0.tgz", + "integrity": "sha512-cJpFVM768h6zkd8qJ1LNRrITfY4gwFt+tziPcIf71Ui8yFzY9wG3snZQqiWVq93PG4Sw6YOtcNiKJfVIs9qfGg==", + "dev": true + }, + "micromark-util-html-tag-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.0.0.tgz", + "integrity": "sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g==", + "dev": true + }, + "micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "dev": true, + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", + "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.0.tgz", + "integrity": "sha512-EsnG2qscmcN5XhkqQBZni/4oQbLFjz9yk3ZM/P8a3YUjwV6+6On2wehr1ALx0MxK3+XXXLTzuBKHDFeDFYRdgQ==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-symbol": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz", + "integrity": "sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ==", + "dev": true + }, + "micromark-util-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.0.tgz", + "integrity": "sha512-psf1WAaP1B77WpW4mBGDkTr+3RsPuDAgsvlP47GJzbH1jmjH8xjOx7Z6kp84L8oqHmy5pYO3Ev46odosZV+3AA==", + "dev": true + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", @@ -3169,6 +3191,12 @@ "run-queue": "^1.0.3" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "nan": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", @@ -3449,6 +3477,20 @@ "safe-buffer": "^5.1.1" } }, + "parse-entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-3.0.0.tgz", + "integrity": "sha512-AJlcIFDNPEP33KyJLguv0xJc83BNvjxwpuUIcetyXUsLpVXAUCePJ5kIoYtEN2R1ac0cYaRu/vk9dVFkewHQhQ==", + "dev": true, + "requires": { + "character-entities": "^2.0.0", + "character-entities-legacy": "^2.0.0", + "character-reference-invalid": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + } + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -3542,12 +3584,6 @@ "find-up": "^3.0.0" } }, - "popper.js": { - "version": "1.16.1-lts", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", - "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -3718,18 +3754,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -4420,12 +4444,6 @@ "setimmediate": "^1.0.4" } }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -4619,6 +4637,15 @@ "imurmurhash": "^0.1.4" } }, + "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", + "integrity": "sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -5029,9 +5056,8 @@ } }, "wc-react": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wc-react/-/wc-react-0.5.0.tgz", - "integrity": "sha512-3c4KzBArlfCqCYqY8hoJAIvxmzpWvoQqsOmy9OiwbnopSTE270i3R+LVD7NjE00UJkjXq7kmhobnx0qhi/eOrA==", + "version": "github:estruyf/wc-react#0989e37af55d3ee97392bf2747a818e4873243fa", + "from": "github:estruyf/wc-react", "dev": true }, "webpack": { diff --git a/package.json b/package.json index d3b5f7b4..d54a7573 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter", "description": "Simplifies working with front matter of your articles. Useful extension when you are using a static site generator like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/front-matter.png", - "version": "1.18.0", + "version": "2.0.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -142,6 +142,11 @@ "default": 160, "description": "Specifies the optimal description length for SEO (set to `-1` to turn it off)." }, + "frontMatter.taxonomy.seoContentLengh": { + "type": "number", + "default": 1760, + "description": "Specifies the optimal minimum length for your articles. Between 1,760 words – 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off)." + }, "frontMatter.taxonomy.seoDescriptionField": { "type": "string", "default": "description", @@ -240,9 +245,6 @@ "devDependencies": { "@bendera/vscode-webview-elements": "0.5.0", "@iarna/toml": "2.2.3", - "@material-ui/core": "4.12.2", - "@material-ui/icons": "4.11.2", - "@material-ui/lab": "4.0.0-alpha.60", "@types/glob": "7.1.3", "@types/js-yaml": "3.12.1", "@types/mocha": "^5.2.6", @@ -257,12 +259,13 @@ "gray-matter": "4.0.2", "html-loader": "1.3.2", "html-webpack-plugin": "4.5.0", + "mdast-util-from-markdown": "1.0.0", "react": "17.0.1", "react-dom": "17.0.1", "ts-loader": "8.0.3", "tslint": "6.1.3", "typescript": "4.0.2", - "wc-react": "0.5.0", + "wc-react": "github:estruyf/wc-react", "webpack": "4.44.2", "webpack-cli": "3.3.12" }, diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index 77752ff5..e941d4b1 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -17,7 +17,7 @@ export class StatusListener { const publishMsg = "to publish"; let editor = vscode.window.activeTextEditor; - if (editor && editor.document && editor.document.languageId.toLowerCase() === "markdown") { + if (editor && editor.document && (editor.document.languageId.toLowerCase() === "markdown" || editor.document.languageId.toLowerCase() === "mdx")) { try { const article = ArticleHelper.getFrontMatter(editor); diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 2a184ba3..befd3921 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -18,6 +18,7 @@ export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType"; export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength"; export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength"; +export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh"; export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField"; export const SETTING_TEMPLATES_FOLDER = "templates.folder"; diff --git a/src/extension.ts b/src/extension.ts index fa7ba3fb..93ef664e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -113,6 +113,6 @@ const debounceShowDraftTrigger = () => { return (fnc: any, time: number) => { const functionCall = (...args: any[]) => fnc.apply(args); clearTimeout(timeout); - timeout = setTimeout(functionCall, time); + timeout = setTimeout(functionCall, time) as any; }; }; diff --git a/src/helpers/FilesHelper.ts b/src/helpers/FilesHelper.ts index 9e87aa97..482bb21e 100644 --- a/src/helpers/FilesHelper.ts +++ b/src/helpers/FilesHelper.ts @@ -9,12 +9,13 @@ export class FilesHelper { public static async getMdFiles(): Promise { const mdFiles = await vscode.workspace.findFiles('**/*.md', "**/node_modules/**,**/archetypes/**"); const markdownFiles = await vscode.workspace.findFiles('**/*.markdown', "**/node_modules/**,**/archetypes/**"); + const mdxFiles = await vscode.workspace.findFiles('**/*.mdx', "**/node_modules/**,**/archetypes/**"); if (!mdFiles && !markdownFiles) { vscode.window.showInformationMessage(`${EXTENSION_NAME}: No MD files found.`); return null; } - const allMdFiles = mdFiles.concat(markdownFiles); + const allMdFiles = [...mdFiles, ...markdownFiles, ...mdxFiles]; return allMdFiles; } } \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 4ad2ce25..d15018f6 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -11,6 +11,7 @@ export interface PanelSettings { export interface SEO { title: number; description: number; + content: number; descriptionField: string; } diff --git a/src/viewpanel/CommandToCode.ts b/src/viewpanel/CommandToCode.ts index e3b1630f..ebb4cdf1 100644 --- a/src/viewpanel/CommandToCode.ts +++ b/src/viewpanel/CommandToCode.ts @@ -6,6 +6,7 @@ export enum CommandToCode { publish = 'publish', updateTags = "update-tags", updateCategories = "update-categories", + updateKeywords = "update-keywords", addTagToSettings = "add-tag", addCategoryToSettings = "add-category", openSettings = "open-settings", diff --git a/src/viewpanel/TagType.ts b/src/viewpanel/TagType.ts index 931bb0ae..bf008af1 100644 --- a/src/viewpanel/TagType.ts +++ b/src/viewpanel/TagType.ts @@ -1,4 +1,5 @@ export enum TagType { tags = "Tags", - categories = "Categories" + categories = "Categories", + keywords = "Keywords" } \ No newline at end of file diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 8bdc5cfb..88ec64dc 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -1,7 +1,14 @@ import * as React from 'react'; import { CommandToCode } from './CommandToCode'; import { Actions } from './components/Actions'; -import { Icon } from './components/Icon'; +import { Collapsible } from './components/Collapsible'; +import { BugIcon } from './components/Icons/BugIcon'; +import { FileIcon } from './components/Icons/FileIcon'; +import { FolderOpenedIcon } from './components/Icons/FolderOpenedIcon'; +import { ListUnorderedIcon } from './components/Icons/ListUnorderedIcon'; +import { SettingsIcon } from './components/Icons/SettingsIcon'; +import { SymbolKeywordIcon } from './components/Icons/SymbolKeywordIcon'; +import { TagIcon } from './components/Icons/TagIcon'; import { SeoStatus } from './components/SeoStatus'; import { Spinner } from './components/Spinner'; import { TagPicker } from './components/TagPicker'; @@ -51,49 +58,57 @@ export const ViewPanel: React.FunctionComponent = (props: React settings && metadata && } - { - (settings && settings.tags && settings.tags.length > 0) && ( - } - crntSelected={metadata.tags || []} - options={settings.tags} - freeform={settings.freeform} - focussed={focusElm === TagType.tags} - unsetFocus={unsetFocus} /> - ) - } - { - (settings && settings.categories && settings.categories.length > 0) && ( - } - crntSelected={metadata.categories || []} - options={settings.categories} - freeform={settings.freeform} - focussed={focusElm === TagType.categories} - unsetFocus={unsetFocus} /> - ) - } + + { + } + crntSelected={metadata.keywords || []} + options={[]} + freeform={true} + focussed={focusElm === TagType.keywords} + unsetFocus={unsetFocus} + disableConfigurable /> + } + { + (settings && settings.tags && settings.tags.length > 0) && ( + } + crntSelected={metadata.tags || []} + options={settings.tags} + freeform={settings.freeform} + focussed={focusElm === TagType.tags} + unsetFocus={unsetFocus} /> + ) + } + { + (settings && settings.categories && settings.categories.length > 0) && ( + } + crntSelected={metadata.categories || []} + options={settings.categories} + freeform={settings.freeform} + focussed={focusElm === TagType.categories} + unsetFocus={unsetFocus} /> + ) + } +
- - Open settings +
- - Reveal file in folder +
- - Reveal project folder +
diff --git a/src/viewpanel/components/Actions.tsx b/src/viewpanel/components/Actions.tsx index 630d4894..22d2ae2f 100644 --- a/src/viewpanel/components/Actions.tsx +++ b/src/viewpanel/components/Actions.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { PanelSettings } from '../../models/PanelSettings'; +import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; import { DateAction } from './DateAction'; -import { Icon } from './Icon'; import { PublishAction } from './PublishAction'; import { SlugAction } from './SlugAction'; @@ -19,22 +19,22 @@ export const Actions: React.FunctionComponent = (props: React.Pro } return ( -
-

Actions

+ +
+ { metadata && metadata.title && } - { metadata && metadata.title && } - - + - { metadata && typeof metadata.draft !== undefined && } + { metadata && typeof metadata.draft !== undefined && } - { - (settings && settings.scripts && settings.scripts.length > 0) && ( - settings.scripts.map((value) => ( - - )) - ) - } -
+ { + (settings && settings.scripts && settings.scripts.length > 0) && ( + settings.scripts.map((value) => ( + + )) + ) + } +
+ ); }; \ No newline at end of file diff --git a/src/viewpanel/components/ArticleDetails.tsx b/src/viewpanel/components/ArticleDetails.tsx new file mode 100644 index 00000000..a0ba922f --- /dev/null +++ b/src/viewpanel/components/ArticleDetails.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell, VsTableRow, VsTableCell } from './VscodeComponents'; + +export interface IArticleDetailsProps { + details: { + headings: number; + paragraphs: number; + wordCount: number; + } +} + +export const ArticleDetails: React.FunctionComponent = ({details}: React.PropsWithChildren) => { + + if (!details || (details.headings === undefined && details.paragraphs === undefined)) { + return null; + } + + return ( +
+

More details

+ + + + Type + Total + + + { + details?.headings !== undefined && ( + + Headings + {details.headings} + + ) + } + + { + details?.paragraphs !== undefined && ( + + Paragraphs + {details.paragraphs} + + ) + } + + +
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Collapsible.tsx b/src/viewpanel/components/Collapsible.tsx new file mode 100644 index 00000000..62876064 --- /dev/null +++ b/src/viewpanel/components/Collapsible.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { VsCollapsible } from './VscodeComponents'; + +export interface ICollapsibleProps { + title: string; + sendUpdate?: (open: boolean) => void; +} + +export const Collapsible: React.FunctionComponent = ({children, title, sendUpdate}: React.PropsWithChildren) => { + const [ isOpen, setIsOpen ] = React.useState(true); + + // This is a work around for a lit-element issue of duplicate slot names + const triggerClick = (e: React.MouseEvent) => { + if ((e.target as any).tagName.toUpperCase() === 'VSCODE-COLLAPSIBLE') { + setIsOpen(prev => { + if (sendUpdate) { + sendUpdate(!prev); + } + return !prev; + }); + } + } + + return ( + +
+ {children} +
+
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/AddIcon.tsx b/src/viewpanel/components/Icons/AddIcon.tsx new file mode 100644 index 00000000..fa94e879 --- /dev/null +++ b/src/viewpanel/components/Icons/AddIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IAddIconProps {} + +export const AddIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/ArchiveIcon.tsx b/src/viewpanel/components/Icons/ArchiveIcon.tsx new file mode 100644 index 00000000..cde66946 --- /dev/null +++ b/src/viewpanel/components/Icons/ArchiveIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IArchiveIconProps {} + +export const ArchiveIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/BugIcon.tsx b/src/viewpanel/components/Icons/BugIcon.tsx new file mode 100644 index 00000000..fa438c38 --- /dev/null +++ b/src/viewpanel/components/Icons/BugIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IBugIconProps {} + +export const BugIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/CheckIcon.tsx b/src/viewpanel/components/Icons/CheckIcon.tsx new file mode 100644 index 00000000..4382e9d1 --- /dev/null +++ b/src/viewpanel/components/Icons/CheckIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface ICheckIconProps {} + +export const CheckIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/FileIcon.tsx b/src/viewpanel/components/Icons/FileIcon.tsx new file mode 100644 index 00000000..42388d21 --- /dev/null +++ b/src/viewpanel/components/Icons/FileIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IFileIconProps {} + +export const FileIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/FolderOpenedIcon.tsx b/src/viewpanel/components/Icons/FolderOpenedIcon.tsx new file mode 100644 index 00000000..e3bc4f44 --- /dev/null +++ b/src/viewpanel/components/Icons/FolderOpenedIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IFolderOpenedIconProps {} + +export const FolderOpenedIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/ListUnorderedIcon.tsx b/src/viewpanel/components/Icons/ListUnorderedIcon.tsx new file mode 100644 index 00000000..751b6e0a --- /dev/null +++ b/src/viewpanel/components/Icons/ListUnorderedIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IListUnorderedIconProps {} + +export const ListUnorderedIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/SettingsIcon.tsx b/src/viewpanel/components/Icons/SettingsIcon.tsx new file mode 100644 index 00000000..0e4b30e1 --- /dev/null +++ b/src/viewpanel/components/Icons/SettingsIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface ISettingsIconProps {} + +export const SettingsIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/SymbolKeywordIcon.tsx b/src/viewpanel/components/Icons/SymbolKeywordIcon.tsx new file mode 100644 index 00000000..b100bfe4 --- /dev/null +++ b/src/viewpanel/components/Icons/SymbolKeywordIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface ISymbolKeywordIconProps {} + +export const SymbolKeywordIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/TagIcon.tsx b/src/viewpanel/components/Icons/TagIcon.tsx new file mode 100644 index 00000000..4178de59 --- /dev/null +++ b/src/viewpanel/components/Icons/TagIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface ITagIconProps {} + +export const TagIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/Icons/WarningIcon.tsx b/src/viewpanel/components/Icons/WarningIcon.tsx new file mode 100644 index 00000000..84b3dde8 --- /dev/null +++ b/src/viewpanel/components/Icons/WarningIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +export interface IWarningIconProps {} + +export const WarningIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoDetails.tsx b/src/viewpanel/components/SeoDetails.tsx index 60b48e42..27c91680 100644 --- a/src/viewpanel/components/SeoDetails.tsx +++ b/src/viewpanel/components/SeoDetails.tsx @@ -1,21 +1,41 @@ import * as React from 'react'; +import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell, VsTableRow, VsTableCell } from './VscodeComponents'; export interface ISeoDetailsProps { allowedLength: number; title: string; - value: string; + value: number; + valueTitle: string; + noValidation?: boolean; } export const SeoDetails: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { allowedLength, title, value } = props; + const { allowedLength, title, value, valueTitle, noValidation } = props; + + const validate = () => { + if (noValidation) { + return ""; + } + + return value <= allowedLength ? "valid" : "not-valid" + }; return ( -
-

{title}

-
    -
  • Length: {value.length}
  • -
  • Recommended length: {allowedLength}
  • -
+
+

{title}

+ + + + {valueTitle} + Recommended + + + + {value} + {allowedLength} + + +
); }; \ No newline at end of file diff --git a/src/viewpanel/components/SeoFieldInfo.tsx b/src/viewpanel/components/SeoFieldInfo.tsx new file mode 100644 index 00000000..e46fa9c0 --- /dev/null +++ b/src/viewpanel/components/SeoFieldInfo.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { ValidInfo } from './ValidInfo'; +import { VsTableCell, VsTableRow } from './VscodeComponents'; + +export interface ISeoFieldInfoProps { + title: string; + value: any; + recommendation: any; + isValid?: boolean; +} + +export const SeoFieldInfo: React.FunctionComponent = ({ title, value, recommendation, isValid }: React.PropsWithChildren) => { + return ( + + {title} + {value} + {recommendation} + + { isValid !== undefined ? : - } + + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoKeywordInfo.tsx b/src/viewpanel/components/SeoKeywordInfo.tsx new file mode 100644 index 00000000..66829fdd --- /dev/null +++ b/src/viewpanel/components/SeoKeywordInfo.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { ValidInfo } from './ValidInfo'; +import { VsTableCell, VsTableRow } from './VscodeComponents'; + +export interface ISeoKeywordInfoProps { + keyword: string; + title: string; + description: string; + slug: string; + content: string; +} + +export const SeoKeywordInfo: React.FunctionComponent = ({keyword, title, description, slug, content}: React.PropsWithChildren) => { + + return ( + + {keyword} + + + + + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoKeywords.tsx b/src/viewpanel/components/SeoKeywords.tsx new file mode 100644 index 00000000..4747ec0b --- /dev/null +++ b/src/viewpanel/components/SeoKeywords.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { SeoKeywordInfo } from './SeoKeywordInfo'; +import { VsTable, VsTableBody, VsTableCell, VsTableHeader, VsTableHeaderCell, VsTableRow } from './VscodeComponents'; + +export interface ISeoKeywordsProps { + keywords: string[] | null; + + title: string; + description: string; + slug: string; + content: string; +} + +export const SeoKeywords: React.FunctionComponent = ({keywords, ...data}: React.PropsWithChildren) => { + + if (!keywords || keywords.length === 0) { + return null; + } + + return ( +
+

Keywords

+ + + + Keyword + Title + Description + Slug + Content + + + { + keywords.map((keyword, index) => { + return ( + + ); + }) + } + + + + +
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoStatus.tsx b/src/viewpanel/components/SeoStatus.tsx index 448e2d59..e97ecef3 100644 --- a/src/viewpanel/components/SeoStatus.tsx +++ b/src/viewpanel/components/SeoStatus.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { SEO } from '../../models/PanelSettings'; -import { Icon } from './Icon'; +import { ArticleDetails } from './ArticleDetails'; +import { Collapsible } from './Collapsible'; import { SeoDetails } from './SeoDetails'; +import { SeoFieldInfo } from './SeoFieldInfo'; +import { SeoKeywords } from './SeoKeywords'; +import { ValidInfo } from './ValidInfo'; +import { VsTable, VsTableBody, VsTableCell, VsTableHeader, VsTableHeaderCell, VsTableRow } from './VscodeComponents'; export interface ISeoStatusProps { seo: SEO; @@ -11,6 +16,7 @@ export interface ISeoStatusProps { export const SeoStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { const { data, seo } = props; const { title } = data; + const [ isOpen, setIsOpen ] = React.useState(true); const { descriptionField } = seo; @@ -18,13 +24,59 @@ export const SeoStatus: React.FunctionComponent = (props: React return null; } + const renderContent = () => { + if (!isOpen) { + return null; + } + + return ( +
+
+

Recommendations

+ + + + Property + Length + Recommended + Valid + + + { + (title && seo.title > 0) && ( + + ) + } + + { + (data[descriptionField] && seo.description > 0) && ( + + ) + } + + { + (seo.content > 0 && data?.articleDetails?.wordCount) && ( + + ) + } + + +
+ + + + +
+ ); + }; + return ( -
-

- SEO Status -

- { (title && seo.title > 0) && } - { (data[descriptionField] && seo.description > 0) && } -
+ setIsOpen(value)}> + { renderContent() } + ); }; \ No newline at end of file diff --git a/src/viewpanel/components/Tag.tsx b/src/viewpanel/components/Tag.tsx index 2f1b88c3..03f900e5 100644 --- a/src/viewpanel/components/Tag.tsx +++ b/src/viewpanel/components/Tag.tsx @@ -1,28 +1,28 @@ import * as React from 'react'; -import AddIcon from '@material-ui/icons/Add'; -import DeleteIcon from '@material-ui/icons/Delete'; -import { Icon } from './Icon'; +import { AddIcon } from './Icons/AddIcon'; +import { ArchiveIcon } from './Icons/ArchiveIcon'; export interface ITagProps { className: string; value: string; title: string; + disableConfigurable?: boolean; + onCreate?: (tags: string) => void; onRemove: (tags: string) => void; } export const Tag: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { value, className, title, onRemove, onCreate } = props; + const { value, className, title, onRemove, onCreate, disableConfigurable } = props; return (
{ - onCreate && - + !disableConfigurable && onCreate && + } - - +
); }; \ No newline at end of file diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index 5ab0c931..804da546 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -5,6 +5,7 @@ import { CommandToCode } from '../CommandToCode'; import { TagType } from '../TagType'; import { MessageHelper } from '../helper/MessageHelper'; import Downshift from 'downshift'; +import { AddIcon } from './Icons/AddIcon'; export interface ITagPickerProps { type: string; @@ -14,10 +15,11 @@ export interface ITagPickerProps { freeform: boolean; focussed: boolean; unsetFocus: () => void; + disableConfigurable?: boolean; } export const TagPicker: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { icon, type, crntSelected, options, freeform, focussed, unsetFocus } = props; + const { icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable } = props; const [ selected, setSelected ] = React.useState([]); const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); @@ -48,7 +50,16 @@ export const TagPicker: React.FunctionComponent = (props: React * @param values */ const sendUpdate = (values: string[]) => { - const cmdType = type === TagType.tags ? CommandToCode.updateTags : CommandToCode.updateCategories; + let cmdType = CommandToCode.updateCategories; + + if (type === TagType.tags) { + cmdType = CommandToCode.updateTags; + } else if (type === TagType.categories) { + cmdType = CommandToCode.updateCategories; + } else if (type === TagType.keywords) { + cmdType = CommandToCode.updateKeywords; + } + MessageHelper.sendMessage(cmdType, values); }; @@ -132,6 +143,12 @@ export const TagPicker: React.FunctionComponent = (props: React ref: inputRef, onFocus: openMenu as any, onClick: openMenu as any, + onKeyDown: (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + insertUnkownTag(closeMenu); + } + }, onBlur: () => { closeMenu(); unsetFocus(); @@ -145,9 +162,12 @@ export const TagPicker: React.FunctionComponent = (props: React { freeform && ( - + onClick={() => insertUnkownTag(closeMenu)}> + + ) }
@@ -166,7 +186,7 @@ export const TagPicker: React.FunctionComponent = (props: React } - a.toLowerCase() < b.toLowerCase() ? -1 : 1 )} onRemove={onRemove} onCreate={onCreate} options={options} /> + a.toLowerCase() < b.toLowerCase() ? -1 : 1 )} onRemove={onRemove} onCreate={onCreate} options={options} disableConfigurable={!!disableConfigurable} /> ); }; \ No newline at end of file diff --git a/src/viewpanel/components/Tags.tsx b/src/viewpanel/components/Tags.tsx index 131161d7..f60dbf15 100644 --- a/src/viewpanel/components/Tags.tsx +++ b/src/viewpanel/components/Tags.tsx @@ -5,12 +5,14 @@ export interface ITagsProps { values: string[]; options: string[]; + disableConfigurable?: boolean; + onCreate: (tags: string) => void; onRemove: (tags: string) => void; } export const Tags: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { values, options, onCreate, onRemove } = props; + const { values, options, onCreate, onRemove, disableConfigurable } = props; const knownTags = values.filter(v => options.includes(v)); const unknownTags = values.filter(v => !options.includes(v)); @@ -24,7 +26,7 @@ export const Tags: React.FunctionComponent = (props: React.PropsWith } { unknownTags.map(t => ( - + )) } diff --git a/src/viewpanel/components/ValidInfo.tsx b/src/viewpanel/components/ValidInfo.tsx new file mode 100644 index 00000000..9936018a --- /dev/null +++ b/src/viewpanel/components/ValidInfo.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { CheckIcon } from './Icons/CheckIcon'; +import { WarningIcon } from './Icons/WarningIcon'; + +export interface IValidInfoProps { + isValid: boolean; +} + +export const ValidInfo: React.FunctionComponent = ({isValid}: React.PropsWithChildren) => { + return ( + <> + { + isValid ? ( + + ) : ( + + ) + } + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/VscodeComponents.ts b/src/viewpanel/components/VscodeComponents.ts index 81a3b561..d4cb77f2 100644 --- a/src/viewpanel/components/VscodeComponents.ts +++ b/src/viewpanel/components/VscodeComponents.ts @@ -1,3 +1,9 @@ import {wrapWc} from 'wc-react'; -export const VscodeIcon = wrapWc(`vscode-icon`); \ No newline at end of file +export const VsTable = wrapWc(`vscode-table`); +export const VsTableHeader = wrapWc(`vscode-table-header`); +export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`); +export const VsTableBody = wrapWc(`vscode-table-body`); +export const VsTableRow = wrapWc(`vscode-table-row`); +export const VsTableCell = wrapWc(`vscode-table-cell`); +export const VsCollapsible = wrapWc(`vscode-collapsible`); \ No newline at end of file diff --git a/src/viewpanel/index.tsx b/src/viewpanel/index.tsx index 4d5d7504..55182aad 100644 --- a/src/viewpanel/index.tsx +++ b/src/viewpanel/index.tsx @@ -2,7 +2,14 @@ import * as React from "react"; import { render } from "react-dom"; import { ViewPanel } from "./ViewPanel"; -import '@bendera/vscode-webview-elements/dist/vscode-icon'; +// require('@vscode/codicons/dist/codicon.css'); +import '@bendera/vscode-webview-elements/dist/vscode-table'; +import '@bendera/vscode-webview-elements/dist/vscode-table-header'; +import '@bendera/vscode-webview-elements/dist/vscode-table-header-cell'; +import '@bendera/vscode-webview-elements/dist/vscode-table-body'; +import '@bendera/vscode-webview-elements/dist/vscode-table-row'; +import '@bendera/vscode-webview-elements/dist/vscode-table-cell'; +import '@bendera/vscode-webview-elements/dist/vscode-collapsible'; declare const acquireVsCodeApi: () => { getState: () => T; diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index c3856f3c..9c7b8cc2 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -1,4 +1,4 @@ -import { SETTING_CUSTOM_SCRIPTS, SETTING_SEO_DESCRIPTION_FIELD } from './../constants/settings'; +import { SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD } from './../constants/settings'; import * as os from 'os'; import { PanelSettings, CustomScript } from './../models/PanelSettings'; import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; @@ -11,6 +11,8 @@ import { TagType } from '../viewpanel/TagType'; import { TaxonomyType } from '../models'; import { exec } from 'child_process'; import * as path from 'path'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { Content } from 'mdast'; export class ExplorerView implements WebviewViewProvider, Disposable { @@ -96,6 +98,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.updateCategories: this.updateTags(TagType.categories, msg.data || []); break; + case CommandToCode.updateKeywords: + this.updateTags(TagType.keywords, msg.data || []); + break; case CommandToCode.addTagToSettings: this.addTags(TagType.tags, msg.data); break; @@ -150,7 +155,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * @param metadata */ public pushMetadata(metadata: any) { - this.postWebviewMessage({ command: Command.metadata, data: metadata }); + this.postWebviewMessage({ command: Command.metadata, data: { + ...metadata, + articleDetails: this.getArticleDetails() + } }); } /** @@ -219,6 +227,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { seo: { title: config.get(SETTING_SEO_TITLE_LENGTH) as number || -1, description: config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1, + content: config.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1, descriptionField: config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description" }, slug: { @@ -243,7 +252,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } const article = ArticleHelper.getFrontMatter(editor); - this.postWebviewMessage({ command: Command.metadata, data: article!.data }); + this.postWebviewMessage({ command: Command.metadata, data: { + ...article!.data, + articleDetails: this.getArticleDetails() + }}); } /** @@ -261,7 +273,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable { if (article && article.data) { article.data[tagType.toLowerCase()] = values || []; ArticleHelper.update(editor, article); - this.postWebviewMessage({ command: Command.metadata, data: article.data }); + this.postWebviewMessage({ command: Command.metadata, data: { + ...article.data, + articleDetails: this.getArticleDetails() + }}); } } @@ -285,6 +300,59 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } + /** + * Get article details + */ + private getArticleDetails() { + const editor = window.activeTextEditor; + if (!editor) { + return ""; + } + + const article = ArticleHelper.getFrontMatter(editor); + + if (article && article.content) { + let content = article.content; + content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes + + const mdTree = fromMarkdown(content); + const headings = mdTree.children.filter(node => node.type === 'heading').length; + const paragraphs = mdTree.children.filter(node => node.type === 'paragraph').length; + const wordCount = this.wordCount(0, mdTree); + + return { + headings, + paragraphs, + wordCount, + content: article.content + }; + } + + return null; + } + + private counts(acc: any, node: any) { + // add 1 to an initial or existing value + acc[node.type] = (acc[node.type] || 0) + 1; + + // find and add up the counts from all of this node's children + return (node.children || []).reduce( + (childAcc: any, childNode: any) => this.counts(childAcc, childNode), + acc + ); + } + + /** + * Get the word count for the current document + */ + private wordCount(count: number, node: Content | any) { + if (node.type === "text") { + return count + node.value.split(" ").length; + } else { + return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count); + } + } + /** * Post data to the panel * @param msg @@ -312,7 +380,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const styleResetUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'reset.css')); const stylesUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'styles.css')); const scriptUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'dist', 'viewpanel.js')); - const codiconsUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css')); + const nonce = this.getNonce(); return ` @@ -324,7 +392,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { - Front Matter diff --git a/webpack.config.js b/webpack.config.js index 966ec999..9dbe39bd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -49,13 +49,15 @@ module.exports = [ extensions: ['.ts', '.js', '.tsx', '.jsx'] }, module: { - rules: [{ - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - use: [{ - loader: 'ts-loader' - }] - }] + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [{ + loader: 'ts-loader' + }] + } + ] }, performance: { maxEntrypointSize: 400000,