Compare commits

...

44 Commits

Author SHA1 Message Date
Elio Struyf
fdfdfda6ce 2.4.1 2021-08-16 14:30:18 +02:00
Elio Struyf
f016ae27ec Better highlight 2021-08-16 14:30:10 +02:00
Elio Struyf
c9d3eca431 Merge branch 'main' of github.com:estruyf/vscode-front-matter into main 2021-08-16 12:30:29 +02:00
Elio Struyf
486beaf650 2.4.0 2021-08-16 12:30:17 +02:00
Elio Struyf
6e82cf221e Updated changelog 2021-08-16 12:30:10 +02:00
Elio Struyf
46b9591859 Merge pull request #54 from estruyf/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-08-16 12:29:40 +02:00
Elio Struyf
3e76da58f5 Updated documentation 2021-08-16 12:28:55 +02:00
Elio Struyf
9d42bd2f97 Update highlighting + documentation 2021-08-16 11:38:51 +02:00
Elio Struyf
75890ec6e8 #55 #56 #57 #58 #59 - multiple enhancements that are linked 2021-08-13 13:33:05 +02:00
Elio Struyf
fb0429e40f Updated changelog 2021-08-12 17:24:17 +02:00
Elio Struyf
8db813a661 #21 - Implemented the folding provider for front matter 2021-08-12 17:15:08 +02:00
dependabot[bot]
2655be9aae Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-11 09:39:10 +00:00
Elio Struyf
fc99756136 Merge branch 'main' of github.com:estruyf/vscode-front-matter into main 2021-08-10 20:09:53 +02:00
Elio Struyf
9159a98dbe Fix image URL 2021-08-10 20:09:46 +02:00
Elio
310f8e4a5e 2.3.0 2021-08-10 15:10:54 +02:00
Elio
d79f3416a3 updated changelog 2021-08-10 15:10:49 +02:00
Elio Struyf
e194928291 #53 - Add save as template 2021-08-09 17:05:40 +02:00
Elio Struyf
32aa8f4223 #31 - Better change detection 2021-08-09 14:46:30 +02:00
Elio Struyf
c88d6be1d7 #31 - implementation of auto-update 2021-08-09 10:47:13 +02:00
Elio Struyf
1b1dc55da7 2.2.0 2021-08-06 14:44:08 +02:00
Elio Struyf
bfbc81c90f Updated release date 2021-08-06 14:44:02 +02:00
Elio Struyf
cefbf74582 #51 - Default panel actions 2021-08-06 14:42:20 +02:00
Elio Struyf
0780842365 Updates for baseview 2021-08-06 13:38:17 +02:00
Elio Struyf
1f94a87993 #28 - Align filename with the slug 2021-08-06 13:35:45 +02:00
Elio Struyf
157228edb5 #49 - Initialize new project 2021-08-05 22:53:46 +02:00
Elio Struyf
10268fc60f #48 - Added new folder registration message 2021-08-05 18:14:12 +02:00
Elio Struyf
e51911ed83 #50 - Fix for 2021-08-05 18:13:07 +02:00
Elio Struyf
e1429bc666 #47 - Fix showing 0 on word count 2021-08-05 16:09:56 +02:00
Elio
a2d6d361d6 Update docs 2021-08-04 15:18:45 +02:00
Elio
c581ead809 Added docs 2021-08-04 15:17:23 +02:00
Elio
3ed5fda4e7 Merge branch 'dev' into main 2021-08-04 15:05:40 +02:00
Elio
33dcfcb09a 2.1.0 2021-08-04 15:02:10 +02:00
Elio
df84c25e01 #44 - Windows path fix 2021-08-04 15:00:26 +02:00
Elio
b8dc7990f7 #45 - WSL support added 2021-08-04 14:38:57 +02:00
Elio
a1ee808ed5 #44 - New article creation command 2021-08-03 14:16:35 +02:00
Elio Struyf
3a35eeb1d5 enhancement preperations 2021-08-03 11:04:43 +02:00
Elio Struyf
c6412760fc #46 - Fix rendering of tag pickers 2021-08-03 10:45:01 +02:00
Elio Struyf
cc21043053 2.0.1 2021-07-27 15:57:16 +02:00
Elio Struyf
b1816a0567 updated changelog 2021-07-27 15:57:04 +02:00
Elio Struyf
29b170a8bd Updated screenshot 2021-07-27 15:17:57 +02:00
Elio Struyf
3ed144f003 #42 - Table updates 2021-07-27 15:16:10 +02:00
Elio Struyf
613d7f2adb Update changelog 2021-07-27 15:08:07 +02:00
Elio Struyf
82260d7030 #43 - Fix for collapsible sections 2021-07-27 15:06:55 +02:00
Elio Struyf
dbd8b1c0ce Fix for onKeyDown enter 2021-07-24 18:08:09 +02:00
60 changed files with 1433 additions and 211 deletions

View File

@@ -1,5 +1,45 @@
# Change Log
## [2.4.1] - 2020-08-16
- Better editor highlighting functionality
## [2.4.0] - 2020-08-16
- [#21](https://github.com/estruyf/vscode-front-matter/issues/21): Folding provider for Front Matter implemented
- [#55](https://github.com/estruyf/vscode-front-matter/issues/55): Highlight Front Matter in Markdown files
- [#56](https://github.com/estruyf/vscode-front-matter/issues/56): Action to collapse all Front Matter panel sections at once
- [#57](https://github.com/estruyf/vscode-front-matter/issues/57): New action added to provide better writing settings (only for Markdown files)
- [#58](https://github.com/estruyf/vscode-front-matter/issues/58): Sections remember their previous state (folded/unfolded)
- [#59](https://github.com/estruyf/vscode-front-matter/issues/59): Center layout view toggle action added
## [2.3.0] - 2020-08-10
- Refactoring and showing other actions in the base view
- Show `BaseView` in Front Matter panel when switching to `welcome` tab
- [#31](https://github.com/estruyf/vscode-front-matter/issues/31): Automatically update the last modification date of the file when performing changes
- [#53](https://github.com/estruyf/vscode-front-matter/issues/53): Create current Markdown file as template
## [2.2.0] - 2020-08-06
- [#28](https://github.com/estruyf/vscode-front-matter/issues/28): Align the file its name with the article slug
- [#47](https://github.com/estruyf/vscode-front-matter/issues/47): Fix when table shows only value `0`
- [#48](https://github.com/estruyf/vscode-front-matter/issues/48): Added new folder registration message + notification helper
- [#49](https://github.com/estruyf/vscode-front-matter/issues/49): New initialize project command
- [#50](https://github.com/estruyf/vscode-front-matter/issues/50): Fix in the table rendering of rows
- [#51](https://github.com/estruyf/vscode-front-matter/issues/51): Panel actions base view enhanced to show project actions and information
## [2.1.0] - 2020-08-04
- [#44](https://github.com/estruyf/vscode-front-matter/issues/45): Added article creation command
- [#45](https://github.com/estruyf/vscode-front-matter/issues/45): WSL support added
- [#46](https://github.com/estruyf/vscode-front-matter/issues/46): Make the tag pickers render in full width
## [2.0.1] - 2020-07-27
- [#42](https://github.com/estruyf/vscode-front-matter/issues/42): Small enhancement to the table layout
- [#43](https://github.com/estruyf/vscode-front-matter/issues/43): Fix for collapsible sections and taxonomy picker
## [2.0.0] - 2020-07-23
- Redesigned sidebar panel

109
README.md
View File

@@ -35,6 +35,7 @@ In version v2.0.0 we released the newly redesigned sidebar panel with improved S
<details open="open">
<summary>Table of Contents</summary>
<ol>
<li><a href="#markdown-features">Markdown features</a></li>
<li><a href="#the-panel">The panel</a></li>
<li><a href="#custom-actions">Custom actions</a></li>
<li><a href="#creating-articles-from-templates">Create articles from templates</a></li>
@@ -45,6 +46,24 @@ In version v2.0.0 we released the newly redesigned sidebar panel with improved S
</ol>
</details>
## Markdown features
The Front Matter extension tries to make it easy to manage your Markdown pages/content. Within a Markdown page, we allow you to fold the file's Front Matter to be less distracting when writing. Also, do we highlight the Front Matter content to create a visual difference between content and metadata.
### Front Matter folding
<p align="center">
<img src="./assets/v2.4.0/folding.png" alt="Front Matter folding" style="display: inline-block" />
</p>
### Front Matter highlighting
<p align="center">
<img src="./assets/v2.4.0/fm-highlight.png" alt="Front Matter highlighting" style="display: inline-block" />
</p>
> **Info**: If you do not want this feature, you can disable it in the extension settings -> `Highlight Front Matter` or by setting the `frontMatter.content.fmHighlight` setting to `false`.
## 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.
@@ -53,7 +72,21 @@ Initially, this panel has been created to make it easier to add tags and categor
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:
When you open the panel and the current file is not a Markdown file, it will contain the following sections:
<p align="center">
<img src="./assets/v2.4.0/baseview.png" alt="Base view" style="display: inline-block" />
</p>
> **Info**: both **Global Settings** and **Other Actions** sections are shown for the base view as when a Markdown file is openend.
When you open the Front Matter panel on a Markdown file, you get to see the following sections:
**Global Settings**
<p align="center">
<img src="./assets/v2.4.0/global-settings.png" alt="Global settings" style="display: inline-block" />
</p>
**SEO Status**
@@ -64,7 +97,7 @@ The panel consists of the following sections:
**Actions**
<p align="center">
<img src="./assets/v2.0.0/actions.png" alt="Actions" style="display: inline-block" />
<img src="./assets/v2.4.0/actions.png" alt="Actions" style="display: inline-block" />
</p>
**Metadata: Keywords, Tags, Categories**
@@ -77,12 +110,12 @@ The panel consists of the following sections:
**Other actions**
At the bottom of the panel you can find the following actions:
<p align="center">
<img src="./assets/v2.0.0/other-actions.png" alt="Other actions" style="display: inline-block" />
<img src="./assets/v2.4.0/other-actions.png" alt="Other actions" style="display: inline-block" />
</p>
**Info**: The `Enable write settings` action allow you to make Markdown specific changes to optimize the writing of your articles. It will change settings like the `fontSize`, `lineHeight`, `wordWrap`, `lineNumbers` and more.
## 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.
@@ -131,7 +164,31 @@ When adding files in the folder, you'll be able to run the `Front Matter: New ar
<img src="./assets/syntax-highlighting.png" alt="Shortcode syntax highlighting" style="display: inline-block" />
</p>
## Available commands:
## Available commands
**Front Matter: Initialize project**
This command will initialize the project with a template folder and an article template. It makes it easier to get you started with the extension and creating your content.
**Front Matter: Create a template from current file**
This command allows you to create a new template from the current open Markdown file. It will ask you for the name of the template and if you want to keep the current file its content in the template.
> **Info**: The create as template action is also available from the `other actions` section in the Front Matter panel.
**Front Matter: New article from template**
With this command, you can easily create content in your project within the registered folders and provided templates.
You can register and unregister folders by right-clicking on the folder in your VSCode explorer panel.
<p align="center">
<img src="./assets/v2.1.0/register-folder.png" alt="Register/unregister a folder" style="display: inline-block" />
</p>
Once you registered a folder and a template has been defined ([how to create a template](#creating-articles-from-templates)), you can make use of this command.
> **Info**: The benefit of this command is that you do not need to search the folder in which you want to create a new article/page/... The extension will do it automatically for you.
**Front Matter: Create <tag | category>**
@@ -165,7 +222,7 @@ Update the `date` property of the current article/post/... to the current date &
**Front Matter: Set lastmod date**
Update the `lastmod` (last modified) property of the current article/post/... to the current date & time.
Update the `lastmod` (last modified) property of the current article/post/... to the current date & time. By setting the `frontMatter.content.autoUpdateDate` setting, it can be done automatically when performing changes to your markdown files.
> **note**: Uses the same date format settings key as current date: `frontMatter.taxonomy.dateFormat`.
@@ -180,7 +237,7 @@ title: Just a sample page with a title
slug: sample-page-title
```
You can also specify a prefix and suffix, which can be added to the slug if you want. Use the following settings to do this: `frontMatter.taxonomy.slugPrefix` and `frontMatter.taxonomy.slugSuffix`. By default, both options are not provided and will not add anything to the slug.
You can also specify a prefix and suffix, which can be added to the slug if you want. Use the following settings to do this: `frontMatter.taxonomy.slugPrefix` and `frontMatter.taxonomy.slugSuffix`. By default, both options are not provided and will not add anything to the slug. Another setting is to allow you to sync the filename with the generated slug. The setting you need to turn on enable for this is `frontMatter.taxonomy.alignFilename`.
> **Info**: At the moment, the extension only supports English stopwords.
@@ -311,6 +368,42 @@ 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`.
### `frontMatter.content.folders`
This array of folders defines where the extension can easily create new content by running the create article command.
```json
{
"frontMatter.content.folders": [{
"title": "Articles",
"fsPath": "<the path to the folder>",
"paths": ["<wsl-folder-path>"]
}]
}
```
> **Important**: This setting can be configured by right-clicking on a folder in the VSCode file explorer view and clicking on the `Front Matter: Register folder` menu item.
### `frontMatter.content.autoUpdateDate`
Specify if you want to automatically update the modification date of your markdown page when doing changes to it. Default: `false`.
```json
{
"frontMatter.content.autoUpdateDate": false
}
```
### `frontMatter.content.fmHighlight`
Specify if you want to highlight the Front Matter in the Markdown file. Default: `true`.
```json
{
"frontMatter.content.fmHighlight": true
}
```
## Feedback / issues / ideas
Please submit them via creating an issue in the project repository: [issue list](https://github.com/estruyf/vscode-front-matter/issues).

View File

@@ -0,0 +1,4 @@
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#F3EFF5">
<path d="M9 9H4v1h5V9z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
</svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@@ -0,0 +1,4 @@
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentcolor">
<path d="M9 9H4v1h5V9z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
</svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@@ -21,6 +21,25 @@
}
}
.relative {
position: relative !important;
}
.absolute {
position: absolute !important;
}
.inherit {
position: inherit !important;
}
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 10 !important; }
.w-full {
width: 100% !important;
}
.collapsible__body,
.ext_settings {
padding: 1rem 1.25rem;
@@ -240,7 +259,10 @@
filter: contrast(60%);
}
.article__actions > * + * {
.article__actions > * + *,
.other_actions > * + *,
.base__actions > * + *,
.base__information > * + * {
--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));
@@ -256,10 +278,21 @@
.ext_link_block {
display: flex;
align-items: center;
width: 100%;
}
.ext_link_block svg {
margin-right: .5rem;
display: block;
width: 16px;
height: 16px;
min-width: 16px;
}
.ext_link_block button span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ext_link_block button,
@@ -278,7 +311,17 @@
padding: 0px 14px;
user-select: none;
text-decoration: none;
width: auto;
width: 100%;
white-space: nowrap;
}
.ext_link_block button.active {
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
}
.ext_link_block button.active:hover {
cursor: pointer;
background: var(--vscode-button-hoverBackground);
}
.ext_link_block a:hover,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
assets/v2.2.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/v2.3.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/v2.4.0/actions.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
assets/v2.4.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
assets/v2.4.0/folding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

34
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "vscode-front-matter",
"version": "2.0.0",
"version": "2.4.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -40,9 +40,9 @@
}
},
"@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==",
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@bendera/vscode-webview-elements/-/vscode-webview-elements-0.6.2.tgz",
"integrity": "sha512-smtr+KvCKV2MwjVrmyvrhonpaXVpxCjTMXUQOwDwWSAQ42x5pnlpjCGElz2dljc5VHS1Mh1ovPSQ/P3jAm7vMQ==",
"dev": true,
"requires": {
"lit-element": "^2.5.1"
@@ -94,6 +94,21 @@
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"@types/lodash": {
"version": "4.14.172",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.172.tgz",
"integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==",
"dev": true
},
"@types/lodash.uniqby": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.6.tgz",
"integrity": "sha512-9wBhrm1y6asW50Joj6tsySCNUgzK2tCqL7vtKIej0E9RyeBFdcte7fxUosmFuMoOU0eHqOMK76kCCrK99jxHgg==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/mdast": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.7.tgz",
@@ -2759,6 +2774,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.uniqby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
"integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -3545,9 +3565,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"pbkdf2": {

View File

@@ -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": "2.0.0",
"version": "2.4.1",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -53,6 +53,11 @@
"onCommand:frontMatter.setLastModifiedDate",
"onCommand:frontMatter.generateSlug",
"onCommand:frontMatter.createFromTemplate",
"onCommand:frontMatter.registerFolder",
"onCommand:frontMatter.unregisterFolder",
"onCommand:frontMatter.createContent",
"onCommand:frontMatter.init",
"onCommand:frontMatter.collapseSections",
"onView:frontMatter.explorer"
],
"main": "./dist/extension",
@@ -80,6 +85,31 @@
"configuration": {
"title": "Front Matter: Configuration",
"properties": {
"frontMatter.content.autoUpdateDate": {
"type": "boolean",
"default": false,
"description": "Specify if you want to automatically update the modified date of your article/page."
},
"frontMatter.content.fmHighlight": {
"type": "boolean",
"default": true,
"description": "Specify if you want to highlight the Front Matter in the Markdown file."
},
"frontMatter.content.folders": {
"type": "array",
"default": [],
"markdownDescription": "This array of folders defines where the extension can easily create new content by running the create article command."
},
"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."
},
"frontMatter.panel.freeform": {
"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.taxonomy.dateField": {
"type": "string",
"default": "date",
@@ -110,6 +140,11 @@
"type": "string",
"markdownDescription": "Specify a suffix for the slug"
},
"frontMatter.taxonomy.alignFilename": {
"type": "boolean",
"default": false,
"markdownDescription": "Align the filename with the new slug when it gets generated."
},
"frontMatter.taxonomy.indentArrays": {
"type": "boolean",
"default": true,
@@ -161,59 +196,93 @@
"type": "string",
"default": "yyyy-MM-dd",
"description": "Specify the prefix you want to add for your new article filenames."
},
"frontMatter.panel.freeform": {
"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."
}
}
},
"commands": [
{
"command": "frontMatter.insertTags",
"title": "Front Matter: Insert tags"
"title": "Insert tags",
"category": "Front matter"
},
{
"command": "frontMatter.insertCategories",
"title": "Front Matter: Insert categories"
"title": "Insert categories",
"category": "Front matter"
},
{
"command": "frontMatter.createTag",
"title": "Front Matter: Create tag"
"title": "Create tag",
"category": "Front matter"
},
{
"command": "frontMatter.createCategory",
"title": "Front Matter: Create category"
"title": "Create category",
"category": "Front matter"
},
{
"command": "frontMatter.exportTaxonomy",
"title": "Front Matter: Export all tags & categories to your settings"
"title": "Export all tags & categories to your settings",
"category": "Front matter"
},
{
"command": "frontMatter.remap",
"title": "Front Matter: Remap or remove tag/category in all articles"
"title": "Remap or remove tag/category in all articles",
"category": "Front matter"
},
{
"command": "frontMatter.setDate",
"title": "Front Matter: Set current date"
"title": "Set current date",
"category": "Front matter"
},
{
"command": "frontMatter.setLastModifiedDate",
"title": "Front Matter: Set lastmod date"
"title": "Set lastmod date",
"category": "Front matter"
},
{
"command": "frontMatter.generateSlug",
"title": "Front Matter: Generate slug based on article title"
"title": "Generate slug based on article title",
"category": "Front matter"
},
{
"command": "frontMatter.createFromTemplate",
"title": "Front Matter: New article from template"
"title": "New article from template",
"category": "Front matter"
},
{
"command": "frontMatter.registerFolder",
"title": "Register folder",
"category": "Front matter"
},
{
"command": "frontMatter.unregisterFolder",
"title": "Unregister folder",
"category": "Front matter"
},
{
"command": "frontMatter.createContent",
"title": "New article from template",
"category": "Front matter"
},
{
"command": "frontMatter.init",
"title": "Initialize project",
"category": "Front matter"
},
{
"command": "frontMatter.createTemplate",
"title": "Create a template from current file",
"category": "Front matter"
},
{
"command": "frontMatter.collapseSections",
"title": "Collapse sections",
"category": "Front matter",
"icon": {
"light": "assets/icons/close-light.svg",
"dark": "assets/icons/close-dark.svg"
}
}
],
"menus": {
@@ -222,6 +291,36 @@
"command": "frontMatter.createFromTemplate",
"when": "explorerResourceIsFolder",
"group": "Front Matter@1"
},
{
"command": "frontMatter.registerFolder",
"when": "explorerResourceIsFolder",
"group": "Front Matter@2"
},
{
"command": "frontMatter.unregisterFolder",
"when": "explorerResourceIsFolder && resourcePath in frontMatter.registeredFolders",
"group": "Front Matter@3"
}
],
"commandPalette": [
{
"command": "frontMatter.init",
"when": "frontMatterCanInit"
},
{
"command": "frontMatter.createTemplate",
"when": "!frontMatterCanInit"
},
{
"command": "frontMatter.collapseSections",
"when": "false"
}
],
"view/title": [
{
"command": "frontMatter.collapseSections",
"group": "navigation"
}
]
},
@@ -243,10 +342,11 @@
"clean": "rm -rf dist"
},
"devDependencies": {
"@bendera/vscode-webview-elements": "0.5.0",
"@bendera/vscode-webview-elements": "0.6.2",
"@iarna/toml": "2.2.3",
"@types/glob": "7.1.3",
"@types/js-yaml": "3.12.1",
"@types/lodash.uniqby": "4.7.6",
"@types/mocha": "^5.2.6",
"@types/node": "10.17.48",
"@types/react": "17.0.0",
@@ -269,5 +369,7 @@
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
},
"dependencies": {}
"dependencies": {
"lodash.uniqby": "4.7.0"
}
}

View File

@@ -1,13 +1,16 @@
import { SETTING_MODIFIED_FIELD } from './../constants/settings';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX } from './../constants/settings';
import * as vscode from 'vscode';
import { TaxonomyType } from "../models";
import { CONFIG_KEY, SETTING_DATE_FORMAT, EXTENSION_NAME, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_DATE_FIELD } from "../constants/settings";
import { CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_DATE_FIELD } from "../constants/settings";
import { format } from "date-fns";
import { ArticleHelper, SettingsHelper, SlugHelper } from '../helpers';
import matter = require('gray-matter');
import { Notifications } from '../helpers/Notifications';
import { extname, basename } from 'path';
export class Article {
private static prevContent = "";
/**
* Insert taxonomy
@@ -53,7 +56,7 @@ export class Article {
}
if (options.length === 0) {
vscode.window.showInformationMessage(`${EXTENSION_NAME}: No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
Notifications.info(`No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
return;
}
@@ -88,7 +91,7 @@ export class Article {
try {
ArticleHelper.update(editor, article);
} catch (e) {
vscode.window.showErrorMessage(`${EXTENSION_NAME}: Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
console.log(e.message);
}
}
@@ -124,18 +127,19 @@ export class Article {
return;
}
const cloneArticle = Object.assign({}, article);
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_MODIFIED_FIELD) as string || "lastmod";
try {
if (dateFormat && typeof dateFormat === "string") {
article.data[dateField] = format(new Date(), dateFormat);
cloneArticle.data[dateField] = format(new Date(), dateFormat);
} else {
article.data[dateField] = new Date().toISOString();
cloneArticle.data[dateField] = new Date().toISOString();
}
ArticleHelper.update(editor, article);
ArticleHelper.update(editor, cloneArticle);
} catch (e) {
vscode.window.showErrorMessage(`${EXTENSION_NAME}: Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
console.log(e.message);
}
}
@@ -143,11 +147,14 @@ export class Article {
/**
* Generate the slug based on the article title
*/
public static generateSlug() {
public static async generateSlug() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const prefix = config.get(SETTING_SLUG_PREFIX) as string;
const suffix = config.get(SETTING_SLUG_SUFFIX) as string;
const updateFileName = config.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
const filePrefix = config.get<string>(SETTING_TEMPLATES_PREFIX);
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
@@ -158,10 +165,42 @@ export class Article {
}
const articleTitle: string = article.data["title"];
const slug = SlugHelper.createSlug(articleTitle);
let slug = SlugHelper.createSlug(articleTitle);
if (slug) {
article.data["slug"] = `${prefix}${slug}${suffix}`;
slug = `${prefix}${slug}${suffix}`;
article.data["slug"] = slug;
ArticleHelper.update(editor, article);
// Check if the file name should be updated by the slug
// This is required for systems like Jekyll
if (updateFileName) {
const editor = vscode.window.activeTextEditor;
if (editor) {
const ext = extname(editor.document.fileName);
const fileName = basename(editor.document.fileName);
let slugName = slug.startsWith("/") ? slug.substring(1) : slug;
slugName = slugName.endsWith("/") ? slugName.substring(0, slugName.length - 1) : slugName;
let newFileName = `${slugName}${ext}`;
if (filePrefix && typeof filePrefix === "string") {
newFileName = `${format(new Date(), filePrefix)}-${newFileName}`;
}
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
try {
await editor.document.save();
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
overwrite: false
});
} catch (e) {
Notifications.error(`Failed to rename file.`);
console.log(e?.message || e);
}
}
}
}
}
@@ -183,6 +222,35 @@ export class Article {
ArticleHelper.update(editor, article);
}
/**
* Article auto updater
* @param fileChanges
*/
public static async autoUpdate(fileChanges: vscode.TextDocumentChangeEvent) {
const txtChanges = fileChanges.contentChanges.map(c => c.text);
const editor = vscode.window.activeTextEditor;
if (txtChanges.length > 0 && editor && ArticleHelper.isMarkdownFile()) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const autoUpdate = config.get(SETTING_AUTO_UPDATE_DATE);
if (autoUpdate) {
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
if (article.content === Article.prevContent) {
return;
}
Article.prevContent = article.content;
Article.setLastModifiedDate();
}
}
}
/**
* Update the article date and return it
* @param article

167
src/commands/Folders.ts Normal file
View File

@@ -0,0 +1,167 @@
import { commands, Uri, workspace, window } from "vscode";
import { CONFIG_KEY, SETTINGS_CONTENT_FOLDERS } from "../constants";
import { basename } from "path";
import { ContentFolder, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
import { Notifications } from "../helpers/Notifications";
import { CONTEXT } from "../constants/context";
export class Folders {
/**
* Create content in a registered folder
* @returns
*/
public static async create() {
const folders = Folders.get();
if (!folders || folders.length === 0) {
Notifications.warning(`There are no known content locations defined in this project.`);
return;
}
const selectedFolder = await window.showQuickPick(folders.map(f => f.title), {
placeHolder: `Select where you want to create your content`
});
if (!selectedFolder) {
Notifications.warning(`You didn't select a place where you wanted to create your content.`);
return;
}
const location = folders.find(f => f.title === selectedFolder);
if (location) {
const folderPath = Folders.getFolderPath(Uri.file(location.fsPath));
if (folderPath) {
Template.create(folderPath);
}
}
}
/**
* Register the new folder path
* @param folder
*/
public static async register(folder: Uri) {
if (folder && folder.fsPath) {
const wslPath = folder.fsPath.replace(/\//g, '\\');
let folders = Folders.get();
const exists = folders.find(f => f.paths.includes(folder.fsPath) || f.paths.includes(wslPath));
if (exists) {
Notifications.warning(`Folder is already registered`);
return;
}
const folderName = await window.showInputBox({
prompt: `Which name would you like to specify for this folder?`,
placeHolder: `Folder name`,
value: basename(folder.fsPath)
});
folders.push({
title: folderName,
fsPath: folder.fsPath,
paths: folder.fsPath === wslPath ? [folder.fsPath] : [folder.fsPath, wslPath]
} as ContentFolder);
folders = uniqBy(folders, f => f.fsPath);
await Folders.update(folders);
Notifications.info(`Folder registered`);
}
Folders.updateVsCodeCtx();
}
/**
* Unregister a folder path
* @param folder
*/
public static async unregister(folder: Uri) {
if (folder && folder.path) {
let folders = Folders.get();
folders = folders.filter(f => f.fsPath !== folder.fsPath);
await Folders.update(folders);
}
Folders.updateVsCodeCtx();
}
/**
* Update the registered folders context
*/
public static updateVsCodeCtx() {
const folders = Folders.get();
let allFolders: string[] = [];
for (const folder of folders) {
allFolders = [...allFolders, ...folder.paths]
}
commands.executeCommand('setContext', CONTEXT.registeredFolders, allFolders);
}
/**
* Retrieve the folder path
* @param folder
* @returns
*/
public static getFolderPath(folder: Uri) {
let folderPath = "";
if (folder && folder.fsPath) {
folderPath = folder.fsPath;
} else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
folderPath = workspace.workspaceFolders[0].uri.fsPath;
}
return folderPath;
}
/**
* Get the registered folders information
*/
public static async getInfo(): Promise<FolderInfo[] | null> {
const folders = Folders.get();
if (folders && folders.length > 0) {
let folderInfo: FolderInfo[] = [];
for (const folder of folders) {
try {
const files = await workspace.fs.readDirectory(Uri.file(folder.fsPath));
if (files) {
folderInfo.push({
title: folder.title,
files: files.length
});
}
} catch (e) {
// Skip the current folder
}
}
return folderInfo;
}
return null;
}
/**
* Get the folder settings
* @returns
*/
private static get() {
const config = workspace.getConfiguration(CONFIG_KEY);
const folders: ContentFolder[] = config.get(SETTINGS_CONTENT_FOLDERS) as ContentFolder[];
return folders;
}
/**
* Update the folder settings
* @param folders
*/
private static async update(folders: ContentFolder[]) {
const config = workspace.getConfiguration(CONFIG_KEY);
await config.update(SETTINGS_CONTENT_FOLDERS, folders);
}
}

65
src/commands/Project.ts Normal file
View File

@@ -0,0 +1,65 @@
import { workspace, Uri } from "vscode";
import { CONFIG_KEY, SETTING_TEMPLATES_FOLDER } from "../constants";
import { join } from "path";
import * as fs from "fs";
import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
export class Project {
private static content = `---
title: "{{name}}"
slug: "/{{kebabCase name}}/"
description:
author:
date: 2019-08-22T15:20:28.000Z
lastmod: 2019-08-22T15:20:28.000Z
draft: true
tags: []
categories: []
---
`;
/**
* Initialize a new "Project" instance.
*/
public static async init(sampleTemplate: boolean = true) {
try {
const folder = Template.getSettings();
const templatePath = Project.templatePath();
if (!folder || !templatePath) {
return;
}
const article = Uri.file(join(templatePath.fsPath, "article.md"));
if (!fs.existsSync(templatePath.fsPath)) {
await workspace.fs.createDirectory(templatePath);
}
if (sampleTemplate) {
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
Notifications.info("Project initialized successfully.");
}
} catch (err) {
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
}
}
/**
* Get the template path for the current project
*/
public static templatePath() {
const folder = Template.getSettings();
const workspaceFolders = workspace.workspaceFolders;
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
return null;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = Uri.file(join(workspaceFolder.uri.fsPath, folder));
return templatePath;
}
}

View File

@@ -6,6 +6,7 @@ import { CONFIG_KEY, SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, EXTENSI
import { ArticleHelper, SettingsHelper, FilesHelper } from '../helpers';
import { TomlEngine, getFmLanguage, getFormatOpts } from '../helpers/TomlEngine';
import { DumpOptions } from 'js-yaml';
import { Notifications } from '../helpers/Notifications';
export class Settings {
@@ -29,7 +30,7 @@ export class Settings {
}
if (options.find(o => o === newOption)) {
vscode.window.showInformationMessage(`${EXTENSION_NAME}: The provided ${type === TaxonomyType.Tag ? "tag" : "category"} already exists.`);
Notifications.info(`The provided ${type === TaxonomyType.Tag ? "tag" : "category"} already exists.`);
return;
}
@@ -145,7 +146,7 @@ export class Settings {
await config.update(SETTING_TAXONOMY_CATEGORIES, crntCategories);
// Done
vscode.window.showInformationMessage(`${EXTENSION_NAME}: Export completed. Tags: ${crntTags.length} - Categories: ${crntCategories.length}.`);
Notifications.info(`Export completed. Tags: ${crntTags.length} - Categories: ${crntCategories.length}.`);
});
}
@@ -171,7 +172,7 @@ export class Settings {
let options = SettingsHelper.getTaxonomy(type);
if (!options || options.length === 0) {
vscode.window.showInformationMessage(`${EXTENSION_NAME}: No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
Notifications.info(`No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
return;
}
@@ -273,7 +274,7 @@ export class Settings {
}
await SettingsHelper.update(type, options);
vscode.window.showInformationMessage(`${EXTENSION_NAME}: ${newOptionValue ? "Remapping" : "Deleation"} of the ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"} completed.`);
Notifications.info(`${newOptionValue ? "Remapping" : "Deleation"} of the ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"} completed.`);
});
}
}

View File

@@ -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" || editor.document.languageId.toLowerCase() === "mdx")) {
if (editor && ArticleHelper.isMarkdownFile()) {
try {
const article = ArticleHelper.getFrontMatter(editor);
@@ -61,6 +61,11 @@ export class StatusListener {
} catch (e) {
// Nothing to do
}
} else {
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
panel.pushMetadata(null);
}
}
frontMatterSB.hide();

View File

@@ -1,14 +1,94 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { CONFIG_KEY, EXTENSION_NAME, SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
import { CONFIG_KEY, SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
import { format } from 'date-fns';
import sanitize from '../helpers/Sanitize';
import { ArticleHelper } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
import { CONTEXT } from '../constants/context';
import { Project } from './Project';
export class Template {
/**
* Check if the template folder is available
*/
public static async init() {
const isInitialized = await Template.isInitialized();
await vscode.commands.executeCommand('setContext', CONTEXT.canInit, !isInitialized);
}
/**
* Check if the project is already initialized
*/
public static async isInitialized() {
const workspaceFolders = vscode.workspace.workspaceFolders;
const folder = Template.getSettings();
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
return false;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, folder));
try {
await vscode.workspace.fs.stat(templatePath);
return true;
} catch (e) {
return false;
}
}
/**
* Generate a template
*/
public static async generate() {
const folder = Template.getSettings();
const editor = vscode.window.activeTextEditor;
if (folder && editor && ArticleHelper.isMarkdownFile()) {
const article = ArticleHelper.getFrontMatter(editor);
const clonedArticle = Object.assign({}, article);
const titleValue = await vscode.window.showInputBox({
prompt: `What name would you like to give your template?`,
placeHolder: `article`
});
if (!titleValue) {
Notifications.warning(`You did not specify a template title.`);
return;
}
const keepContents = await vscode.window.showQuickPick(
["yes", "no"],
{
canPickMany: false,
placeHolder: `Do you want to keep the article its contents for the template?`,
}
);
if (!keepContents) {
Notifications.warning(`You did not pick any of the options for keeping the template its content.`);
return;
}
await Project.init(false);
const templatePath = Project.templatePath();
if (templatePath) {
let fileContents = ArticleHelper.stringifyFrontMatter(keepContents === "no" ? "" : clonedArticle.content, clonedArticle.data);
const templateFile = path.join(templatePath.fsPath, `${titleValue}.md`);
fs.writeFileSync(templateFile, fileContents, { encoding: "utf-8" });
Notifications.info(`Template created and is now available in your ${folder} folder.`);
}
}
}
/**
* Create from a template
*/
@@ -18,18 +98,18 @@ export class Template {
const prefix = config.get<string>(SETTING_TEMPLATES_PREFIX);
if (!folderPath) {
this.showNoTemplates(`Incorrect project folder path retrieved.`);
Notifications.warning(`Incorrect project folder path retrieved.`);
return;
}
if (!folder) {
this.showNoTemplates(`No templates found.`);
Notifications.warning(`No templates found.`);
return;
}
const templates = await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
if (!templates || templates.length === 0) {
this.showNoTemplates(`No templates found.`);
Notifications.warning(`No templates found.`);
return;
}
@@ -37,7 +117,7 @@ export class Template {
placeHolder: `Select the article template to use`
});
if (!selectedTemplate) {
this.showNoTemplates(`No template selected.`);
Notifications.warning(`No template selected.`);
return;
}
@@ -46,14 +126,14 @@ export class Template {
placeHolder: `Article title`
});
if (!titleValue) {
this.showNoTemplates(`You did not specify an article title.`);
Notifications.warning(`You did not specify an article title.`);
return;
}
// Start the template read
const template = templates.find(t => t.fsPath.endsWith(selectedTemplate));
if (!template) {
this.showNoTemplates(`Article template could not be found.`);
Notifications.warning(`Article template could not be found.`);
return;
}
@@ -66,7 +146,7 @@ export class Template {
const newFilePath = path.join(folderPath, newFileName);
if (fs.existsSync(newFilePath)) {
this.showNoTemplates(`File already exists, please remove it before creating a new one with the same title.`);
Notifications.warning(`File already exists, please remove it before creating a new one with the same title.`);
return;
}
@@ -76,7 +156,7 @@ export class Template {
// Update the properties inside the template
let frontMatter = ArticleHelper.getFrontMatterByPath(newFilePath);
if (!frontMatter) {
this.showNoTemplates(`Something failed when retrieving the newly created file.`);
Notifications.warning(`Something failed when retrieving the newly created file.`);
return;
}
@@ -101,13 +181,15 @@ export class Template {
vscode.window.showTextDocument(txtDoc);
}
vscode.window.showInformationMessage(`${EXTENSION_NAME}: Your new article has been created.`);
Notifications.info(`Your new article has been created.`);
}
/**
* Show a warning message when no templates are found
* Get the folder settings
*/
private static showNoTemplates(value: string) {
vscode.window.showWarningMessage(`${EXTENSION_NAME}: ${value}`);
public static getSettings() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
return folder;
}
}

View File

@@ -0,0 +1,25 @@
const extensionName = "frontMatter";
export const getCommandName = (command: string) => {
return `${extensionName}.${command}`;
};
export const COMMAND_NAME = {
init: getCommandName("init"),
insertTags: getCommandName("insertTags"),
insertCategories: getCommandName("insertCategories"),
createTag: getCommandName("createTag"),
createCategory: getCommandName("createCategory"),
exportTaxonomy: getCommandName("exportTaxonomy"),
remap: getCommandName("remap"),
setDate: getCommandName("setDate"),
setLastModifiedDate: getCommandName("setLastModifiedDate"),
generateSlug: getCommandName("generateSlug"),
createFromTemplate: getCommandName("createFromTemplate"),
toggleDraft: getCommandName("toggleDraft"),
registerFolder: getCommandName("registerFolder"),
unregisterFolder: getCommandName("unregisterFolder"),
createContent: getCommandName("createContent"),
createTemplate: getCommandName("createTemplate"),
collapseSections: getCommandName("collapseSections"),
};

6
src/constants/context.ts Normal file
View File

@@ -0,0 +1,6 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
registeredFolders: 'frontMatter.registeredFolders'
};

View File

@@ -10,6 +10,7 @@ export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";
export const SETTING_SLUG_PREFIX = "taxonomy.slugPrefix";
export const SETTING_SLUG_SUFFIX = "taxonomy.slugSuffix";
export const SETTING_SLUG_UPDATE_FILE_NAME = "taxonomy.alignFilename";
export const SETTING_INDENT_ARRAY = "taxonomy.indentArrays";
export const SETTING_REMOVE_QUOTES = "taxonomy.noPropertyValueQuotes";
@@ -26,4 +27,8 @@ export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
export const SETTING_PANEL_FREEFORM = "panel.freeform";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";

View File

@@ -1,15 +1,22 @@
import * as vscode from 'vscode';
import { Article, Settings, StatusListener } from './commands';
import { Folders } from './commands/Folders';
import { Project } from './commands/Project';
import { Template } from './commands/Template';
import { COMMAND_NAME } from './constants/Extension';
import { TaxonomyType } from './models';
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
import { TagType } from './viewpanel/TagType';
import { ExplorerView } from './webview/ExplorerView';
let frontMatterStatusBar: vscode.StatusBarItem;
let debouncer: { (fnc: any, time: number): void; };
let statusDebouncer: { (fnc: any, time: number): void; };
let editDebounce: { (fnc: any, time: number): void; };
let collection: vscode.DiagnosticCollection;
export function activate({ subscriptions, extensionUri }: vscode.ExtensionContext) {
const mdSelector: vscode.DocumentSelector = { language: 'markdown', scheme: 'file' };
export async function activate({ subscriptions, extensionUri }: vscode.ExtensionContext) {
collection = vscode.languages.createDiagnosticCollection('frontMatter');
const explorerSidebar = ExplorerView.getInstance(extensionUri);
@@ -19,95 +26,142 @@ export function activate({ subscriptions, extensionUri }: vscode.ExtensionContex
}
});
let insertTags = vscode.commands.registerCommand('frontMatter.insertTags', async () => {
// Folding the front matter of markdown files
vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider());
let insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => {
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.tags);
});
let insertCategories = vscode.commands.registerCommand('frontMatter.insertCategories', async () => {
let insertCategories = vscode.commands.registerCommand(COMMAND_NAME.insertCategories, async () => {
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.categories);
});
let createTag = vscode.commands.registerCommand('frontMatter.createTag', () => {
let createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
Settings.create(TaxonomyType.Tag);
});
let createCategory = vscode.commands.registerCommand('frontMatter.createCategory', () => {
let createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
Settings.create(TaxonomyType.Category);
});
let exportTaxonomy = vscode.commands.registerCommand('frontMatter.exportTaxonomy', () => {
let exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, () => {
Settings.export();
});
let remap = vscode.commands.registerCommand('frontMatter.remap', () => {
let remap = vscode.commands.registerCommand(COMMAND_NAME.remap, () => {
Settings.remap();
});
let setDate = vscode.commands.registerCommand('frontMatter.setDate', () => {
let setDate = vscode.commands.registerCommand(COMMAND_NAME.setDate, () => {
Article.setDate();
});
let setLastModifiedDate = vscode.commands.registerCommand('frontMatter.setLastModifiedDate', () => {
let setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, () => {
Article.setLastModifiedDate();
});
let generateSlug = vscode.commands.registerCommand('frontMatter.generateSlug', () => {
let generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, () => {
Article.generateSlug();
});
let createFromTemplate = vscode.commands.registerCommand('frontMatter.createFromTemplate', (e: vscode.Uri) => {
let folderPath = "";
if (e && e.fsPath) {
folderPath = e.fsPath;
} else if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
folderPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
}
Template.create(folderPath);
});
let createFromTemplate = vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
const folderPath = Folders.getFolderPath(folder);
if (folderPath) {
Template.create(folderPath);
}
});
const toggleDraftCommand = 'frontMatter.toggleDraft';
let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate);
const toggleDraftCommand = COMMAND_NAME.toggleDraft;
const toggleDraft = vscode.commands.registerCommand(toggleDraftCommand, async () => {
await Article.toggleDraft();
triggerShowDraftStatus();
});
// Register project folders
const registerFolder = vscode.commands.registerCommand(COMMAND_NAME.registerFolder, Folders.register);
const unregisterFolder = vscode.commands.registerCommand(COMMAND_NAME.unregisterFolder, Folders.unregister);
const createContent = vscode.commands.registerCommand(COMMAND_NAME.createContent, Folders.create);
Folders.updateVsCodeCtx();
// Initialize command
Template.init();
const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, Project.init);
// Collapse all sections in the webview
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
ExplorerView.getInstance()?.collapseAll();
});
// Things to do when configuration changes
vscode.workspace.onDidChangeConfiguration(() => {
Template.init();
Folders.updateVsCodeCtx();
});
// Create the status bar
frontMatterStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
frontMatterStatusBar.command = toggleDraftCommand;
subscriptions.push(frontMatterStatusBar);
debouncer = debounceShowDraftTrigger();
statusDebouncer = debounceCallback();
// Register listeners that make sure the status bar updates
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(triggerShowDraftStatus));
subscriptions.push(vscode.window.onDidChangeTextEditorSelection(triggerShowDraftStatus));
// Automatically run the command
triggerShowDraftStatus();
// Listener for file edit changes
editDebounce = debounceCallback();
subscriptions.push(vscode.workspace.onDidChangeTextDocument(triggerFileChange));
// Listener for setting changes
subscriptions.push(vscode.workspace.onDidChangeConfiguration(MarkdownFoldingProvider.triggerHighlighting));
// Subscribe all commands
subscriptions.push(insertTags);
subscriptions.push(explorerView);
subscriptions.push(insertCategories);
subscriptions.push(createTag);
subscriptions.push(createCategory);
subscriptions.push(exportTaxonomy);
subscriptions.push(remap);
subscriptions.push(setDate);
subscriptions.push(setLastModifiedDate);
subscriptions.push(generateSlug);
subscriptions.push(createFromTemplate);
subscriptions.push(toggleDraft);
subscriptions.push(
insertTags,
explorerView,
insertCategories,
createTag,
createCategory,
exportTaxonomy,
remap,
setDate,
setLastModifiedDate,
generateSlug,
createFromTemplate,
createTemplate,
toggleDraft,
registerFolder,
unregisterFolder,
createContent,
projectInit,
collapseAll
);
}
export function deactivate() {}
const triggerShowDraftStatus = () => {
debouncer(() => { StatusListener.verify(frontMatterStatusBar, collection); }, 1000);
const triggerFileChange = (e: vscode.TextDocumentChangeEvent) => {
editDebounce(() => Article.autoUpdate(e), 1000);
};
const debounceShowDraftTrigger = () => {
const triggerShowDraftStatus = () => {
statusDebouncer(() => { StatusListener.verify(frontMatterStatusBar, collection); }, 1000);
};
const debounceCallback = () => {
let timeout: NodeJS.Timeout;
return (fnc: any, time: number) => {
@@ -115,4 +169,4 @@ const debounceShowDraftTrigger = () => {
clearTimeout(timeout);
timeout = setTimeout(functionCall, time) as any;
};
};
};

View File

@@ -96,4 +96,12 @@ export class ArticleHelper {
indent: spaces || 2
} as DumpOptions as any));
}
/**
* Checks if the current file is a markdown file
*/
public static isMarkdownFile() {
const editor = vscode.window.activeTextEditor;
return (editor && editor.document && (editor.document.languageId.toLowerCase() === "markdown" || editor.document.languageId.toLowerCase() === "mdx"));
}
}

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { EXTENSION_NAME } from '../constants';
import { Notifications } from './Notifications';
export class FilesHelper {
@@ -11,7 +12,7 @@ export class FilesHelper {
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.`);
Notifications.info(`No MD files found.`);
return null;
}

View File

@@ -0,0 +1,18 @@
import { window } from "vscode";
import { EXTENSION_NAME } from "../constants";
export class Notifications {
public static info(message: string) {
window.showInformationMessage(`${EXTENSION_NAME}: ${message}`);
}
public static warning(message: string) {
window.showWarningMessage(`${EXTENSION_NAME}: ${message}`);
}
public static error(message: string) {
window.showErrorMessage(`${EXTENSION_NAME}: ${message}`);
}
}

View File

@@ -0,0 +1,5 @@
export interface ContentFolder {
title: string;
fsPath: string;
paths: string[];
}

View File

@@ -6,6 +6,11 @@ export interface PanelSettings {
categories: string[];
freeform: boolean;
scripts: CustomScript[];
isInitialized: boolean;
modifiedDateUpdate: boolean;
contentInfo: FolderInfo[] | null;
writingSettingsEnabled: boolean;
fmHighlighting: boolean;
}
export interface SEO {
@@ -20,6 +25,11 @@ export interface Slug {
suffix: number;
}
export interface FolderInfo {
title: string;
files: number;
}
export interface CustomScript {
title: string;
script: string;

View File

@@ -1 +1,3 @@
export * from './ContentFolder';
export * from './PanelSettings';
export * from './TaxonomyType';

View File

@@ -0,0 +1,13 @@
import { TextEditorDecorationType, window, ColorThemeKind } from "vscode";
export class FrontMatterDecorationProvider {
get(): TextEditorDecorationType {
const colorThemeKind = window.activeColorTheme.kind;
return window.createTextEditorDecorationType({
backgroundColor: colorThemeKind === ColorThemeKind.Dark ? "#ffffff14" : "#00000014",
isWholeLine: true,
});
}
}

View File

@@ -0,0 +1,62 @@
import { TextEditorDecorationType, workspace } from 'vscode';
import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode';
import { CONFIG_KEY, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT } from '../constants';
import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider';
export class MarkdownFoldingProvider implements FoldingRangeProvider {
private static start: number | null = null;
private static end: number | null = null;
private static endLine: number | null = null;
private static decType: TextEditorDecorationType | null = null;
public async provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken): Promise<FoldingRange[]> {
const ranges: FoldingRange[] = [];
const lines = document.getText().split('\n');
let start: number | null = null;
let end: number | null = null;
MarkdownFoldingProvider.start = null;
MarkdownFoldingProvider.end = null;
MarkdownFoldingProvider.endLine = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('---')) {
if (i === 0 && start === null) {
start = i;
MarkdownFoldingProvider.start = start;
} else if (start !== null && end === null) {
end = i;
MarkdownFoldingProvider.end = end;
MarkdownFoldingProvider.endLine = line.length;
MarkdownFoldingProvider.triggerHighlighting();
ranges.push(new FoldingRange(start, end, FoldingRangeKind.Region));
return ranges;
}
}
}
return ranges;
}
public static triggerHighlighting() {
const config = workspace.getConfiguration(CONFIG_KEY);
const fmHighlight = config.get<boolean>(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT);
if (MarkdownFoldingProvider.start !== null && MarkdownFoldingProvider.end !== null && MarkdownFoldingProvider.endLine !== null) {
const range = new Range(new Position(MarkdownFoldingProvider.start, 0), new Position(MarkdownFoldingProvider.end, MarkdownFoldingProvider.endLine));
if (MarkdownFoldingProvider.decType !== null) {
MarkdownFoldingProvider.decType.dispose();
}
if (fmHighlight) {
MarkdownFoldingProvider.decType = new FrontMatterDecorationProvider().get();
window.activeTextEditor?.setDecorations(MarkdownFoldingProvider.decType, [range]);
}
}
}
}

View File

@@ -3,5 +3,6 @@ export enum Command {
metadata = "metadata",
settings = "settings",
focusOnTags = "focusOnTags",
focusOnCategories = "focusOnCategories"
focusOnCategories = "focusOnCategories",
closeSections = "closeSections",
}

View File

@@ -12,5 +12,12 @@ export enum CommandToCode {
openSettings = "open-settings",
openFile = "open-file",
openProject = "open-project",
runCustomScript = "custom-script"
runCustomScript = "custom-script",
initProject = "init-project",
createContent = "create-content",
updateModifiedUpdating = "update-modified-updates",
updateFmHighlight = "update-fm-highlight",
createTemplate = "create-template",
toggleCenterMode = "toggle-center-mode",
toggleWritingSettings = "toggle-writing-settings",
}

View File

@@ -1,18 +1,15 @@
import * as React from 'react';
import { CommandToCode } from './CommandToCode';
import { Actions } from './components/Actions';
import { BaseView } from './components/BaseView';
import { Collapsible } from './components/Collapsible';
import { BugIcon } from './components/Icons/BugIcon';
import { FileIcon } from './components/Icons/FileIcon';
import { FolderOpenedIcon } from './components/Icons/FolderOpenedIcon';
import { GlobalSettings } from './components/GlobalSettings';
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 { OtherActions } from './components/OtherActions';
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';
@@ -30,27 +27,15 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
if (!metadata || Object.keys(metadata).length === 0) {
return (
<div className="frontmatter">
<p>Current view/file is not supported by FrontMatter.</p>
</div>
<BaseView settings={settings} />
);
}
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
};
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openFile);
};
const openProject = () => {
MessageHelper.sendMessage(CommandToCode.openProject);
};
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<GlobalSettings settings={settings} />
{
settings && settings.seo && <SeoStatus seo={settings.seo} data={metadata} />
}
@@ -58,7 +43,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
settings && metadata && <Actions metadata={metadata} settings={settings} />
}
<Collapsible title="Metadata">
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
@@ -92,24 +77,8 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
)
}
</Collapsible>
</div>
<div className={`ext_settings`}>
<div className="ext_link_block">
<button onClick={openSettings}><SettingsIcon /> Open settings</button>
</div>
<div className="ext_link_block">
<button onClick={openFile}><FileIcon /> Reveal file in folder</button>
</div>
<div className="ext_link_block">
<button onClick={openProject}><FolderOpenedIcon /> Reveal project folder</button>
</div>
<div className="ext_link_block">
<a href="https://github.com/estruyf/vscode-front-matter/issues" title="Open an issue on GitHub"><BugIcon /> Report an issue</a>
</div>
<OtherActions settings={settings} isFile={true} />
</div>
</div>
);

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
export interface IActionButtonProps {
title: string;
className?: string;
disabled?: boolean;
onClick: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
}
export const ActionButton: React.FunctionComponent<IActionButtonProps> = ({className, onClick, disabled,title}: React.PropsWithChildren<IActionButtonProps>) => {
return (
<div className={`article__action`}>
<button onClick={onClick} className={className || ""} disabled={disabled}>{title}</button>
</div>
);
};

View File

@@ -1,5 +1,8 @@
import * as React from 'react';
import { PanelSettings } from '../../models/PanelSettings';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
import { Collapsible } from './Collapsible';
import { CustomScript } from './CustomScript';
import { DateAction } from './DateAction';
@@ -19,8 +22,11 @@ export const Actions: React.FunctionComponent<IActionsProps> = (props: React.Pro
}
return (
<Collapsible title="Actions">
<Collapsible id={`actions`} title="Actions">
<div className={`article__actions`}>
<ActionButton onClick={() => MessageHelper.sendMessage(CommandToCode.toggleCenterMode)} title={`Toggle center mode`} />
{ metadata && metadata.title && <SlugAction value={metadata.title} crntValue={metadata.slug} slugOpts={settings.slug} /> }
<DateAction />

View File

@@ -0,0 +1,55 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { GlobalSettings } from './GlobalSettings';
import { OtherActions } from './OtherActions';
export interface IBaseViewProps {
settings: PanelSettings | undefined;
}
export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: React.PropsWithChildren<IBaseViewProps>) => {
const initProject = () => {
MessageHelper.sendMessage(CommandToCode.initProject);
};
const createContent = () => {
MessageHelper.sendMessage(CommandToCode.createContent);
};
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<GlobalSettings settings={settings} isBase />
<Collapsible id={`base_actions`} title="Actions">
<div className={`base__actions`}>
<button onClick={initProject} disabled={settings?.isInitialized}>Initialize project</button>
<button onClick={createContent} disabled={!settings?.isInitialized}>Create new content</button>
</div>
</Collapsible>
{
settings?.contentInfo && (
<Collapsible id={`base_content`} title="Content information">
<div className="base__information">
{
settings.contentInfo.map(folder => (
<div key={folder.title}>
{folder.title}: {folder.files} file{folder.files > 1 ? 's' : ''}
</div>
))
}
</div>
</Collapsible>
)
}
<OtherActions settings={settings} isFile={false} isBase />
</div>
</div>
);
};

View File

@@ -1,13 +1,22 @@
import * as React from 'react';
import { useEffect } from 'react';
import { Command } from '../Command';
import { VsCollapsible } from './VscodeComponents';
export interface ICollapsibleProps {
id: string;
title: string;
className?: string;
sendUpdate?: (open: boolean) => void;
}
export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({children, title, sendUpdate}: React.PropsWithChildren<ICollapsibleProps>) => {
const [ isOpen, setIsOpen ] = React.useState(true);
export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children, title, sendUpdate, className}: React.PropsWithChildren<ICollapsibleProps>) => {
const [ isOpen, setIsOpen ] = React.useState(false);
const collapseKey = `collapse-${id}`;
const updateStorage = (value: boolean) => {
window.localStorage.setItem(collapseKey, value.toString());
}
// This is a work around for a lit-element issue of duplicate slot names
const triggerClick = (e: React.MouseEvent<HTMLElement>) => {
@@ -16,14 +25,32 @@ export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({childre
if (sendUpdate) {
sendUpdate(!prev);
}
updateStorage(!prev);
return !prev;
});
}
}
useEffect(() => {
const collapsed = window.localStorage.getItem(collapseKey);
if (collapsed === null || collapsed === 'true') {
setIsOpen(true);
updateStorage(true);
}
window.addEventListener('message', event => {
const message = event.data;
if (message.command === Command.closeSections) {
setIsOpen(false);
updateStorage(false);
}
});
}, ['']);
return (
<VsCollapsible title={title} onClick={triggerClick} open={isOpen}>
<div className={`section collapsible__body`} slot="body">
<div className={`section collapsible__body ${className || ""}`} slot="body">
{children}
</div>
</VsCollapsible>

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface ICustomScriptProps {
title: string;
@@ -14,8 +15,6 @@ export const CustomScript: React.FunctionComponent<ICustomScriptProps> = ({title
};
return (
<div className={`article__action`}>
<button onClick={runCustomScript}>{title}</button>
</div>
<ActionButton onClick={runCustomScript} title={title} />
);
};

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface IDateActionProps {}
@@ -16,12 +17,8 @@ export const DateAction: React.FunctionComponent<IDateActionProps> = (props: Rea
return (
<>
<div className={`article__action`}>
<button onClick={setDate}>Set publish date</button>
</div>
<div className={`article__action`}>
<button onClick={setLastMod}>Set modified date</button>
</div>
<ActionButton onClick={setDate} title={`Set publish date`} />
<ActionButton onClick={setLastMod} title={`Set modified date`} />
</>
);
};

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { VsCheckbox } from './VscodeComponents';
export interface IGlobalSettingsProps {
settings: PanelSettings | undefined;
isBase?: boolean;
}
export const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings, isBase}: React.PropsWithChildren<IGlobalSettingsProps>) => {
const { modifiedDateUpdate, fmHighlighting } = settings || {};
const onDateCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
};
const onHighlightCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateFmHighlight, !fmHighlighting);
};
return (
<>
<Collapsible id={`${isBase ? "base_" : ""}settings`} title="Global settings">
<div className={`base__actions`}>
<VsCheckbox label="Auto-update modified date" checked={modifiedDateUpdate} onClick={onDateCheck} />
</div>
<div className={`base__actions`}>
<VsCheckbox label="Highlight Front Matter" checked={fmHighlighting} onClick={onHighlightCheck} />
</div>
</Collapsible>
</>
);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export interface ITemplateIconProps {}
export const TemplateIcon: React.FunctionComponent<ITemplateIconProps> = (props: React.PropsWithChildren<ITemplateIconProps>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.3} d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
</svg>
);
};

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
export interface IWritingIconProps {}
export const WritingIcon: React.FunctionComponent<IWritingIconProps> = (props: React.PropsWithChildren<IWritingIconProps>) => {
return (
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fillRule="evenodd" clipRule="evenodd" d="M7.223 10.933c.326.192.699.29 1.077.282a2.159 2.159 0 0 0 1.754-.842 3.291 3.291 0 0 0 .654-2.113 2.886 2.886 0 0 0-.576-1.877 1.99 1.99 0 0 0-1.634-.733 2.294 2.294 0 0 0-1.523.567V3.475h-.991V11.1h.995v-.344c.076.066.158.125.244.177zM7.85 6.7c.186-.079.388-.113.59-.1a1.08 1.08 0 0 1 .896.428c.257.363.382.802.357 1.245a2.485 2.485 0 0 1-.4 1.484 1.133 1.133 0 0 1-.96.508 1.224 1.224 0 0 1-.976-.417A1.522 1.522 0 0 1 6.975 8.8v-.6a1.722 1.722 0 0 1 .393-1.145c.13-.154.296-.276.482-.355zM3.289 5.675a3.03 3.03 0 0 0-.937.162 2.59 2.59 0 0 0-.8.4l-.1.077v1.2l.423-.359a2.1 2.1 0 0 1 1.366-.572.758.758 0 0 1 .661.282c.15.232.23.503.231.779L2.9 7.825a2.6 2.6 0 0 0-1.378.575 1.65 1.65 0 0 0-.022 2.336 1.737 1.737 0 0 0 1.253.454 1.96 1.96 0 0 0 1.107-.332c.102-.068.197-.145.286-.229v.444h.941V7.715a2.193 2.193 0 0 0-.469-1.5 1.687 1.687 0 0 0-1.329-.54zm.857 3.041c.02.418-.12.829-.391 1.148a1.221 1.221 0 0 1-.955.422.832.832 0 0 1-.608-.2.833.833 0 0 1 0-1.091c.281-.174.6-.277.93-.3l1.02-.148.004.169zm8.313 2.317c.307.13.64.193.973.182.495.012.983-.114 1.41-.365l.123-.075.013-.007V9.615l-.446.32c-.316.224-.696.34-1.084.329A1.3 1.3 0 0 1 12.4 9.8a1.975 1.975 0 0 1-.4-1.312 2.01 2.01 0 0 1 .453-1.381A1.432 1.432 0 0 1 13.6 6.6a1.8 1.8 0 0 1 .971.279l.43.265V5.97l-.17-.073a2.9 2.9 0 0 0-1.17-.247 2.52 2.52 0 0 0-1.929.817 2.9 2.9 0 0 0-.747 2.049c-.028.707.21 1.4.67 1.939.222.249.497.446.804.578z"/></svg>
);
};

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
export interface IOtherActionButtonProps {
className?: string;
disabled?: boolean;
onClick: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
}
export const OtherActionButton: React.FunctionComponent<IOtherActionButtonProps> = ({ className, disabled, onClick, children}: React.PropsWithChildren<IOtherActionButtonProps>) => {
return (
<div className={`ext_link_block`}>
<button onClick={onClick} className={className || ""} disabled={disabled}>{children}</button>
</div>
);
};

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { BugIcon } from './Icons/BugIcon';
import { FileIcon } from './Icons/FileIcon';
import { FolderOpenedIcon } from './Icons/FolderOpenedIcon';
import { SettingsIcon } from './Icons/SettingsIcon';
import { TemplateIcon } from './Icons/TemplateIcon';
import { WritingIcon } from './Icons/WritingIcon';
import { OtherActionButton } from './OtherActionButton';
export interface IOtherActionsProps {
isFile: boolean;
settings: PanelSettings | undefined;
isBase?: boolean;
}
export const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, settings, isBase}: React.PropsWithChildren<IOtherActionsProps>) => {
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
};
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openFile);
};
const openProject = () => {
MessageHelper.sendMessage(CommandToCode.openProject);
};
const createAsTemplate = () => {
MessageHelper.sendMessage(CommandToCode.createTemplate);
};
const toggleWritingSettings = () => {
MessageHelper.sendMessage(CommandToCode.toggleWritingSettings);
};
return (
<>
<Collapsible id={`${isBase ? "base_" : ""}other_actions`} title="Other actions" className={`other_actions`}>
<OtherActionButton className={settings?.writingSettingsEnabled ? "active" : ""} onClick={toggleWritingSettings} disabled={typeof settings?.writingSettingsEnabled === "undefined"}><WritingIcon /> <span>{settings?.writingSettingsEnabled ? "Writing settings enabled" : "Enable writing settings"}</span></OtherActionButton>
<OtherActionButton onClick={createAsTemplate} disabled={!isFile}><TemplateIcon /> <span>Create as template</span></OtherActionButton>
<OtherActionButton onClick={openSettings}><SettingsIcon /> <span>Open settings</span></OtherActionButton>
<OtherActionButton onClick={openFile} disabled={!isFile}><FileIcon /> <span>Reveal file in folder</span></OtherActionButton>
<OtherActionButton onClick={openProject}><FolderOpenedIcon /> <span>Reveal project folder</span></OtherActionButton>
<div className="ext_link_block">
<a href="https://github.com/estruyf/vscode-front-matter/issues" title="Open an issue on GitHub"><BugIcon /> <span>Report an issue</span></a>
</div>
</Collapsible>
</>
);
};

View File

@@ -3,6 +3,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface IPublishActionProps {
draft: boolean;
@@ -16,8 +17,6 @@ export const PublishAction: React.FunctionComponent<IPublishActionProps> = (prop
};
return (
<div className={`article__action`}>
<button onClick={publish} className={`${draft ? "" : "secondary"}`}>{draft ? "Publish" : "Revert to draft"}</button>
</div>
<ActionButton onClick={publish} className={`${draft ? "" : "secondary"}`} title={draft ? "Publish" : "Revert to draft"} />
);
};

View File

@@ -13,8 +13,7 @@ export const SeoFieldInfo: React.FunctionComponent<ISeoFieldInfoProps> = ({ titl
return (
<VsTableRow>
<VsTableCell className={`table__cell table__title`}>{title}</VsTableCell>
<VsTableCell className={`table__cell`}>{value}</VsTableCell>
<VsTableCell className={`table__cell`}>{recommendation}</VsTableCell>
<VsTableCell className={`table__cell`}>{value}/{recommendation}</VsTableCell>
<VsTableCell className={`table__cell table__cell__validation`}>
{ isValid !== undefined ? <ValidInfo isValid={isValid} /> : <span>-</span> }
</VsTableCell>

View File

@@ -2,11 +2,9 @@ import * as React from 'react';
import { SEO } from '../../models/PanelSettings';
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';
import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents';
export interface ISeoStatusProps {
seo: SEO;
@@ -17,6 +15,7 @@ export const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React
const { data, seo } = props;
const { title } = data;
const [ isOpen, setIsOpen ] = React.useState(true);
const tableRef = React.useRef<HTMLElement>();
const { descriptionField } = seo;
@@ -34,29 +33,28 @@ export const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React
<div className={`seo__status__details`}>
<h4>Recommendations</h4>
<VsTable bordered>
<VsTable ref={tableRef} bordered zebra>
<VsTableHeader slot="header">
<VsTableHeaderCell className={`table__cell`}>Property</VsTableHeaderCell>
<VsTableHeaderCell className={`table__cell`}>Length</VsTableHeaderCell>
<VsTableHeaderCell className={`table__cell`}>Recommended</VsTableHeaderCell>
<VsTableHeaderCell className={`table__cell`}>Valid</VsTableHeaderCell>
</VsTableHeader>
<VsTableBody slot="body">
{
(title && seo.title > 0) && (
<SeoFieldInfo title={`title`} value={`${title.length} chars`} recommendation={`${seo.title} chars`} isValid={title.length <= seo.title} />
<SeoFieldInfo title={`title`} value={title.length} recommendation={`${seo.title} chars`} isValid={title.length <= seo.title} />
)
}
{
(data[descriptionField] && seo.description > 0) && (
<SeoFieldInfo title={descriptionField} value={`${data[descriptionField].length} chars`} recommendation={`${seo.description} chars`} isValid={data[descriptionField].length <= seo.description} />
<SeoFieldInfo title={descriptionField} value={data[descriptionField].length} recommendation={`${seo.description} chars`} isValid={data[descriptionField].length <= seo.description} />
)
}
{
(seo.content > 0 && data?.articleDetails?.wordCount) && (
<SeoFieldInfo title={`Article length`} value={`${data?.articleDetails?.wordCount} words`} recommendation={`${seo.content} words`} />
(seo.content > 0 && data?.articleDetails?.wordCount > 0) && (
<SeoFieldInfo title={`Article length`} value={data?.articleDetails?.wordCount} recommendation={`${seo.content} words`} />
)
}
</VsTableBody>
@@ -74,8 +72,23 @@ export const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React
);
};
// Workaround for lit components not updating render
React.useEffect(() => {
setTimeout(() => {
let height = 0;
tableRef.current?.childNodes.forEach((elm: any) => {
height += elm.clientHeight;
});
if (height > 0 && tableRef.current) {
tableRef.current.style.height = `${height}px`;
}
}, 10);
}, [title, data[descriptionField], data?.articleDetails?.wordCount]);
return (
<Collapsible title="SEO Status" sendUpdate={(value) => setIsOpen(value)}>
<Collapsible id={`seo`} title="SEO Status" sendUpdate={(value) => setIsOpen(value)}>
{ renderContent() }
</Collapsible>
);

View File

@@ -3,6 +3,7 @@ import { SlugHelper } from '../../helpers/SlugHelper';
import { Slug } from '../../models/PanelSettings';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface ISlugActionProps {
value: string;
@@ -21,8 +22,6 @@ export const SlugAction: React.FunctionComponent<ISlugActionProps> = (props: Rea
};
return (
<div className={`article__action`}>
<button onClick={optimize} disabled={crntValue === slug}>Optimize slug</button>
</div>
<ActionButton onClick={optimize} disabled={crntValue === slug} title={`Optimize slug`} />
);
};

View File

@@ -126,7 +126,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
}, [crntSelected]);
return (
<div className={`section article__tags`}>
<div className={`article__tags`}>
<h3>{icon} {type}</h3>
<Downshift ref={dsRef}
@@ -143,12 +143,6 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (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();
@@ -175,7 +169,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
<ul className={`article__tags__dropbox ${isOpen ? "open" : "closed" }`} {...getMenuProps()}>
{
isOpen ? options.filter((option) => filterList(option, inputValue)).map((item, index) => (
<li {...getItemProps({ key: item, index, item })} >
<li {...getItemProps({ key: item, index, item })}>
{ item }
</li>
)) : null

View File

@@ -6,4 +6,5 @@ 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`);
export const VsCollapsible = wrapWc(`vscode-collapsible`);
export const VsCheckbox = wrapWc(`vscode-checkbox`);

View File

@@ -10,6 +10,7 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-body';
import '@bendera/vscode-webview-elements/dist/vscode-table-row';
import '@bendera/vscode-webview-elements/dist/vscode-table-cell';
import '@bendera/vscode-webview-elements/dist/vscode-collapsible';
import '@bendera/vscode-webview-elements/dist/vscode-checkbox';
declare const acquireVsCodeApi: <T = unknown>() => {
getState: () => T;

View File

@@ -1,4 +1,5 @@
import { SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD } from './../constants/settings';
import { Template } from './../commands/Template';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME } 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";
@@ -13,6 +14,9 @@ import { exec } from 'child_process';
import * as path from 'path';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Content } from 'mdast';
import { Notifications } from '../helpers/Notifications';
import { COMMAND_NAME } from '../constants/Extension';
import { Folders } from '../commands/Folders';
export class ExplorerView implements WebviewViewProvider, Disposable {
@@ -74,7 +78,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
webviewView.onDidDispose(() => { webviewView.webview.html = ""; }, this),
);
webviewView.webview.onDidReceiveMessage(msg => {
webviewView.webview.onDidReceiveMessage(async (msg) => {
switch(msg.command) {
case CommandToCode.getData:
this.getSettings();
@@ -111,7 +115,11 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
commands.executeCommand('workbench.action.openSettings', '@ext:eliostruyf.vscode-front-matter');
break;
case CommandToCode.openFile:
commands.executeCommand('revealFileInOS');
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
commands.executeCommand('remote-wsl.revealInExplorer');
} else {
commands.executeCommand('revealFileInOS');
}
break;
case CommandToCode.runCustomScript:
this.runCustomScript(msg);
@@ -124,11 +132,35 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
exec(`open ${wsPath}`);
} else if (os.type() === "Windows_NT") {
exec(`explorer ${wsPath}`);
} else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
exec('explorer.exe `wslpath -w "$PWD"`');
} else {
exec(`xdg-open ${wsPath}`);
}
}
break;
case CommandToCode.initProject:
await commands.executeCommand(COMMAND_NAME.init);
this.getSettings();
break;
case CommandToCode.createContent:
await commands.executeCommand(COMMAND_NAME.createContent);
break;
case CommandToCode.createTemplate:
await commands.executeCommand(COMMAND_NAME.createTemplate);
break;
case CommandToCode.updateModifiedUpdating:
this.updateModifiedUpdating(msg.data || false);
break;
case CommandToCode.toggleWritingSettings:
this.toggleWritingSettings();
break;
case CommandToCode.updateFmHighlight:
this.updateFmHighlight((msg.data !== null && msg.data !== undefined) ? msg.data : false);
break;
case CommandToCode.toggleCenterMode:
await commands.executeCommand(`workbench.action.toggleCenteredLayout`);
break;
}
});
@@ -155,10 +187,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* @param metadata
*/
public pushMetadata(metadata: any) {
const articleDetails = this.getArticleDetails();
if (articleDetails) {
metadata.articleDetails = articleDetails;
}
this.postWebviewMessage({ command: Command.metadata, data: {
...metadata,
articleDetails: this.getArticleDetails()
} });
...metadata
}});
}
/**
@@ -173,6 +210,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Trigger all sections to close
*/
public collapseAll() {
this.postWebviewMessage({ command: Command.closeSections });
}
/**
* Run a custom script
* @param msg
@@ -200,7 +244,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
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}`);
Notifications.error(`${msg?.data?.title}: ${error.message}`);
return;
}
@@ -218,7 +262,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
/**
* Retrieve the extension settings
*/
private getSettings() {
private async getSettings() {
const config = workspace.getConfiguration(CONFIG_KEY);
this.postWebviewMessage({
@@ -232,12 +276,18 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
},
slug: {
prefix: config.get(SETTING_SLUG_PREFIX) || "",
suffix: config.get(SETTING_SLUG_SUFFIX) || ""
suffix: config.get(SETTING_SLUG_SUFFIX) || "",
updateFileName: !!config.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
},
tags: config.get(SETTING_TAXONOMY_TAGS) || [],
categories: config.get(SETTING_TAXONOMY_CATEGORIES) || [],
freeform: config.get(SETTING_PANEL_FREEFORM),
scripts: config.get(SETTING_CUSTOM_SCRIPTS)
scripts: config.get(SETTING_CUSTOM_SCRIPTS),
isInitialized: await Template.isInitialized(),
contentInfo: await Folders.getInfo() || null,
modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: config.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
} as PanelSettings
});
}
@@ -252,10 +302,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
const article = ArticleHelper.getFrontMatter(editor);
this.postWebviewMessage({ command: Command.metadata, data: {
...article!.data,
articleDetails: this.getArticleDetails()
}});
this.pushMetadata(article!.data);
}
/**
@@ -273,10 +320,7 @@ 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,
articleDetails: this.getArticleDetails()
}});
this.pushMetadata(article!.data);
}
}
@@ -306,7 +350,11 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
private getArticleDetails() {
const editor = window.activeTextEditor;
if (!editor) {
return "";
return null;
}
if (!ArticleHelper.isMarkdownFile()) {
return null;
}
const article = ArticleHelper.getFrontMatter(editor);
@@ -353,6 +401,58 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Toggle the writing settings
*/
private async toggleWritingSettings() {
const config = workspace.getConfiguration("", { languageId: "markdown" });
const enabled = this.isWritingSettingsEnabled();
await config.update("editor.fontSize", enabled ? undefined : 14, false, true);
await config.update("editor.lineHeight", enabled ? undefined : 26, false, true);
await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true);
await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true);
await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true);
await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true);
await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true);
this.getSettings();
}
/**
* Check if the writing settings are enabled
*/
private isWritingSettingsEnabled() {
const config = workspace.getConfiguration("", { languageId: "markdown" });
const fontSize = config.get("editor.fontSize");
const lineHeight = config.get("editor.lineHeight");
const wordWrap = config.get("editor.wordWrap");
const wordWrapColumn = config.get("editor.wordWrapColumn");
const lineNumbers = config.get("editor.lineNumbers");
const quickSuggestions = config.get<boolean>("editor.quickSuggestions");
return fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined;
}
/**
* Toggle the Front Matter highlighting
*/
private async updateFmHighlight(autoUpdate: boolean) {
const config = workspace.getConfiguration(CONFIG_KEY);
await config.update(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, autoUpdate);
this.getSettings();
}
/**
* Toggle the modified auto-update setting
*/
private async updateModifiedUpdating(autoUpdate: boolean) {
const config = workspace.getConfiguration(CONFIG_KEY);
await config.update(SETTING_AUTO_UPDATE_DATE, autoUpdate);
this.getSettings();
}
/**
* Post data to the panel
* @param msg
@@ -361,7 +461,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
this.panel!.webview.postMessage(msg);
}
/**
* Generate a unique nonce
*/
private getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
@@ -387,7 +489,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webView.cspSource} 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; style-src ${webView.cspSource} 'self' 'unsafe-inline'; font-src ${webView.cspSource}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; style-src ${webView.cspSource} 'self' 'unsafe-inline'; font-src ${webView.cspSource}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleResetUri}" rel="stylesheet">
<link href="${styleVSCodeUri}" rel="stylesheet">
@@ -398,6 +500,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
<body>
<div id="app"></div>
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759" alt="Daily usage" />
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>