Compare commits

...

23 Commits

Author SHA1 Message Date
Elio Struyf
6395e471f0 2.5.1 2021-08-23 08:51:09 +02:00
Elio Struyf
59f0351b79 Fix typo 2021-08-23 08:50:59 +02:00
Elio Struyf
1b16fa69d0 Update docs 2021-08-21 10:23:54 +02:00
Elio Struyf
e8f67590bc Updated image 2021-08-19 14:22:22 +02:00
Elio Struyf
b34a8feb63 changelog update 2021-08-19 14:09:02 +02:00
Elio Struyf
fcbca1e48a Fix typo 2021-08-19 14:08:44 +02:00
Elio Struyf
4585790b45 updated changelog 2021-08-19 14:08:00 +02:00
Elio Struyf
445b64a2ec 2.5.0 2021-08-19 14:07:47 +02:00
Elio Struyf
5d18d09b72 #60 - site preview + refactoring 2021-08-19 13:34:15 +02:00
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
60 changed files with 952 additions and 123 deletions

View File

@@ -1,5 +1,27 @@
# Change Log
## [2.5.1] - 2020-08-23
- Fix typo in the `package.json` file for the preview command
## [2.5.0] - 2020-08-19
- Moved the center layout button to the other actions section
- [#60](https://github.com/estruyf/vscode-front-matter/issues/60): Added the ability to open a site preview in VS Code
## [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

View File

@@ -1,6 +1,6 @@
<h1 align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter">
<img alt="Front Matter" src="./assets/front-matter.png">
<img alt="Front Matter" src="./assets/frontmatter-128x128.png">
</a>
</h1>
@@ -24,19 +24,31 @@ This VSCode extension simplifies working with your markdown articles' front matt
The extension will automatically verify if your title and description are SEO compliant. If this would not be the case, it will give you a warning.
<p align="center">
<img src="./assets/v2.5.0/site-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 2**
In version v2.0.0 we released the newly redesigned sidebar panel with improved SEO support. This extension makes it the only extension to manage your Markdown pages for your static sites in Visual Studio Code.
<p align="center" style="margin-top: 2rem;">
<a href="https://www.producthunt.com/posts/front-matter?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-front-matter" target="_blank">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.png?post_id=309033&theme=dark" alt="Front Matter - Managing your static sites straight from within VS Code | Product Hunt" style="width: 250px; height: 40px;" />
</a>
</p>
<h2 id="table-of-contents">Table of Contents</h2>
<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="#site-preview">Site preview</a></li>
<li><a href="#custom-actions">Custom actions/scripts</a></li>
<li><a href="#creating-articles-from-templates">Create articles from templates</a></li>
<li><a href="#syntax-highlighting-for-hugo-shortcodes">Syntax highlighting for Hugo Shortcodes</a></li>
<li><a href="#available-commands">Available commands</a></li>
@@ -45,6 +57,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.
@@ -56,7 +86,7 @@ To leverage most of the capabilities of the extension. SEO information and every
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.3.0/baseview.png" alt="Base view" style="display: inline-block" />
<img src="./assets/v2.5.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.
@@ -66,7 +96,7 @@ When you open the Front Matter panel on a Markdown file, you get to see the foll
**Global Settings**
<p align="center">
<img src="./assets/v2.3.0/global-settings.png.png" alt="Global settings" style="display: inline-block" />
<img src="./assets/v2.5.0/global-settings.png" alt="Global settings" style="display: inline-block" />
</p>
**SEO Status**
@@ -78,9 +108,11 @@ When you open the Front Matter panel on a Markdown file, you get to see the foll
**Actions**
<p align="center">
<img src="./assets/v2.0.0/actions.png" alt="Actions" style="display: inline-block" />
<img src="./assets/v2.5.0/actions.png" alt="Actions" style="display: inline-block" />
</p>
> **Info**: To gain the `open preview` button to show up, you will need to first set the `local preview URL`. You can do this within the `Global Settings` section or by updating the `frontMatter.preview.host` setting.
**Metadata: Keywords, Tags, Categories**
<p align="center">
@@ -92,9 +124,23 @@ When you open the Front Matter panel on a Markdown file, you get to see the foll
**Other actions**
<p align="center">
<img src="./assets/v2.3.0/other-actions.png" alt="Other actions" style="display: inline-block" />
<img src="./assets/v2.5.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.
## Site preview
The Markdown preview is not consistently delivering the same result as the one you will see on your site. The Front Matter extension provides you a way to show the site result. For this, you will first have to set the `frontMatter.preview.host` setting. You can perform it from the `Global Settings` section in the panel or in your `settings.json` file to set the setting.
For example, with Hugo, the local server spins up on `http://localhost:1313`. When you set this URL as the value of the `frontMatter.preview.host` setting. You can click on the `open preview` button and the site preview will be shown.
<p align="center">
<img src="./assets/v2.5.0/site-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> **Important**: Be sure to spin up your local server. This is an action the extension currently doesn't do for you. If you want, you create yourself a custom action in order to start it.
## 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.
@@ -220,6 +266,10 @@ You can also specify a prefix and suffix, which can be added to the slug if you
> **Info**: At the moment, the extension only supports English stopwords.
**Front Matter: Preview article**
Opens the site preview for the current article. More information about it can be found in the [site preview](#site-preview) section.
### Usage
- Start by opening the command prompt:
@@ -373,12 +423,44 @@ Specify if you want to automatically update the modification date of your markdo
}
```
### `frontMatter.content.fmHighlight`
Specify if you want to highlight the Front Matter in the Markdown file. Default: `true`.
```json
{
"frontMatter.content.fmHighlight": true
}
```
### `frontMatter.preview.host`
Specify the host URL (example: http://localhost:1313) to be used when opening the preview.
```json
{
"frontMatter.preview.host": ""
}
```
### `frontMatter.preview.pathName`
Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM` (if not set via the slug). The date will be generated based on the article its date field value.
```json
{
"frontMatter.preview.pathName": ""
}
```
> **Important**: As the value will be formatted with the article's date, it will try to convert all characters you enter. In case you wan to skip some characters or all of them, you need to wrap that part between two single quotes. Example: `"'blog/'yyyy/MM"` will result in: `blog/2021/08`.
## Feedback / issues / ideas
Please submit them via creating an issue in the project repository: [issue list](https://github.com/estruyf/vscode-front-matter/issues).
<p align="center">
<a href="#">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" />
<a href="https://visitorbadge.io">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
</a>
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 28 28" style="enable-background:new 0 0 28 28;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#FFFFFF;}
.st2{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
.st3{fill:none;}
</style>
<g class="st0">
<path class="st1" d="M4,11.4H2.2V2.9h3.2v2H4v1.2h1.3V8H4V11.4z"/>
<path class="st1" d="M10.9,11.4H9l-0.9-3c0-0.1,0-0.1,0-0.2C8,8.1,8,8,7.9,7.8v0.6v3H6.1V2.9H8c0.8,0,1.4,0.2,1.9,0.6
c0.5,0.5,0.8,1.3,0.8,2.2c0,1-0.4,1.7-1.1,2.1L10.9,11.4z M8,6.8h0.1c0.2,0,0.4-0.1,0.5-0.3C8.7,6.3,8.8,6,8.8,5.7
c0-0.6-0.3-1-0.8-1l0,0V6.8z"/>
<path class="st1" d="M16.5,7.2c0,1.3-0.2,2.4-0.7,3.2c-0.5,0.8-1.1,1.2-1.8,1.2s-1.2-0.3-1.7-0.9c-0.6-0.8-0.9-2-0.9-3.5
s0.3-2.7,0.9-3.5c0.5-0.6,1-0.9,1.7-0.9c0.8,0,1.4,0.4,1.9,1.2C16.2,4.7,16.5,5.8,16.5,7.2z M14.6,7.2c0-1.5-0.2-2.3-0.7-2.3
c-0.2,0-0.4,0.2-0.5,0.6s-0.2,0.9-0.2,1.7c0,0.7,0.1,1.3,0.2,1.7c0.1,0.4,0.3,0.6,0.5,0.6s0.4-0.2,0.5-0.6
C14.5,8.4,14.6,7.9,14.6,7.2z"/>
<path class="st1" d="M17.2,11.4V2.9H19l0.9,3C20,6,20,6.2,20.1,6.5c0.1,0.2,0.1,0.5,0.2,0.8L20.5,8c-0.1-0.7-0.1-1.4-0.2-1.9
s-0.1-1-0.1-1.3V2.9H22v8.5h-1.7l-0.9-3.1c-0.1-0.3-0.2-0.6-0.3-0.9S19,6.8,18.9,6.6c0,0.6,0.1,1.1,0.1,1.6c0,0.4,0,0.8,0,1.2v2.2
h-1.8V11.4z"/>
<path class="st1" d="M25.3,11.4h-1.8V4.9h-1v-2h3.9v2h-1.1V11.4z"/>
</g>
<rect class="st2" width="28" height="28"/>
<g class="st0">
<path class="st1" d="M2.9,17h0.9l0.6,3c0.1,0.4,0.2,0.8,0.2,1.2c0.1,0.4,0.1,0.8,0.2,1.2c0-0.1,0-0.1,0-0.1v-0.1L5,21.3l0.1-0.8
L5.2,20l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3s0-0.2,0-0.2v-1v-0.9l0,0c0,0,0,0,0-0.1v0.2c0,0.2,0,0.3-0.1,0.5
s0,0.2-0.1,0.3l-0.1,0.7v0.3l-0.6,3.3H4.5l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2l-0.3,5.2h-1L2.9,17z"/>
<path class="st1" d="M9.3,17h0.8l1.6,7.5h-1L10.4,23H8.9l-0.3,1.5h-1L9.3,17z M10.3,22.2L10,21c-0.1-0.8-0.3-1.7-0.4-2.6
c0,0.5-0.1,0.9-0.2,1.4c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1L10.3,22.2L10.3,22.2z"/>
<path class="st1" d="M11.5,17h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V17z"/>
<path class="st1" d="M14.8,17h3.3v0.9H17v6.7h-1v-6.7h-1.2V17z"/>
<path class="st1" d="M18.7,17h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V17z"/>
<path class="st1" d="M22.3,17h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6
l1.4,3.7h-1l-1.4-3.7v3.7h-1L22.3,17L22.3,17z M23.3,20.3c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6
s-0.1-0.3-0.2-0.4s-0.2-0.2-0.3-0.2c-0.1,0-0.3-0.1-0.4-0.1h-0.2v2.5H23.3z"/>
</g>
<rect x="-33.5" y="14" class="st3" width="8.6" height="14"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 28 28" style="color:#ad0670" xml:space="preserve">
<style type="text/css">
.st1 {
fill: none;
stroke: #ad0670;
stroke-width: 2;
stroke-miterlimit: 10;
}
</style>
<g class="st0">
<path fill="currentcolor" d="M4,11.4H2.2V2.9h3.2v2H4v1.2h1.3V8H4V11.4z" />
<path fill="currentcolor" d="M10.9,11.4H9l-0.9-3c0-0.1,0-0.1,0-0.2C8,8.1,8,8,7.9,7.8l0,0.6v3H6.1V2.9H8c0.8,0,1.4,0.2,1.9,0.6
c0.5,0.5,0.8,1.3,0.8,2.2c0,1-0.4,1.7-1.1,2.1L10.9,11.4z M8,6.8h0.1c0.2,0,0.4-0.1,0.5-0.3C8.7,6.3,8.8,6,8.8,5.7
c0-0.6-0.3-1-0.8-1H8V6.8z" />
<path fill="currentcolor" d="M16.5,7.2c0,1.3-0.2,2.4-0.7,3.2c-0.5,0.8-1.1,1.2-1.8,1.2c-0.7,0-1.2-0.3-1.7-0.9c-0.6-0.8-0.9-2-0.9-3.5
c0-1.5,0.3-2.7,0.9-3.5c0.5-0.6,1-0.9,1.7-0.9c0.8,0,1.4,0.4,1.9,1.2C16.2,4.7,16.5,5.8,16.5,7.2z M14.6,7.2c0-1.5-0.2-2.3-0.7-2.3
c-0.2,0-0.4,0.2-0.5,0.6c-0.1,0.4-0.2,0.9-0.2,1.7c0,0.7,0.1,1.3,0.2,1.7c0.1,0.4,0.3,0.6,0.5,0.6c0.2,0,0.4-0.2,0.5-0.6
C14.5,8.4,14.6,7.9,14.6,7.2z" />
<path fill="currentcolor" d="M17.2,11.4V2.9H19l0.9,3C20,6,20,6.2,20.1,6.5c0.1,0.2,0.1,0.5,0.2,0.8L20.5,8c-0.1-0.7-0.1-1.4-0.2-1.9s-0.1-1-0.1-1.3
V2.9H22v8.5h-1.7l-0.9-3.1c-0.1-0.3-0.2-0.6-0.3-0.9s-0.1-0.6-0.2-0.8c0,0.6,0.1,1.1,0.1,1.6c0,0.4,0,0.8,0,1.2v2.2H17.2z" />
<path fill="currentcolor" d="M25.3,11.4h-1.8V4.9h-1v-2h3.9v2h-1.1V11.4z" />
</g>
<rect class="st1" width="28" height="28" />
<g class="st0">
<path fill="currentcolor" d="M2.9,17h0.9L4.4,20c0.1,0.4,0.2,0.8,0.2,1.2c0.1,0.4,0.1,0.8,0.2,1.2c0-0.1,0-0.1,0-0.1c0,0,0-0.1,0-0.1L5,21.3l0.1-0.8
L5.2,20l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3s0-0.2,0-0.2l0-1l0-0.9c0,0,0,0,0,0c0,0,0,0,0-0.1l0,0.2
c0,0.2,0,0.3-0.1,0.5s0,0.2-0.1,0.3l-0.1,0.7l0,0.3l-0.6,3.3H4.5l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2
l-0.3,5.2h-1L2.9,17z" />
<path fill="currentcolor" d="M9.3,17h0.8l1.6,7.5h-1L10.4,23H8.9l-0.3,1.5h-1L9.3,17z M10.3,22.2L10,21c-0.1-0.8-0.3-1.7-0.4-2.6c0,0.5-0.1,0.9-0.2,1.4
c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1H10.3z" />
<path fill="currentcolor" d="M11.5,17h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V17z" />
<path fill="currentcolor" d="M14.8,17h3.3v0.9H17v6.7h-1v-6.7h-1.2V17z" />
<path fill="currentcolor" d="M18.7,17h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V17z" />
<path fill="currentcolor" d="M22.3,17h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6l1.4,3.7h-1
l-1.4-3.7v3.7h-1V17z M23.3,20.3c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6s-0.1-0.3-0.2-0.4
s-0.2-0.2-0.3-0.2c-0.1,0-0.3-0.1-0.4-0.1h-0.2V20.3z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/frontmatter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

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

@@ -268,6 +268,14 @@
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.base__action label {
display: block;
}
.base__action input {
width: 100% !important;
}
.seo__status__details ul > * + *,
.ext_settings > * + * {
--tw-space-y-reverse: 0;
@@ -278,10 +286,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,
@@ -301,6 +320,16 @@
user-select: none;
text-decoration: none;
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,

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "vscode-front-matter",
"version": "2.3.0",
"version": "2.5.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3565,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

@@ -2,8 +2,8 @@
"name": "vscode-front-matter",
"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.3.0",
"icon": "assets/frontmatter-128x128.png",
"version": "2.5.1",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -57,6 +57,8 @@
"onCommand:frontMatter.unregisterFolder",
"onCommand:frontMatter.createContent",
"onCommand:frontMatter.init",
"onCommand:frontMatter.collapseSections",
"onCommand:frontMatter.preview",
"onView:frontMatter.explorer"
],
"main": "./dist/extension",
@@ -84,6 +86,41 @@
"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.preview.host": {
"type": "string",
"default": "",
"markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview."
},
"frontMatter.preview.pathName": {
"type": "string",
"default": "",
"markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value."
},
"frontMatter.taxonomy.dateField": {
"type": "string",
"default": "date",
@@ -94,11 +131,6 @@
"default": "lastmod",
"description": "Specifies the modified date field name to use in your Front Matter"
},
"frontMatter.content.autoUpdateDate": {
"type": "boolean",
"default": false,
"description": "Specify if you want to automatically update the modified date of your article/page."
},
"frontMatter.taxonomy.tags": {
"type": "array",
"description": "Specifies the tags which can be used in the Front Matter"
@@ -175,21 +207,6 @@
"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."
},
"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."
}
}
},
@@ -268,6 +285,20 @@
"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"
}
},
{
"command": "frontMatter.preview",
"title": "Preview article",
"category": "Front matter"
}
],
"menus": {
@@ -296,6 +327,20 @@
{
"command": "frontMatter.createTemplate",
"when": "!frontMatterCanInit"
},
{
"command": "frontMatter.preview",
"when": "frontMatterCanOpenPreview"
},
{
"command": "frontMatter.collapseSections",
"when": "false"
}
],
"view/title": [
{
"command": "frontMatter.collapseSections",
"group": "navigation"
}
]
},

View File

@@ -18,13 +18,13 @@ export class Article {
* @param type
*/
public static async insert(type: TaxonomyType) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
const article = Article.getCurrent();
if (!article) {
return;
}
@@ -101,7 +101,7 @@ export class Article {
* @param article
*/
public static updateDate(article: matter.GrayMatterFile<string>, forceCreate: boolean = false) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
const modField = config.get(SETTING_MODIFIED_FIELD) as string || "date";
@@ -116,7 +116,7 @@ export class Article {
* Sets the article lastmod date
*/
public static async setLastModifiedDate() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
@@ -148,7 +148,7 @@ export class Article {
* Generate the slug based on the article title
*/
public static async generateSlug() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
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;
@@ -231,7 +231,7 @@ export class Article {
const editor = vscode.window.activeTextEditor;
if (txtChanges.length > 0 && editor && ArticleHelper.isMarkdownFile()) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const autoUpdate = config.get(SETTING_AUTO_UPDATE_DATE);
if (autoUpdate) {
@@ -251,6 +251,23 @@ export class Article {
}
}
/**
* Get the current article
*/
private static getCurrent(): matter.GrayMatterFile<string> | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
return article;
}
/**
* Update the article date and return it
* @param article

View File

@@ -6,6 +6,7 @@ import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
import { Notifications } from "../helpers/Notifications";
import { CONTEXT } from "../constants/context";
import { SettingsHelper } from "../helpers";
export class Folders {
@@ -151,7 +152,7 @@ export class Folders {
* @returns
*/
private static get() {
const config = workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const folders: ContentFolder[] = config.get(SETTINGS_CONTENT_FOLDERS) as ContentFolder[];
return folders;
}
@@ -161,7 +162,7 @@ export class Folders {
* @param folders
*/
private static async update(folders: ContentFolder[]) {
const config = workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
await config.update(SETTINGS_CONTENT_FOLDERS, folders);
}
}

137
src/commands/Preview.ts Normal file
View File

@@ -0,0 +1,137 @@
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME } from './../constants/settings';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { join } from "path";
import { commands, env, Uri, ViewColumn, window } from "vscode";
import { SettingsHelper } from '../helpers';
import { PreviewSettings } from '../models';
import { format } from 'date-fns';
import { CONTEXT } from '../constants/context';
export class Preview {
/** 
* Init the preview
*/
public static async init() {
const settings = Preview.getSettings();
await commands.executeCommand('setContext', CONTEXT.canOpenPreview, !!settings.host);
}
/**
* Open the markdown preview in the editor
*/
public static async open(extensionPath: string) {
const settings = Preview.getSettings();
if (!settings.host) {
return;
}
const editor = window.activeTextEditor;
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
let slug = article?.data ? article.data.slug : "";
if (settings.pathname) {
const articleDate = ArticleHelper.getDate(article);
try {
slug = join(format(articleDate || new Date(), settings.pathname), slug);
} catch (error) {
slug = join(settings.pathname, slug);
}
}
// Create the preview webview
const webView = window.createWebviewPanel(
'frontMatterPreview',
'FrontMatter Preview',
{
viewColumn: ViewColumn.Beside,
preserveFocus: true
},
{
enableScripts: true
}
);
webView.iconPath = {
dark: Uri.file(join(extensionPath, 'assets/frontmatter-dark.svg')),
light: Uri.file(join(extensionPath, 'assets/frontmatter.svg'))
}
const localhostUrl = await env.asExternalUri(
Uri.parse(settings.host)
);
const cspSource = webView.webview.cspSource;
webView.webview.html = `<!DOCTYPE html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; frame-src ${localhostUrl} ${cspSource} http: https:; img-src ${localhostUrl} ${cspSource} http: https:; script-src ${localhostUrl} ${cspSource} 'unsafe-inline'; style-src ${localhostUrl} ${cspSource} 'self' 'unsafe-inline' http: https:;"
/>
<style>
html,body {
margin: 0;
padding: 0;
background: white;
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
}
iframe {
width: 100%;
height: calc(100% - 30px);
border: 0;
margin-top: 30px;
}
.slug {
width: 100%;
position: fixed;
height: 30px;
display: flex;
align-items: center;
background-color: var(--vscode-editor-background);
border-bottom: 1px solid var(--vscode-focusBorder);
}
input {
color: var(--vscode-editor-foreground);
padding: 0.25rem 0.5rem;
background: none;
border: 0;
width: 100%;
}
</style>
</head>
<body>
<div class="slug">
<input type="text" value="${join(localhostUrl.toString(), slug)}" disabled />
</div>
<iframe src="${join(localhostUrl.toString(), slug)}" >
</body>
</html>`;
}
/**
* Retrieve all settings related to the preview command
*/
public static getSettings(): PreviewSettings {
const config = SettingsHelper.getConfig();
const host = config.get<string>(SETTING_PREVIEW_HOST);
const pathname = config.get<string>(SETTING_PREVIEW_PATHNAME);
return {
host,
pathname
};
}
}

View File

@@ -22,7 +22,7 @@ export class Settings {
});
if (newOption) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const configSetting = type === TaxonomyType.Tag ? SETTING_TAXONOMY_TAGS : SETTING_TAXONOMY_CATEGORIES;
let options = config.get(configSetting) as string[];
if (!options) {
@@ -72,7 +72,7 @@ export class Settings {
* Export the tags/categories front matter to the user settings
*/
public static async export() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
// Retrieve all the Markdown files
const allMdFiles = await FilesHelper.getMdFiles();
@@ -155,8 +155,6 @@ export class Settings {
* Remap a tag or category to a new one
*/
public static async remap() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const taxType = await vscode.window.showQuickPick([
"Tag",
"Category"

View File

@@ -1,7 +1,6 @@
import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings';
import * as vscode from 'vscode';
import { CONFIG_KEY } from '../constants';
import { ArticleHelper, SeoHelper } from '../helpers';
import { ArticleHelper, SeoHelper, SettingsHelper } from '../helpers';
import { ExplorerView } from '../webview/ExplorerView';
export class StatusListener {
@@ -38,7 +37,7 @@ export class StatusListener {
collection.clear();
// Retrieve the SEO config properties
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const titleLength = config.get(SETTING_SEO_TITLE_LENGTH) as number || -1;
const descLength = config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1;
const fieldName = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description";

View File

@@ -4,7 +4,7 @@ import * as fs from 'fs';
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 { ArticleHelper, SettingsHelper } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
import { CONTEXT } from '../constants/context';
@@ -93,7 +93,7 @@ export class Template {
* Create from a template
*/
public static async create(folderPath: string) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
const prefix = config.get<string>(SETTING_TEMPLATES_PREFIX);
@@ -188,7 +188,7 @@ export class Template {
* Get the folder settings
*/
public static getSettings() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
return folder;
}

View File

@@ -20,5 +20,7 @@ export const COMMAND_NAME = {
registerFolder: getCommandName("registerFolder"),
unregisterFolder: getCommandName("unregisterFolder"),
createContent: getCommandName("createContent"),
createTemplate: getCommandName("createTemplate")
createTemplate: getCommandName("createTemplate"),
collapseSections: getCommandName("collapseSections"),
preview: getCommandName("preview"),
};

View File

@@ -2,5 +2,6 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
canOpenPreview: "frontMatterCanOpenPreview",
registeredFolders: 'frontMatter.registeredFolders'
};

View File

@@ -7,7 +7,6 @@ export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories";
export const SETTING_DATE_FORMAT = "taxonomy.dateFormat";
export const SETTING_DATE_FIELD = "taxonomy.dateField";
export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTING_SLUG_PREFIX = "taxonomy.slugPrefix";
export const SETTING_SLUG_SUFFIX = "taxonomy.slugSuffix";
@@ -28,6 +27,11 @@ export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
export const SETTING_PANEL_FREEFORM = "panel.freeform";
export const SETTING_PREVIEW_HOST = "preview.host";
export const SETTING_PREVIEW_PATHNAME = "preview.pathName";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
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,10 +1,12 @@
import * as vscode from 'vscode';
import { Article, Settings, StatusListener } from './commands';
import { Folders } from './commands/Folders';
import { Preview } from './commands/Preview';
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';
@@ -13,7 +15,9 @@ let statusDebouncer: { (fnc: any, time: number): void; };
let editDebounce: { (fnc: any, time: number): void; };
let collection: vscode.DiagnosticCollection;
export async function activate({ subscriptions, extensionUri }: vscode.ExtensionContext) {
const mdSelector: vscode.DocumentSelector = { language: 'markdown', scheme: 'file' };
export async function activate({ subscriptions, extensionUri, extensionPath }: vscode.ExtensionContext) {
collection = vscode.languages.createDiagnosticCollection('frontMatter');
const explorerSidebar = ExplorerView.getInstance(extensionUri);
@@ -23,6 +27,9 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
}
});
// 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');
@@ -68,7 +75,7 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
if (folderPath) {
Template.create(folderPath);
}
});
});
let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate);
@@ -91,9 +98,15 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
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();
Preview.init();
Folders.updateVsCodeCtx();
});
@@ -114,6 +127,13 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
editDebounce = debounceCallback();
subscriptions.push(vscode.workspace.onDidChangeTextDocument(triggerFileChange));
// Listener for setting changes
subscriptions.push(vscode.workspace.onDidChangeConfiguration(MarkdownFoldingProvider.triggerHighlighting));
// Webview for preview
Preview.init();
subscriptions.push(vscode.commands.registerCommand('frontMatter.preview', () => Preview.open(extensionPath) ));
// Subscribe all commands
subscriptions.push(
insertTags,
@@ -132,7 +152,8 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
registerFolder,
unregisterFolder,
createContent,
projectInit
projectInit,
collapseAll
);
}

View File

@@ -1,9 +1,11 @@
import * as vscode from 'vscode';
import * as matter from "gray-matter";
import * as fs from "fs";
import { CONFIG_KEY, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES } from '../constants';
import { CONFIG_KEY, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES } from '../constants';
import { DumpOptions } from 'js-yaml';
import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine';
import { SettingsHelper } from '.';
import { parse } from 'date-fns';
export class ArticleHelper {
@@ -54,7 +56,7 @@ export class ArticleHelper {
* @param article
*/
public static async update(editor: vscode.TextEditor, article: matter.GrayMatterFile<string>) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const removeQuotes = config.get(SETTING_REMOVE_QUOTES) as string[];
let newMarkdown = this.stringifyFrontMatter(article.content, article.data);
@@ -80,7 +82,7 @@ export class ArticleHelper {
* @param data
*/
public static stringifyFrontMatter(content: string, data: any) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const indentArray = config.get(SETTING_INDENT_ARRAY) as boolean;
const language = getFmLanguage();
@@ -104,4 +106,28 @@ export class ArticleHelper {
const editor = vscode.window.activeTextEditor;
return (editor && editor.document && (editor.document.languageId.toLowerCase() === "markdown" || editor.document.languageId.toLowerCase() === "mdx"));
}
/**
* Get date from front matter
*/
public static getDate(article: matter.GrayMatterFile<string> | null) {
if (!article) {
return;
}
const config = SettingsHelper.getConfig();
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
if (typeof article.data[dateField] !== "undefined") {
if (dateFormat && typeof dateFormat === "string") {
const date = parse(article.data[dateField], dateFormat, new Date());
return date;
} else {
const date = new Date(article.data[dateField]);
return date;
}
}
return;
}
}

View File

@@ -4,6 +4,10 @@ import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY } from '
export class SettingsHelper {
public static getConfig(): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(CONFIG_KEY);
}
/**
* Return the taxonomy settings
*

View File

@@ -1,9 +1,10 @@
import * as vscode from 'vscode';
import * as toml from '@iarna/toml';
import { CONFIG_KEY, SETTING_FRONTMATTER_TYPE } from '../constants';
import { SETTING_FRONTMATTER_TYPE } from '../constants';
import { SettingsHelper } from '.';
export const getFmLanguage = (): string => {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const language = config.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
return language.toLowerCase();
};

View File

@@ -9,6 +9,9 @@ export interface PanelSettings {
isInitialized: boolean;
modifiedDateUpdate: boolean;
contentInfo: FolderInfo[] | null;
writingSettingsEnabled: boolean;
fmHighlighting: boolean;
preview: PreviewSettings;
}
export interface SEO {
@@ -32,4 +35,9 @@ export interface CustomScript {
title: string;
script: string;
nodeBin?: string;
}
export interface PreviewSettings {
host: string | undefined;
pathname: string | undefined;
}

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,63 @@
import { TextEditorDecorationType } from 'vscode';
import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT } from '../constants';
import { SettingsHelper } from '../helpers';
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 = SettingsHelper.getConfig();
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

@@ -16,5 +16,10 @@ export enum CommandToCode {
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",
openPreview = "open-preview",
updatePreviewUrl = "update-preview-url",
}

View File

@@ -43,7 +43,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
settings && metadata && <Actions metadata={metadata} settings={settings} />
}
<Collapsible title="Metadata" className={`inherit z-20`}>
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
@@ -78,7 +78,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
}
</Collapsible>
<OtherActions isFile={true} />
<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

@@ -3,6 +3,7 @@ import { PanelSettings } from '../../models/PanelSettings';
import { Collapsible } from './Collapsible';
import { CustomScript } from './CustomScript';
import { DateAction } from './DateAction';
import { Preview } from './Preview';
import { PublishAction } from './PublishAction';
import { SlugAction } from './SlugAction';
@@ -19,10 +20,13 @@ export const Actions: React.FunctionComponent<IActionsProps> = (props: React.Pro
}
return (
<Collapsible title="Actions">
<Collapsible id={`actions`} title="Actions">
<div className={`article__actions`}>
{ metadata && metadata.title && <SlugAction value={metadata.title} crntValue={metadata.slug} slugOpts={settings.slug} /> }
{ settings?.preview?.host && <Preview slug={metadata.slug} /> }
<DateAction />
{ metadata && typeof metadata.draft !== undefined && <PublishAction draft={metadata.draft} />}

View File

@@ -23,9 +23,9 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<GlobalSettings settings={settings} />
<GlobalSettings settings={settings} isBase />
<Collapsible title="Actions">
<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>
@@ -34,7 +34,7 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
{
settings?.contentInfo && (
<Collapsible title="Content information">
<Collapsible id={`base_content`} title="Content information">
<div className="base__information">
{
settings.contentInfo.map(folder => (
@@ -48,7 +48,7 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
)
}
<OtherActions isFile={false} />
<OtherActions settings={settings} isFile={false} isBase />
</div>
</div>
);

View File

@@ -1,14 +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, className}: 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>) => {
@@ -17,11 +25,29 @@ 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 ${className || ""}`} slot="body">

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

@@ -2,25 +2,66 @@ import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { useDebounce } from '../hooks/useDebounce';
import { Collapsible } from './Collapsible';
import { VsCheckbox } from './VscodeComponents';
import { VsCheckbox, VsLabel } from './VscodeComponents';
export interface IGlobalSettingsProps {
settings: PanelSettings | undefined;
isBase?: boolean;
}
export const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings}: React.PropsWithChildren<IGlobalSettingsProps>) => {
const { modifiedDateUpdate } = settings || {};
export const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings, isBase}: React.PropsWithChildren<IGlobalSettingsProps>) => {
const { modifiedDateUpdate, fmHighlighting } = settings || {};
const [ previewUrl, setPreviewUrl ] = React.useState<string>("");
const [ isDirty, setIsDirty ] = React.useState<boolean>(false);
const debouncePreviewUrl = useDebounce(previewUrl, 1000);
const onCheck = () => {
const onDateCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
};
const onHighlightCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateFmHighlight, !fmHighlighting);
};
const previewChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsDirty(true);
setPreviewUrl(e.currentTarget.value);
};
React.useEffect(() => {
if (settings?.preview.host) {
setPreviewUrl(settings.preview.host);
}
}, [settings?.preview.host]);
React.useEffect(() => {
if (isDirty) {
console.log(debouncePreviewUrl);
setIsDirty(false);
MessageHelper.sendMessage(CommandToCode.updatePreviewUrl, debouncePreviewUrl);
}
}, [debouncePreviewUrl]);
return (
<>
<Collapsible title="Global settings">
<div className={`base__actions`}>
<VsCheckbox label="Auto-update modified date" checked={modifiedDateUpdate} onClick={onCheck} />
<Collapsible id={`${isBase ? "base_" : ""}settings`} className={`base__actions`} title="Global settings">
<div className={`base__action`}>
<VsLabel>Modified date</VsLabel>
<VsCheckbox label="Auto-update modified date" checked={modifiedDateUpdate} onClick={onDateCheck} />
</div>
<div className={`base__action`}>
<VsLabel>Front Matter highlight</VsLabel>
<VsCheckbox label="Highlight Front Matter" checked={fmHighlighting} onClick={onHighlightCheck} />
</div>
<div className={`base__action`}>
<VsLabel>Local preview</VsLabel>
<input
type={`text`}
placeholder="Example: http://localhost:1313"
value={previewUrl}
onChange={previewChange} />
</div>
</Collapsible>
</>

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
export interface ICenterIconProps {}
export const CenterIcon: React.FunctionComponent<ICenterIconProps> = (props: React.PropsWithChildren<ICenterIconProps>) => {
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect x="1.2" y="0.6" stroke="currentcolor" strokeMiterlimit="10" width="13.6" height="14.8"/>
<path stroke="currentcolor" d="M2.6,10V9h10.8v1H2.6z M2.6,6h10.8v1H2.6V6z M13.4,3v1H2.6V3H13.4z M2.6,12v1h10.8v-1H2.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

@@ -1,18 +1,24 @@
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 { CenterIcon } from './Icons/CenterIcon';
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}: React.PropsWithChildren<IOtherActionsProps>) => {
export const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, settings, isBase}: React.PropsWithChildren<IOtherActionsProps>) => {
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
@@ -30,27 +36,29 @@ export const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFil
MessageHelper.sendMessage(CommandToCode.createTemplate);
};
const toggleWritingSettings = () => {
MessageHelper.sendMessage(CommandToCode.toggleWritingSettings);
};
return (
<>
<Collapsible title="Other actions" className={`other_actions`}>
<div className="ext_link_block">
<button onClick={createAsTemplate} disabled={!isFile}><TemplateIcon /> Create as template</button>
</div>
<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={() => MessageHelper.sendMessage(CommandToCode.toggleCenterMode)}>
<CenterIcon /> <span>Toggle center mode</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">
<button onClick={openSettings}><SettingsIcon /> Open settings</button>
</div>
<div className="ext_link_block">
<button onClick={openFile} disabled={!isFile}><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>
<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

@@ -0,0 +1,23 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface IPreviewProps {
slug: string;
}
export const Preview: React.FunctionComponent<IPreviewProps> = ({slug}: React.PropsWithChildren<IPreviewProps>) => {
const open = () => {
MessageHelper.sendMessage(CommandToCode.openPreview);
};
if (!slug) {
return null;
}
return (
<ActionButton onClick={open} title={`Open preview`} />
);
};

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

@@ -88,7 +88,7 @@ export const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React
}, [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

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

View File

@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";
export function useDebounce(value: string, delay: number) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}

View File

@@ -11,6 +11,7 @@ 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';
import '@bendera/vscode-webview-elements/dist/vscode-label';
declare const acquireVsCodeApi: <T = unknown>() => {
getState: () => T;

View File

@@ -1,9 +1,9 @@
import { Template } from './../commands/Template';
import { 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 { 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, SETTING_PREVIEW_HOST } from './../constants/settings';
import * as os from 'os';
import { PanelSettings, CustomScript } from './../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
import { CONFIG_KEY, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { ArticleHelper, SettingsHelper } from "../helpers";
import { Command } from "../viewpanel/Command";
import { CommandToCode } from '../viewpanel/CommandToCode';
@@ -17,6 +17,7 @@ import { Content } from 'mdast';
import { Notifications } from '../helpers/Notifications';
import { COMMAND_NAME } from '../constants/Extension';
import { Folders } from '../commands/Folders';
import { Preview } from '../commands/Preview';
export class ExplorerView implements WebviewViewProvider, Disposable {
@@ -152,6 +153,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
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;
case CommandToCode.openPreview:
await commands.executeCommand(COMMAND_NAME.preview);
break;
case CommandToCode.updatePreviewUrl:
this.updatePreviewUrl(msg.data || "");
break;
}
});
@@ -201,12 +217,19 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Trigger all sections to close
*/
public collapseAll() {
this.postWebviewMessage({ command: Command.closeSections });
}
/**
* Run a custom script
* @param msg
*/
private runCustomScript(msg: { command: string, data: any}) {
const config = workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
const scripts: CustomScript[] | undefined = config.get(SETTING_CUSTOM_SCRIPTS);
if (msg?.data?.title && msg?.data?.script && scripts) {
@@ -247,7 +270,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* Retrieve the extension settings
*/
private async getSettings() {
const config = workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
this.postWebviewMessage({
command: Command.settings,
@@ -269,7 +292,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
scripts: config.get(SETTING_CUSTOM_SCRIPTS),
isInitialized: await Template.isInitialized(),
contentInfo: await Folders.getInfo() || null,
modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false
modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: config.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
preview: Preview.getSettings(),
} as PanelSettings
});
}
@@ -313,7 +339,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
*/
private async addTags(tagType: TagType, value: string) {
if (value) {
const config = workspace.getConfiguration(CONFIG_KEY);
const config = SettingsHelper.getConfig();
let options = tagType === TagType.tags ? config.get<string[]>(SETTING_TAXONOMY_TAGS) : config.get<string[]>(SETTING_TAXONOMY_CATEGORIES);
if (!options) {
@@ -383,8 +409,63 @@ 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;
}
/**
* Update the preview URL
*/
private async updatePreviewUrl(previewUrl: string) {
const config = SettingsHelper.getConfig();
await config.update(SETTING_PREVIEW_HOST, previewUrl);
this.getSettings();
}
/**
* Toggle the Front Matter highlighting
*/
private async updateFmHighlight(autoUpdate: boolean) {
const config = SettingsHelper.getConfig();
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);
const config = SettingsHelper.getConfig();
await config.update(SETTING_AUTO_UPDATE_DATE, autoUpdate);
this.getSettings();
}
@@ -397,7 +478,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
this.panel!.webview.postMessage(msg);
}
/**
* Generate a unique nonce
*/
private getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';