diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5d875945 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [estruyf] +custom: ["https://www.buymeacoffee.com/zMeFRy9"] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..6471eb9e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release +on: + release: + types: + - published + workflow_dispatch: + +jobs: + build: + name: "Build and release" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + + - name: Install the dependencies + run: npm i + + - name: Install vsce + run: npm i -g vsce + + - name: Publish + run: vsce publish -p ${{ secrets.VSCE_PAT }} + diff --git a/.vscode/launch.json b/.vscode/launch.json index a47e93c5..ae2fc84b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,6 +18,18 @@ ], "preLaunchTask": "npm: build:ext" }, + { + "name": "Attach Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ] + }, { "name": "Extension Tests", "type": "extensionHost", diff --git a/CHANGELOG.md b/CHANGELOG.md index bd170853..9213bbc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Change Log +## [1.19.0] - 2020-07-20 + +- Sidebar background styling match the VSCode defined sidebar color + +## [1.18.0] - 2020-07-20 + +- Updated README + +## [1.17.1] - 2020-06-28 + +- [#34](https://github.com/estruyf/vscode-front-matter/issues/34): Fix that last modification date does not update the publication date +- [#38](https://github.com/estruyf/vscode-front-matter/issues/38): Update the last modification date on new page creation from the template + +## [1.17.0] - 2020-06-14 + +- [#36](https://github.com/estruyf/vscode-front-matter/issues/36): Add the option to change the Front Matter its description field + +## [1.16.1] - 2020-05-27 + +- Fix for Node.js v14.16.0 + +## [1.16.0] - 2020-05-04 + +- Add all front matter properties as an argument for custom scripts + +## [1.15.1] - 2020-05-04 + +- Add the ability to specify a custom Node path + +## [1.15.0] - 2020-05-04 + +- Added the ability to add your own custom scripts as panel actions. + +## [1.14.0] - 2020-03-19 + +- New links added to the Front Matter panel to reveal the file and folder. + +## [1.13.0] - 2020-02-26 + +- Implemented links to quickly open the extension settings + issue from the FrontMatter view panel +- Added button to update the modified time in the FrontMatter view panel + +## [1.12.1] - 2020-02-13 + +- Fix Front Matter SVG + +## [1.12.0] - 2020-01-26 + +- [#29](https://github.com/estruyf/vscode-front-matter/issues/29): Open file after creating it from the template + ## [1.11.1] - 2020-12-10 - [#26](https://github.com/estruyf/vscode-front-matter/issues/26): Fix for arrow selection in the dropdown. diff --git a/README.md b/README.md index 8ea99ecb..dc3a9a57 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,24 @@ -[![Version](https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter) -   -[![Installs](https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter) -   -[![Rating](https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details) +

+ + Front Matter + +

+ +

Front Matter is an essential Visual Studio Code extension when you want to manage the markdown pages of your static sites.

+ +

+ + Visual Studio Marketplace + + + Number of installs + + Ratings + + + Buy me a coffee + +

This VSCode extension simplifies working with your markdown articles' front matter when using a static site generator like Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more... For example, you can keep a list of used tags, categories and add/remove them from your article with the extension. @@ -22,6 +38,32 @@ To leverage most of the capabilities of the extension. SEO information and every > **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`. +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) + +The current workspace-, file-path, and front matter data will be passed as an argument. In your script fetch these arguments as follows: + +```javascript +const arguments = process.argv; + +if (arguments && arguments.length > 0) { + const workspaceArg = arguments[2]; // The workspace path + const fileArg = arguments[3]; // The file path + const frontMatterArg = arguments[4]; // Front matter data + + console.log(`The content returned for your notification.`); +} +``` + +> A sample file can be found here: [script-sample.js](./sample/script-sample.js) + +The output of the script will be passed as a notification, and it allows you to copy the output. + +![](./assets/custom-action-notification.png) + ## Creating articles from templates By default, the extension looks for files stored in a `.templates` folder that should be located in your website project's root. @@ -119,6 +161,15 @@ Specifies the optimal description length for SEO (set to `-1` to turn it off). D "frontMatter.taxonomy.seoDescriptionLength": 160 } ``` +### `frontMatter.taxonomy.seoDescriptionLength` + +Specifies the name of the SEO description field for your page. Default is `description`. + +```json +{ + "frontMatter.taxonomy.seoDescriptionField": "description" +} +``` ### `frontMatter.taxonomy.frontMatterType` @@ -170,6 +221,22 @@ Specifies the modified date field name to use in your Front Matter. Default valu } ``` +### `frontMatter.custom.scripts` + +Allows you to specify a title and script path (starting relative from the root of your project). These values will be used to create custom actions on the Front Matter panel. Default value: `[]`. + +```json +{ + "frontMatter.custom.scripts": [{ + "title": "Generate social image", + "script": "./scripts/social-img.js", + "nodeBin": "~/.nvm/versions/node/v14.15.5/bin/node" + }] +} +``` + +> **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: @@ -180,3 +247,9 @@ Specifies the modified date field name to use in your Front Matter. Default valu ## Feedback / issues / ideas Please submit them via creating an issue in the project repository: [issue list](https://github.com/estruyf/vscode-front-matter/issues). + +

+ + + +

\ No newline at end of file diff --git a/assets/custom-action-notification.png b/assets/custom-action-notification.png new file mode 100644 index 00000000..65b6a193 Binary files /dev/null and b/assets/custom-action-notification.png differ diff --git a/assets/custom-actions.png b/assets/custom-actions.png new file mode 100644 index 00000000..d2594b88 Binary files /dev/null and b/assets/custom-actions.png differ diff --git a/assets/frontmatter-panel-old.png b/assets/frontmatter-panel-old.png new file mode 100644 index 00000000..2d4a4ffb Binary files /dev/null and b/assets/frontmatter-panel-old.png differ diff --git a/assets/frontmatter-panel.png b/assets/frontmatter-panel.png index 2d4a4ffb..b0f2f068 100644 Binary files a/assets/frontmatter-panel.png and b/assets/frontmatter-panel.png differ diff --git a/assets/frontmatter.svg b/assets/frontmatter.svg index 6b8187cc..3c56d871 100644 --- a/assets/frontmatter.svg +++ b/assets/frontmatter.svg @@ -1,17 +1,39 @@ - + -FRONT - -MATTER - + + + + + + + + + + + + + + + + + diff --git a/assets/media/styles.css b/assets/media/styles.css index df2a9242..736dd20e 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -21,6 +21,10 @@ } } +#app, .frontmatter { + height: 100%; +} + .spinner, .spinner:before, .spinner:after { @@ -58,6 +62,12 @@ left: 3.5em; } +.frontmatter { + padding: var(--input-margin-vertical) 0; + display: flex; + flex-direction: column; + justify-content: space-between; +} .frontmatter h3 { margin-bottom: 1rem; @@ -69,6 +79,16 @@ margin-bottom: .5rem; } +.article__tags h3, +.seo__status h3 { + display: flex; + align-items: center; +} + +.section h3 i { + margin-right: 0.5rem; +} + .seo__status__details { margin-bottom: 2rem; } @@ -154,22 +174,28 @@ } .article__tags__items__item { - display: inline-block; + display: inline-flex; margin-bottom: .5rem; margin-right: .5rem; } -.article__tags__items__item_add, -.article__tags__items__item_delete { - display: inline-block; +.article__tags__items__item_add { + display: inline-flex; + align-items: center; width: auto; } -.article__tags__items__item svg { - display: inline; - vertical-align: bottom; +.article__tags__items__item_delete { + display: inline-flex; + align-items: center; + width: auto; } +.article__tags__items__item_delete i { + margin-left: 0.5rem; +} + + .article__tags__items__item_delete span { margin-left: .5rem; } @@ -199,4 +225,30 @@ border-right: 1px solid var(--vscode-inputValidation-infoBorder); filter: contrast(60%); +} + +.ext_settings > * + * { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.ext_link_block { + display: flex; + align-items: center; +} + +.ext_link_block i { + margin-right: .5rem; +} + +.ext_link_block a { + color: var(--vscode-textLink-foreground); +} + +.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); } \ No newline at end of file diff --git a/assets/media/vscode.css b/assets/media/vscode.css index cb0a647b..8c491f11 100644 --- a/assets/media/vscode.css +++ b/assets/media/vscode.css @@ -6,13 +6,17 @@ --input-margin-horizontal: 0; } +html, body { + height: 100%; +} + body { padding: 0 var(--container-paddding); color: var(--vscode-foreground); font-size: var(--vscode-font-size); font-weight: var(--vscode-font-weight); font-family: var(--vscode-font-family); - background-color: var(--vscode-editor-background); + background-color: var(--vscode-sideBar-background); } ol, @@ -20,12 +24,6 @@ ul { padding-left: var(--container-paddding); } -body > *, -form > * { - margin-block-start: var(--input-margin-vertical); - margin-block-end: var(--input-margin-vertical); -} - *:focus { outline-color: var(--vscode-focusBorder) !important; } diff --git a/package-lock.json b/package-lock.json index 6ffd4b1d..bd0eda2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-front-matter", - "version": "1.11.0", + "version": "1.18.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -39,6 +39,15 @@ "regenerator-runtime": "^0.13.4" } }, + "@bendera/vscode-webview-elements": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@bendera/vscode-webview-elements/-/vscode-webview-elements-0.5.0.tgz", + "integrity": "sha512-mlZi8RG+tsqr1bDbA7H82spyWzZyj/tsyHb9eta7kE0xRhvx7ON6w6DG4PONew1rVJv1knTKOAW4iQKVBYQhVQ==", + "dev": true, + "requires": { + "lit-element": "^2.5.1" + } + }, "@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -52,23 +61,36 @@ "dev": true }, "@material-ui/core": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.1.tgz", - "integrity": "sha512-aesI8lOaaw0DRIfNG+Anepf61NH5Q+cmkxJOZvI1oHkmD5cKubkZ0C7INqFKjfFpSFlFnqHkTasoM7ogFAvzOg==", + "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.1", - "@material-ui/system": "^4.9.14", - "@material-ui/types": "^5.1.0", - "@material-ui/utils": "^4.10.2", + "@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", + "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": { @@ -81,66 +103,88 @@ } }, "@material-ui/lab": { - "version": "4.0.0-alpha.56", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz", - "integrity": "sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw==", + "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.10.2", + "@material-ui/utils": "^4.11.2", "clsx": "^1.0.4", "prop-types": "^15.7.2", - "react-is": "^16.8.0" + "react-is": "^16.8.0 || ^17.0.0" } }, "@material-ui/styles": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.1.tgz", - "integrity": "sha512-GqzsFsVWT8jXa8OWAd1WD6WIaqtXr2mUPbRZ1EjkiM3Dlta4mCRaToDxkFVv6ZHfXlFjMJzdaIEvCpZOCvZTvg==", + "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.9.6", + "@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.0.3", - "jss-plugin-camel-case": "^10.0.3", - "jss-plugin-default-unit": "^10.0.3", - "jss-plugin-global": "^10.0.3", - "jss-plugin-nested": "^10.0.3", - "jss-plugin-props-sort": "^10.0.3", - "jss-plugin-rule-value-function": "^10.0.3", - "jss-plugin-vendor-prefixer": "^10.0.3", + "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.14", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz", - "integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==", + "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.9.14", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz", - "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==", + "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.9.6", + "@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.14", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz", - "integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==", + "version": "2.6.17", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", + "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", "dev": true } } @@ -152,14 +196,14 @@ "dev": true }, "@material-ui/utils": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz", - "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==", + "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" + "react-is": "^16.8.0 || ^17.0.0" } }, "@types/anymatch": { @@ -240,9 +284,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", - "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", + "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": "*" @@ -324,6 +368,12 @@ } } }, + "@vscode/codicons": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.20.tgz", + "integrity": "sha512-LlO6K7nzrIWDCZN1Zi6J6ibxrpMibSAct+zNjAwpkNkwup6cJLx5diYvsOJODMPWOuQlBO21qkxtdkSRzW6+Jw==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -1427,9 +1477,9 @@ } }, "dom-helpers": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", - "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", + "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", @@ -1520,24 +1570,24 @@ } }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -2510,15 +2560,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indefinite-observable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz", - "integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==", - "dev": true, - "requires": { - "symbol-observable": "1.2.0" - } - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -2542,9 +2583,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "interpret": { @@ -2797,90 +2838,89 @@ } }, "jss": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.5.0.tgz", - "integrity": "sha512-B6151NvG+thUg3murLNHRPLxTLwQ13ep4SH5brj4d8qKtogOx/jupnpfkPGSHPqvcwKJaCLctpj2lEk+5yGwMw==", + "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", - "indefinite-observable": "^2.0.1", "is-in-browser": "^1.1.3", "tiny-warning": "^1.0.2" } }, "jss-plugin-camel-case": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.0.tgz", - "integrity": "sha512-GSjPL0adGAkuoqeYiXTgO7PlIrmjv5v8lA6TTBdfxbNYpxADOdGKJgIEkffhlyuIZHlPuuiFYTwUreLUmSn7rg==", + "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.5.0" + "jss": "10.7.1" } }, "jss-plugin-default-unit": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.0.tgz", - "integrity": "sha512-rsbTtZGCMrbcb9beiDd+TwL991NGmsAgVYH0hATrYJtue9e+LH/Gn4yFD1ENwE+3JzF3A+rPnM2JuD9L/SIIWw==", + "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.5.0" + "jss": "10.7.1" } }, "jss-plugin-global": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.5.0.tgz", - "integrity": "sha512-FZd9+JE/3D7HMefEG54fEC0XiQ9rhGtDHAT/ols24y8sKQ1D5KIw6OyXEmIdKFmACgxZV2ARQ5pAUypxkk2IFQ==", + "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.5.0" + "jss": "10.7.1" } }, "jss-plugin-nested": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.5.0.tgz", - "integrity": "sha512-ejPlCLNlEGgx8jmMiDk/zarsCZk+DV0YqXfddpgzbO9Toamo0HweCFuwJ3ZO40UFOfqKwfpKMVH/3HUXgxkTMg==", + "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.5.0", + "jss": "10.7.1", "tiny-warning": "^1.0.2" } }, "jss-plugin-props-sort": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.0.tgz", - "integrity": "sha512-kTLRvrOetFKz5vM88FAhLNeJIxfjhCepnvq65G7xsAQ/Wgy7HwO1BS/2wE5mx8iLaAWC6Rj5h16mhMk9sKdZxg==", + "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.5.0" + "jss": "10.7.1" } }, "jss-plugin-rule-value-function": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.0.tgz", - "integrity": "sha512-jXINGr8BSsB13JVuK274oEtk0LoooYSJqTBCGeBu2cG/VJ3+4FPs1gwLgsq24xTgKshtZ+WEQMVL34OprLidRA==", + "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.5.0", + "jss": "10.7.1", "tiny-warning": "^1.0.2" } }, "jss-plugin-vendor-prefixer": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.0.tgz", - "integrity": "sha512-rux3gmfwDdOKCLDx0IQjTwTm03IfBa+Rm/hs747cOw5Q7O3RaTUIMPKjtVfc31Xr/XI9Abz2XEupk1/oMQ7zRA==", + "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.5.0" + "jss": "10.7.1" } }, "kind-of": { @@ -2889,6 +2929,21 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "lit-element": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz", + "integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==", + "dev": true, + "requires": { + "lit-html": "^1.1.1" + } + }, + "lit-html": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", + "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==", + "dev": true + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -2917,9 +2972,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "loose-envify": { @@ -3507,14 +3562,6 @@ "requires": { "lodash": "^4.17.20", "renderkid": "^2.0.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "process": { @@ -3672,9 +3719,9 @@ "dev": true }, "react-transition-group": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", - "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "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", @@ -3777,12 +3824,6 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -4227,9 +4268,9 @@ "dev": true }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -4310,12 +4351,6 @@ "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4993,6 +5028,12 @@ } } }, + "wc-react": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wc-react/-/wc-react-0.5.0.tgz", + "integrity": "sha512-3c4KzBArlfCqCYqY8hoJAIvxmzpWvoQqsOmy9OiwbnopSTE270i3R+LVD7NjE00UJkjXq7kmhobnx0qhi/eOrA==", + "dev": true + }, "webpack": { "version": "4.44.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", @@ -5317,9 +5358,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { diff --git a/package.json b/package.json index 3118029b..d3b5f7b4 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.11.0", + "version": "1.18.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -15,6 +15,11 @@ "description": "version", "url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square", "href": "https://github.com/estruyf/vscode-front-matter" + }, + { + "description": "Buy me a coffee", + "url": "https://img.shields.io/badge/Buy%20me%20a%20coffee-€%203-blue?logo=buy-me-a-coffee&style=flat-square", + "href": "https://www.buymeacoffee.com/zMeFRy9" } ], "engines": { @@ -137,6 +142,11 @@ "default": 160, "description": "Specifies the optimal description length for SEO (set to `-1` to turn it off)." }, + "frontMatter.taxonomy.seoDescriptionField": { + "type": "string", + "default": "description", + "description": "Specifies the name of the SEO description field for your page. Default is 'description'." + }, "frontMatter.templates.folder": { "type": "string", "default": ".templates", @@ -151,6 +161,11 @@ "type": "boolean", "default": true, "markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true." + }, + "frontMatter.custom.scripts": { + "type": "array", + "default": [], + "markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument." } } }, @@ -223,10 +238,11 @@ "clean": "rm -rf dist" }, "devDependencies": { + "@bendera/vscode-webview-elements": "0.5.0", "@iarna/toml": "2.2.3", - "@material-ui/core": "4.11.1", + "@material-ui/core": "4.12.2", "@material-ui/icons": "4.11.2", - "@material-ui/lab": "4.0.0-alpha.56", + "@material-ui/lab": "4.0.0-alpha.60", "@types/glob": "7.1.3", "@types/js-yaml": "3.12.1", "@types/mocha": "^5.2.6", @@ -234,7 +250,9 @@ "@types/react": "17.0.0", "@types/react-dom": "17.0.0", "@types/vscode": "1.51.0", + "@vscode/codicons": "0.0.20", "date-fns": "2.0.1", + "downshift": "6.0.6", "glob": "7.1.6", "gray-matter": "4.0.2", "html-loader": "1.3.2", @@ -244,9 +262,9 @@ "ts-loader": "8.0.3", "tslint": "6.1.3", "typescript": "4.0.2", + "wc-react": "0.5.0", "webpack": "4.44.2", - "webpack-cli": "3.3.12", - "downshift": "6.0.6" + "webpack-cli": "3.3.12" }, "dependencies": {} } diff --git a/sample/script-sample.js b/sample/script-sample.js new file mode 100644 index 00000000..e950e688 --- /dev/null +++ b/sample/script-sample.js @@ -0,0 +1,10 @@ + +const arguments = process.argv; + +if (arguments && arguments.length > 0) { + const workspaceArg = arguments[2]; // The workspace path + const fileArg = arguments[3]; // The file path + const frontMatterArg = arguments[4]; // Front matter data + + console.log(`The content returned for your notification.`); +} \ No newline at end of file diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 2885cbf8..0da99c86 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -101,14 +101,11 @@ export class Article { const config = vscode.workspace.getConfiguration(CONFIG_KEY); const dateFormat = config.get(SETTING_DATE_FORMAT) as string; const dateField = config.get(SETTING_DATE_FIELD) as string || "date"; + const modField = config.get(SETTING_MODIFIED_FIELD) as string || "date"; + + article = this.articleDate(article, dateFormat, dateField, forceCreate); + article = this.articleDate(article, dateFormat, modField, false); - if (typeof article.data[dateField] !== "undefined" || forceCreate) { - if (dateFormat && typeof dateFormat === "string") { - article.data[dateField] = format(new Date(), dateFormat); - } else { - article.data[dateField] = new Date(); - } - } return article; } @@ -133,7 +130,7 @@ export class Article { if (dateFormat && typeof dateFormat === "string") { article.data[dateField] = format(new Date(), dateFormat); } else { - article.data[dateField] = new Date(); + article.data[dateField] = new Date().toISOString(); } ArticleHelper.update(editor, article); @@ -185,4 +182,22 @@ export class Article { article.data["draft"] = newDraftStatus; ArticleHelper.update(editor, article); } + + /** + * Update the article date and return it + * @param article + * @param dateFormat + * @param field + * @param forceCreate + */ + private static articleDate(article: matter.GrayMatterFile, dateFormat: string, field: string, forceCreate: boolean) { + if (typeof article.data[field] !== "undefined" || forceCreate) { + if (dateFormat && typeof dateFormat === "string") { + article.data[field] = format(new Date(), dateFormat); + } else { + article.data[field] = new Date().toISOString(); + } + } + return article; + } } diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index e940f7fb..77752ff5 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -1,4 +1,4 @@ -import { SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings'; +import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings'; import * as vscode from 'vscode'; import { CONFIG_KEY } from '../constants'; import { ArticleHelper, SeoHelper } from '../helpers'; @@ -41,13 +41,14 @@ export class StatusListener { const config = vscode.workspace.getConfiguration(CONFIG_KEY); const titleLength = config.get(SETTING_SEO_TITLE_LENGTH) as number || -1; const descLength = config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1; + const fieldName = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description"; if (article.data.title && titleLength > -1) { SeoHelper.checkLength(editor, collection, article, "title", titleLength); } - if (article.data.description && descLength > -1) { - SeoHelper.checkLength(editor, collection, article, "description", descLength); + if (article.data[fieldName] && descLength > -1) { + SeoHelper.checkLength(editor, collection, article, fieldName, descLength); } } diff --git a/src/commands/Template.ts b/src/commands/Template.ts index 79625df2..9945028b 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -92,6 +92,8 @@ export class Template { frontMatter = Article.updateDate(frontMatter); fs.writeFileSync(newFilePath, ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data), { encoding: "utf8" }); + + await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(newFilePath)); } const txtDoc = await vscode.workspace.openTextDocument(vscode.Uri.parse(newFilePath)); diff --git a/src/constants/settings.ts b/src/constants/settings.ts index c690f22f..2a184ba3 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -18,8 +18,11 @@ 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_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField"; export const SETTING_TEMPLATES_FOLDER = "templates.folder"; export const SETTING_TEMPLATES_PREFIX = "templates.prefix"; -export const SETTING_PANEL_FREEFORM = "panel.freeform"; \ No newline at end of file +export const SETTING_PANEL_FREEFORM = "panel.freeform"; + +export const SETTING_CUSTOM_SCRIPTS = "custom.scripts"; \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 03d6061a..4ad2ce25 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -5,14 +5,22 @@ export interface PanelSettings { tags: string[]; categories: string[]; freeform: boolean; + scripts: CustomScript[]; } export interface SEO { title: number; description: number; + descriptionField: string; } export interface Slug { prefix: number; suffix: number; +} + +export interface CustomScript { + title: string; + script: string; + nodeBin?: string; } \ No newline at end of file diff --git a/src/viewpanel/CommandToCode.ts b/src/viewpanel/CommandToCode.ts index b3e02fcf..e3b1630f 100644 --- a/src/viewpanel/CommandToCode.ts +++ b/src/viewpanel/CommandToCode.ts @@ -2,9 +2,14 @@ export enum CommandToCode { getData = "get-data", updateSlug = 'update-slug', updateDate = 'update-date', + updateLastMod = 'update-lastmod', publish = 'publish', updateTags = "update-tags", updateCategories = "update-categories", addTagToSettings = "add-tag", - addCategoryToSettings = "add-category" + addCategoryToSettings = "add-category", + openSettings = "open-settings", + openFile = "open-file", + openProject = "open-project", + runCustomScript = "custom-script" } \ No newline at end of file diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 716b780c..8bdc5cfb 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -1,8 +1,11 @@ import * as React from 'react'; +import { CommandToCode } from './CommandToCode'; import { Actions } from './components/Actions'; +import { Icon } from './components/Icon'; import { SeoStatus } from './components/SeoStatus'; import { Spinner } from './components/Spinner'; import { TagPicker } from './components/TagPicker'; +import { MessageHelper } from './helper/MessageHelper'; import useMessages from './hooks/useMessages'; import { TagType } from './TagType'; @@ -26,34 +29,73 @@ export const ViewPanel: React.FunctionComponent = (props: React ); } + const openSettings = () => { + MessageHelper.sendMessage(CommandToCode.openSettings); + }; + + const openFile = () => { + MessageHelper.sendMessage(CommandToCode.openFile); + }; + + const openProject = () => { + MessageHelper.sendMessage(CommandToCode.openProject); + }; + return (
- { - settings && settings.seo && - } - { - settings && metadata && - } - { - (settings && settings.tags && settings.tags.length > 0) && ( - - ) - } - { - (settings && settings.categories && settings.categories.length > 0) && ( - - ) - } +
+ { + settings && settings.seo && + } + { + 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} /> + ) + } +
+ +
); }; \ No newline at end of file diff --git a/src/viewpanel/components/Actions.tsx b/src/viewpanel/components/Actions.tsx index 42b67fb9..630d4894 100644 --- a/src/viewpanel/components/Actions.tsx +++ b/src/viewpanel/components/Actions.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { PanelSettings } from '../../models/PanelSettings'; +import { CustomScript } from './CustomScript'; import { DateAction } from './DateAction'; +import { Icon } from './Icon'; import { PublishAction } from './PublishAction'; import { SlugAction } from './SlugAction'; @@ -17,14 +19,22 @@ export const Actions: React.FunctionComponent = (props: React.Pro } return ( -
-

Actions

+
+

Actions

{ metadata && metadata.title && } { metadata && typeof metadata.draft !== undefined && } + + { + (settings && settings.scripts && settings.scripts.length > 0) && ( + settings.scripts.map((value) => ( + + )) + ) + }
); }; \ No newline at end of file diff --git a/src/viewpanel/components/CustomScript.tsx b/src/viewpanel/components/CustomScript.tsx new file mode 100644 index 00000000..bffa4f9d --- /dev/null +++ b/src/viewpanel/components/CustomScript.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { CommandToCode } from '../CommandToCode'; +import { MessageHelper } from '../helper/MessageHelper'; + +export interface ICustomScriptProps { + title: string; + script: string; +} + +export const CustomScript: React.FunctionComponent = ({title, script}: React.PropsWithChildren) => { + + const runCustomScript = () => { + MessageHelper.sendMessage(CommandToCode.runCustomScript, { title, script }); + }; + + return ( +
+ +
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/DateAction.tsx b/src/viewpanel/components/DateAction.tsx index d18a94d0..27c49aeb 100644 --- a/src/viewpanel/components/DateAction.tsx +++ b/src/viewpanel/components/DateAction.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../helper/MessageHelper'; @@ -11,10 +9,19 @@ export const DateAction: React.FunctionComponent = (props: Rea const setDate = () => { MessageHelper.sendMessage(CommandToCode.updateDate); }; + + const setLastMod = () => { + MessageHelper.sendMessage(CommandToCode.updateLastMod); + }; return ( -
- -
+ <> +
+ +
+
+ +
+ ); }; \ No newline at end of file diff --git a/src/viewpanel/components/Icon.tsx b/src/viewpanel/components/Icon.tsx new file mode 100644 index 00000000..81848dd3 --- /dev/null +++ b/src/viewpanel/components/Icon.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; + +export interface IIconProps { + name: string; +} + +export const Icon: React.FunctionComponent = ({ name }: React.PropsWithChildren) => { + + return (); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoStatus.tsx b/src/viewpanel/components/SeoStatus.tsx index 466683bc..448e2d59 100644 --- a/src/viewpanel/components/SeoStatus.tsx +++ b/src/viewpanel/components/SeoStatus.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { SEO } from '../../models/PanelSettings'; +import { Icon } from './Icon'; import { SeoDetails } from './SeoDetails'; export interface ISeoStatusProps { @@ -9,17 +10,21 @@ export interface ISeoStatusProps { export const SeoStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { const { data, seo } = props; - const { title, description } = data; + const { title } = data; - if (!title && !description) { + const { descriptionField } = seo; + + if (!title && !data[descriptionField]) { return null; } return ( -
-

SEO Status

+
+

+ SEO Status +

{ (title && seo.title > 0) && } - { (description && seo.description > 0) && } + { (data[descriptionField] && seo.description > 0) && }
); }; \ No newline at end of file diff --git a/src/viewpanel/components/Tag.tsx b/src/viewpanel/components/Tag.tsx index 180aefe2..2f1b88c3 100644 --- a/src/viewpanel/components/Tag.tsx +++ b/src/viewpanel/components/Tag.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import AddIcon from '@material-ui/icons/Add'; -import DeleteForeverTwoToneIcon from '@material-ui/icons/DeleteForeverTwoTone'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { Icon } from './Icon'; export interface ITagProps { className: string; @@ -18,9 +19,10 @@ export const Tag: React.FunctionComponent = (props: React.PropsWithCh
{ onCreate && - + } - + +
); }; \ No newline at end of file diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index c6a4f58c..804f8ade 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -9,6 +9,7 @@ import LocalOfferTwoToneIcon from '@material-ui/icons/LocalOfferTwoTone'; export interface ITagPickerProps { type: string; + icon: JSX.Element; crntSelected: string[]; options: string[]; freeform: boolean; @@ -17,7 +18,7 @@ export interface ITagPickerProps { } export const TagPicker: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { type, crntSelected, options, freeform, focussed, unsetFocus } = props; + const { icon, type, crntSelected, options, freeform, focussed, unsetFocus } = props; const [ selected, setSelected ] = React.useState([]); const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); @@ -115,8 +116,8 @@ export const TagPicker: React.FunctionComponent = (props: React }, [crntSelected]); return ( -
-

{type}

+
+

{icon} {type}

onSelect(selected || "")} diff --git a/src/viewpanel/components/VscodeComponents.ts b/src/viewpanel/components/VscodeComponents.ts new file mode 100644 index 00000000..81a3b561 --- /dev/null +++ b/src/viewpanel/components/VscodeComponents.ts @@ -0,0 +1,3 @@ +import {wrapWc} from 'wc-react'; + +export const VscodeIcon = wrapWc(`vscode-icon`); \ No newline at end of file diff --git a/src/viewpanel/index.tsx b/src/viewpanel/index.tsx index 527cdbb7..4d5d7504 100644 --- a/src/viewpanel/index.tsx +++ b/src/viewpanel/index.tsx @@ -2,6 +2,8 @@ import * as React from "react"; import { render } from "react-dom"; import { ViewPanel } from "./ViewPanel"; +import '@bendera/vscode-webview-elements/dist/vscode-icon'; + declare const acquireVsCodeApi: () => { getState: () => T; setState: (data: T) => void; @@ -9,4 +11,4 @@ declare const acquireVsCodeApi: () => { }; const elm = document.querySelector("#app"); -render(, elm); \ No newline at end of file +render(, elm); diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index fec3ed65..c3856f3c 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -1,5 +1,7 @@ -import { PanelSettings } from './../models/PanelSettings'; -import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace } from "vscode"; +import { SETTING_CUSTOM_SCRIPTS, 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"; import { CONFIG_KEY, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants"; import { ArticleHelper, SettingsHelper } from "../helpers"; import { Command } from "../viewpanel/Command"; @@ -7,6 +9,8 @@ import { CommandToCode } from '../viewpanel/CommandToCode'; import { Article } from '../commands'; import { TagType } from '../viewpanel/TagType'; import { TaxonomyType } from '../models'; +import { exec } from 'child_process'; +import * as path from 'path'; export class ExplorerView implements WebviewViewProvider, Disposable { @@ -54,8 +58,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { */ public async resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Promise { - console.log(context); - this.panel = webviewView; webviewView.webview.options = { @@ -82,6 +84,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.updateDate: Article.setDate(); break; + case CommandToCode.updateLastMod: + Article.setLastModifiedDate(); + break; case CommandToCode.publish: Article.toggleDraft(); break; @@ -97,6 +102,28 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.addCategoryToSettings: this.addTags(TagType.categories, msg.data); break; + case CommandToCode.openSettings: + commands.executeCommand('workbench.action.openSettings', '@ext:eliostruyf.vscode-front-matter'); + break; + case CommandToCode.openFile: + commands.executeCommand('revealFileInOS'); + break; + case CommandToCode.runCustomScript: + this.runCustomScript(msg); + break; + case CommandToCode.openProject: + const wsFolders = workspace.workspaceFolders; + if (wsFolders && wsFolders.length > 0) { + const wsPath = wsFolders[0].uri.fsPath; + if (os.type() === "Darwin") { + exec(`open ${wsPath}`); + } else if (os.type() === "Windows_NT") { + exec(`explorer ${wsPath}`); + } else { + exec(`xdg-open ${wsPath}`); + } + } + break; } }); @@ -138,6 +165,48 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } + /** + * Run a custom script + * @param msg + */ + private runCustomScript(msg: { command: string, data: any}) { + const config = workspace.getConfiguration(CONFIG_KEY); + const scripts: CustomScript[] | undefined = config.get(SETTING_CUSTOM_SCRIPTS); + + if (msg?.data?.title && msg?.data?.script && scripts) { + const customScript = scripts.find((s: CustomScript) => s.title === msg.data.title); + if (customScript?.script && customScript?.title) { + const editor = window.activeTextEditor; + if (!editor) return; + + const article = ArticleHelper.getFrontMatter(editor); + + const wsFolders = workspace.workspaceFolders; + if (wsFolders && wsFolders.length > 0) { + const wsPath = wsFolders[0].uri.fsPath; + + let articleData = `'${JSON.stringify(article?.data)}'`; + if (os.type() === "Windows_NT") { + articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`; + } + + exec(`${customScript.nodeBin || "node"} ${path.join(wsPath, msg.data.script)} "${wsPath}" "${editor?.document.uri.fsPath}" ${articleData}`, (error, stdout) => { + if (error) { + window.showErrorMessage(`${msg?.data?.title}: ${error.message}`); + return; + } + + window.showInformationMessage(`${msg?.data?.title}: ${stdout || "Executed your custom script."}`, 'Copy output').then(value => { + if (value === 'Copy output') { + vscodeEnv.clipboard.writeText(stdout); + } + }); + }); + } + } + } + } + /** * Retrieve the extension settings */ @@ -149,7 +218,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { data: { seo: { title: config.get(SETTING_SEO_TITLE_LENGTH) as number || -1, - description: config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1 + description: config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1, + descriptionField: config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description" }, slug: { prefix: config.get(SETTING_SLUG_PREFIX) || "", @@ -157,7 +227,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { }, tags: config.get(SETTING_TAXONOMY_TAGS) || [], categories: config.get(SETTING_TAXONOMY_CATEGORIES) || [], - freeform: config.get(SETTING_PANEL_FREEFORM) + freeform: config.get(SETTING_PANEL_FREEFORM), + scripts: config.get(SETTING_CUSTOM_SCRIPTS) } as PanelSettings }); } @@ -241,6 +312,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 ` @@ -252,8 +324,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { + - FrontMatter + Front Matter