Compare commits

...

121 Commits

Author SHA1 Message Date
Elio Struyf
0ef0fbc3f9 3.0.0 2021-08-27 12:32:09 +02:00
Elio Struyf
95ae50c24e Small optimization for content folder selection 2021-08-27 12:31:50 +02:00
Elio Struyf
3a43c4b5b9 Windows fixes 2021-08-27 12:21:11 +02:00
Elio Struyf
0e73c9bec5 Change order 2021-08-27 11:36:03 +02:00
Elio Struyf
9479f5d53d Updated readme 2021-08-27 11:35:21 +02:00
Elio Struyf
7716d98868 #69 - Welcome screen integration 2021-08-27 11:30:25 +02:00
Elio Struyf
918a33b45e Welcome message updated 2021-08-26 20:40:30 +02:00
Elio Struyf
2c1fd2890c Updates to support the upcoming welcome message 2021-08-26 20:28:43 +02:00
Elio Struyf
939cf94de6 Merge branch 'issue/65' into main 2021-08-26 20:02:08 +02:00
Elio Struyf
a912a7702d Make the dashboard start first 2021-08-26 14:05:59 +02:00
Elio Struyf
b35b0e9515 Added publicFolder info 2021-08-26 13:43:43 +02:00
Elio Struyf
8ad93a6916 Updated readme 2021-08-26 13:36:15 +02:00
Elio Struyf
aac34c96f7 Added sponsor message ❤️ 2021-08-26 13:21:00 +02:00
Elio Struyf
975bb10001 Support filtering, documentation updates 2021-08-26 11:40:18 +02:00
Elio Struyf
6cce35de6c Dashboard updates to support content creation + search 2021-08-25 21:39:26 +02:00
Elio Struyf
722c0d6888 #65 - Dashboard implementation 2021-08-24 21:01:58 +02:00
Elio Struyf
ce8a343ffd Merge branch 'poc-tailwind' 2021-08-24 12:06:44 +02:00
Elio Struyf
5e539a3d14 #64 - Toggle implementation 2021-08-24 10:30:23 +02:00
Elio Struyf
1224316217 Refactoring + optimizing files and folder updates 2021-08-23 21:27:47 +02:00
Elio Struyf
8d7aeb3edd #61 - Implementation of recently modified files 2021-08-23 15:37:51 +02:00
Elio Struyf
c65500b061 Tailwind css 2021-08-23 13:49:01 +02:00
Elio Struyf
8642b8b46a FileList component 2021-08-23 13:48:41 +02:00
Elio Struyf
ea381eac51 Open file updates 2021-08-23 13:22:47 +02:00
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
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
Elio Struyf
d2a4a281a3 2.0.0 2021-07-23 10:26:06 +02:00
Elio Struyf
37021e7a0a Fix 2021-07-23 10:26:03 +02:00
Elio Struyf
02c171d64c Merge branch 'dev' 2021-07-23 10:25:09 +02:00
Elio Struyf
a99f20b9f1 Updated image 2021-07-23 10:21:36 +02:00
Elio Struyf
14d66203d3 Updated dependency for the release 2021-07-23 10:01:25 +02:00
Elio Struyf
6e1b28c59e Removed material UI 2021-07-22 20:13:10 +02:00
Elio Struyf
53a1b19e07 Combine the title, description and artilce length details 2021-07-22 15:58:35 +02:00
Elio Struyf
87e735faa9 Update documentation + images + icons 2021-07-21 19:38:56 +02:00
Elio Struyf
b2f0d51aa2 Added table of contents 2021-07-21 18:16:11 +02:00
Elio Struyf
9a6403a6cd Updated images 2021-07-21 18:07:34 +02:00
Elio Struyf
021b3952ec updated changelog 2021-07-21 16:45:58 +02:00
Elio Struyf
8ddeab7a88 #40 - Implemented keyword checks 2021-07-21 16:45:37 +02:00
Elio Struyf
bea11bf7df Collapsible headers 2021-07-21 14:42:52 +02:00
Elio Struyf
323807c0e1 #41 - Better visualization of the article length 2021-07-21 12:23:46 +02:00
Elio Struyf
d55b122d33 mdx support + styling changes 2021-07-20 18:37:09 +02:00
Elio Struyf
a118b461a7 #41 - word count implementation + extra details 2021-07-20 18:37:01 +02:00
Elio Struyf
c572a821e9 Icon updates 2021-07-20 16:12:29 +02:00
Elio Struyf
a3f18bb143 Merge branch 'master' into dev 2021-07-20 15:18:31 +02:00
Elio Struyf
525a289a2c New panel updates 2021-07-20 15:17:48 +02:00
Elio Struyf
d8d058360b 1.18.0 2021-07-20 12:02:28 +02:00
Elio Struyf
5ccb528e02 Updated changelog 2021-07-20 12:02:22 +02:00
Elio Struyf
d9818f4b2d Update readme 2021-07-20 10:38:28 +02:00
Elio Struyf
4e905d0334 Update title 2021-07-20 10:35:25 +02:00
Elio Struyf
033b08b1bb Remove hr 2021-07-20 10:34:25 +02:00
Elio Struyf
430775649c Update readme 2021-07-20 10:33:59 +02:00
Elio Struyf
0bd714bb02 Added badge 2021-07-13 12:16:25 +02:00
Elio Struyf
0d0289cbf7 Added visitor counter 2021-07-13 12:13:54 +02:00
Elio Struyf
6b21c76332 Updated link 2021-07-07 12:37:33 +02:00
Elio Struyf
59c962d8fc Added sample for custom actions 2021-07-07 12:36:48 +02:00
Elio Struyf
13cb8fcff5 Update changelog 2021-06-28 15:36:17 +02:00
Elio Struyf
4d6317f3bc 1.17.1 2021-06-28 15:35:24 +02:00
Elio Struyf
1b4ce2b925 update changelog 2021-06-28 15:35:18 +02:00
Elio Struyf
8f7f61f2af #34 - Fix date updates 2021-06-28 15:33:08 +02:00
Elio Struyf
f11b884bed #34 - Fix for last modified time on template creation 2021-06-28 14:58:08 +02:00
Elio Struyf
3a968a305f Merge pull request #37 from estruyf/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-06-14 18:22:07 +02:00
dependabot[bot]
847cce915b Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

---
updated-dependencies:
- dependency-name: lodash
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 16:21:16 +00:00
Elio Struyf
704d84454d 1.17.0 2021-06-14 18:20:32 +02:00
Elio Struyf
6d46762e30 #36 - added new option 2021-06-14 18:20:19 +02:00
Elio Struyf
82cf9497ae Merge pull request #32 from estruyf/dependabot/npm_and_yarn/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4
2021-06-14 18:03:36 +02:00
Elio Struyf
d1563c2c23 Merge pull request #33 from estruyf/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-06-14 18:03:27 +02:00
Elio Struyf
7744fdc4b2 Merge pull request #35 from estruyf/dependabot/npm_and_yarn/ssri-6.0.2
Bump ssri from 6.0.1 to 6.0.2
2021-06-14 18:03:18 +02:00
dependabot[bot]
e2042b590a Bump ssri from 6.0.1 to 6.0.2
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-30 03:05:17 +00:00
dependabot[bot]
d8ed7464a1 Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-30 22:54:18 +00:00
dependabot[bot]
cc08ac6f53 Bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-09 16:01:01 +00:00
Elio Struyf
976a473d39 Added a new tag icon instead of the + 2020-12-11 10:10:14 +01:00
165 changed files with 6564 additions and 6834 deletions

12
.vscode/launch.json vendored
View File

@@ -18,6 +18,18 @@
],
"preLaunchTask": "npm: build:ext"
},
{
"name": "Attach Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
},
{
"name": "Extension Tests",
"type": "extensionHost",

View File

@@ -1,5 +1,84 @@
# Change Log
## [3.0.0] - 2020-08-27
- [#61](https://github.com/estruyf/vscode-front-matter/issues/61): List of recently modified files
- [#64](https://github.com/estruyf/vscode-front-matter/issues/64): Publish toggle for easier publishing an article
- [#65](https://github.com/estruyf/vscode-front-matter/issues/65): Aggregate articles in draft
- [#66](https://github.com/estruyf/vscode-front-matter/issues/66): New dashboard webview on which you can manage all your content
- [#69](https://github.com/estruyf/vscode-front-matter/issues/69): Welcome screen for getting started
## [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
- 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
- Sidebar background styling match the VSCode defined sidebar color
- Added support for `mdx` files
- Added support for `enter` press in the combobox
- [#41](https://github.com/estruyf/vscode-front-matter/issues/41): Word count implementation + extra details
- [#40](https://github.com/estruyf/vscode-front-matter/issues/40): Added checks for the keyword usage in title, description, slug, and content
## [1.18.0] - 2020-07-20
- Updated README
## [1.17.1] - 2020-06-28
- [#34](https://github.com/estruyf/vscode-front-matter/issues/34): Fix that last modification date does not update the publication date
- [#38](https://github.com/estruyf/vscode-front-matter/issues/38): Update the last modification date on new page creation from the template
## [1.17.0] - 2020-06-14
- [#36](https://github.com/estruyf/vscode-front-matter/issues/36): Add the option to change the Front Matter its description field
## [1.16.1] - 2020-05-27
- Fix for Node.js v14.16.0

400
README.md
View File

@@ -1,46 +1,235 @@
[![Version](https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter)
&nbsp;&nbsp;
[![Installs](https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter)
&nbsp;&nbsp;
[![Rating](https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg)](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details)
<h1 align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter">
<img alt="Front Matter" src="./assets/frontmatter-128x128.png">
</a>
</h1>
This VSCode extension simplifies working with your markdown articles' front matter when using a static site generator like Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more... For example, you can keep a list of used tags, categories and add/remove them from your article with the extension.
<h2 align="center">Front Matter is an essential Visual Studio Code extension when you want to manage the markdown pages of your static sites.</h2>
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">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter" title="Check it out on the Visual Studio Marketplace">
<img src="https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
</a>
<img src="https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<a href="https://www.buymeacoffee.com/zMeFRy9" title="Buy me a coffee" style="margin-left:10px">
<img src="https://img.shields.io/badge/Buy%20me%20a%20coffee-€%203-blue?logo=buy-me-a-coffee&style=flat" alt="Buy me a coffee" style="display: inline-block" />
</a>
</p>
<p align="center">
<img src="./assets/v3.0.0/welcome-progress.png" alt="Welcome to Front Matter" style="display: inline-block" />
</p>
Front Matter is an essential Visual Studio Code extension that simplifies working and managing your markdown articles. We created the extension to support many static-site generators like Hugo, Jekyll, Hexo, NextJs, Gatsby, and more.
The extension brings Content Management System (CMS) capabilities straight within Visual Studio Code. For example, you can keep a list of the used tags, categories, create content, and so much more.
Our main extension features are:
- Page dashboard where you can get an overview of all your markdown pages. You can use it to search, filter, sort your contents.
- Site preview within Visual Studio Code
- SEO checks for title, description, and keywords
- Support for custom actions/scripts
- and many more
<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.
## FrontMatter Panel (introduced in 1.10.0)
**Version 3**
In version `1.10.0` of this extension, the FrontMatter panel got introduced. This panel allows you to perform most of the extension actions by just a click on the button.
In version v3 we introduced the welcome and dashboard webview. The welcome view allows to get you started using the extension, and the dashboard allows you to manage all your markdown pages in one place. This makes it easy to search, filter, sort, and more.
![FrontMatter Panel](./assets/frontmatter-panel.png)
**Version 2**
In version v2 we released the re-designed 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="#welcome-view">Welcome view</a></li>
<li><a href="#markdown-features">Markdown features</a></li>
<li><a href="#dashboard">Dashboard</a></li>
<li><a href="#the-panel">The panel</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>
<li><a href="#extension-settings">Extension settings</a></li>
<li><a href="#feedback--issues--ideas">Feedback / issues / ideas</a></li>
</ol>
</details>
## Welcome view
The first time you open the dashboard, or when you did not initialize the Front Matter extension yet, the welcome view will get shown.
<p align="center">
<img src="./assets/v3.0.0/welcome.png" alt="Welcome to Front Matter" style="display: inline-block" />
</p>
It also supports light themes:
<p align="center">
<img src="./assets/v3.0.0/welcome-light.png" alt="Welcome view light" style="display: inline-block" />
</p>
## 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`.
## Dashboard
Managing your Markdown pages has never been easier in VS Code. With the Front Matter dashboard, you will be able to view all your pages and search through them, filter, sort, and much more.
<p align="center">
<img src="./assets/v3.0.0/dashboard.png" alt="Dashboard" style="display: inline-block" />
</p>
In order to start using the dashboard, you will have to let the extension know in which folder(s) it can find your pages. A content folder can be registered or unregistered, by right-clicking on the folder in your VSCode explorer panel and clicking on the `Register folder` or `Unregister folder` menu item.
<p align="center">
<img src="./assets/v2.1.0/register-folder.png" alt="Register/unregister a folder" style="display: inline-block" />
</p>
> **Info**: If you want, you can click on the `Open on startup?` checkbox. This setting will allow the dashboard to automatically open when you launch the project in VS Code. It will only apply to the current project, not for all of them.
> **Details**: If your preview images aren't loaded, it might be that you need to configure the `publicFolder` where the extension can find them. For instance, in Hugo this is the `static` folder. You can configure this by updating the `frontMatter.content.publicFolder` setting.
## The panel
The Front Matter panel allows you to perform most of the extension actions by just a click on the button and it shows the SEO statuses of your title, description, and more.
Initially, this panel has been created to make it easier to add tags and categories to your articles as the current VSCode multi-select is not optimal to use.
To leverage most of the capabilities of the extension. SEO information and everyday actions like slug optimization, updating the date, and publish/drafting the article.
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/v3.0.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.5.0/global-settings.png" alt="Global settings" style="display: inline-block" />
</p>
**SEO Status**
<p align="center">
<img src="./assets/v2.0.0/seo.png" alt="SEO article status" style="display: inline-block" />
</p>
**Actions**
<p align="center">
<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**
<p align="center">
<img src="./assets/v3.0.0/metadata.png" alt="Article metadata" style="display: inline-block" />
</p>
> **Info**: By default, the tags/categories picker allows you to insert existing and none tags/categories. When you enter a none existing tag/category, the panel shows an add `+` icon in front of that button. This functionality allows you to store this tag/category in your settings. If you want to disable this feature, you can do that by setting the `frontMatter.panel.freeform` setting to `false`.
Since version `1.15.0`, the extension allows you to create your own custom actions, by running Node.js scripts from your project. In order to use this functionality, you will need to configure the [`frontMatter.custom.scripts`](#frontMatter.custom.scripts) setting for your project.
**Recently Modified**
<p align="center">
<img src="./assets/v3.0.0/recent-files.png" alt="Recently modified files" style="display: inline-block" />
</p>
Navigate quickly to a recently modified file. In the section, the latest 10 modified files get shown. In order to use this functionality, a registered content folder needs to be present. Check [Front Matter: New article from template](#front-matter-new-article-from-template) for more information about how you can register your content folders.
**Other actions**
<p align="center">
<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.
Once a custom action has been configured, it will appear on the Front Matter panel.
![](./assets/custom-actions.png)
<p align="center">
<img src="./assets/v2.0.0/custom-action.png" alt="Custom action" style="display: inline-block" />
</p>
The current workspace-, file-path, and front matter data will be passed as an argument. In your script fetch these arguments as follows:
```javascript
const arguments = process.argv;
const workspaceArg = arguments[2];
const fileArg = arguments[3];
const dataArg = arguments[4];
const data = dataArg && typeof dataArg === "string" ? JSON.parse(dataArg) : null;
if (arguments && arguments.length > 0) {
const workspaceArg = arguments[2]; // The workspace path
const fileArg = arguments[3]; // The file path
const frontMatterArg = arguments[4]; // Front matter data
console.log(`The content returned for your notification.`);
}
```
> A sample file can be found here: [script-sample.js](./sample/script-sample.js)
The output of the script will be passed as a notification, and it allows you to copy the output.
![](./assets/custom-action-notification.png)
<p align="center">
<img src="./assets/custom-action-notification.png" alt="Custom action notification" style="display: inline-block" />
</p>
## Creating articles from templates
@@ -54,58 +243,98 @@ When adding files in the folder, you'll be able to run the `Front Matter: New ar
## Syntax highlighting for Hugo Shortcodes
![Shortcode syntax highlighting](./assets/syntax-highlighting.png)
<p align="center">
<img src="./assets/syntax-highlighting.png" alt="Shortcode syntax highlighting" style="display: inline-block" />
</p>
## Available commands:
## Available commands
**Front Matter: Create <tag | category>**
### 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>
Creates a new <tag | category> and allows you to include it into your post automatically
![Create tag or category](./assets/create-tag-category.gif)
<p align="center">
<img src="./assets/create-tag-category.gif" alt="Create tag or category" style="display: inline-block" />
</p>
**Front Matter: Insert <tags | categories>**
### Front Matter: Insert <tags | categories>
Inserts a selected <tags | categories> into the front matter of your article/post/... - When using this command, the FrontMatter panel opens and focuses on the specified type.
Inserts a selected <tags | categories> into the front matter of your article/post/... - When using this command, the Front Matter panel opens and focuses on the specified type.
> **Info**: This experience changed in version `1.11.0`.
**Front Matter: Export all tags & categories to your settings**
### Front Matter: Export all tags & categories to your settings
Export all the already used tags & categories in your articles/posts/... to your user settings.
**Front Matter: Remap or remove tag/category in all articles**
### Front Matter: Remap or remove tag/category in all articles
This command helps you quickly update/remap or delete a tag or category in your markdown files. The extension will ask you to select the taxonomy type (*tag* or *category*), the old taxonomy value, and the new one (leave the input field *blank* to remove the tag/category).
> **Info**: Once the remapping/deleting process completes. Your VSCode settings update with all new taxonomy tags/categories.
**Front Matter: Set current date**
### Front Matter: Set current date
Update the `date` property of the current article/post/... to the current date & time.
**Optional**: if you want, you can specify the date property format by adding your settings' preference. Settings key: `frontMatter.taxonomy.dateFormat`. Check [date-fns formatting](https://date-fns.org/v2.0.1/docs/format) for more information on which patterns you can use.
> **Optional**: if you want, you can specify the date property format by adding your settings' preference. Settings key: `frontMatter.taxonomy.dateFormat`. Check [date-fns formatting](https://date-fns.org/v2.0.1/docs/format) for more information on which patterns you can use.
**Front Matter: Set lastmod 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`.
**Front Matter: Generate slug based on article title**
### Front Matter: Generate slug based on article title
This command generates a clean slug for your article. It removes known stop words, punctuations, and special characters.
Example:
```
title: Just a sample page with a title
slug: sample-page-title
```
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.
### 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:
- Windows: ⇧+ctrl+P
- Mac: ⇧+⌘+P
- Use one of the commands from above
## Where is the data stored?
The tags and categories are stored in the project VSCode user settings. You can find them back under: `.vscode/settings.json`.
@@ -117,7 +346,7 @@ The tags and categories are stored in the project VSCode user settings. You can
}
```
## Additional extension settings
## Extension settings
The extension has more settings that allow you to configure it to your needs further. Here is a list of settings that you can set:
@@ -140,6 +369,26 @@ Specifies the optimal description length for SEO (set to `-1` to turn it off). D
}
```
### `frontMatter.taxonomy.seoContentLength`
Specifies the optimal minimum length for your articles. Between 1,760 words 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off).
```json
{
"frontMatter.taxonomy.seoContentLength": 1760
}
```
### `frontMatter.taxonomy.seoDescriptionLength`
Specifies the name of the SEO description field for your page. Default is `description`.
```json
{
"frontMatter.taxonomy.seoDescriptionField": "description"
}
```
### `frontMatter.taxonomy.frontMatterType`
Specify which Front Matter language you want to use. The extension supports `YAML` (default) and `TOML`.
@@ -206,13 +455,90 @@ Allows you to specify a title and script path (starting relative from the root o
> **Important**: When the command execution would fail when it cannot find the `node` command. You are able to specify your path to the node app. This is for instance required when using `nvm`.
## Usage
### `frontMatter.content.folders`
- Start by opening the command prompt:
- Windows: ⇧+ctrl+P
- Mac: ⇧+⌘+P
- Use one of the commands from above
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
}
```
### `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`.
### `frontMatter.dashboard.openOnStart`
Specify if you want to open the dashboard when you start VS Code.
```json
{
"frontMatter.dashboard.openOnStart": null
}
```
### `frontMatter.content.publicFolder`
Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder.
```json
{
"frontMatter.content.publicFolder": ""
}
```
## 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="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 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#ad0670"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#ad0670"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#ad0670"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#ad0670"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#ad0670"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#ad0670" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#ad0670"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#ad0670"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#ad0670"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#ad0670"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#ad0670"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#ad0670"/></svg>

After

Width:  |  Height:  |  Size: 2.2 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

@@ -21,6 +21,35 @@
}
}
.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;
box-sizing: border-box;
}
#app, .frontmatter {
height: 100%;
}
.spinner,
.spinner:before,
.spinner:after {
@@ -58,6 +87,13 @@
left: 3.5em;
}
.frontmatter {
padding-top: 0;
padding-bottom: var(--input-margin-vertical);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.frontmatter h3 {
margin-bottom: 1rem;
@@ -69,22 +105,34 @@
margin-bottom: .5rem;
}
.seo__status__details {
margin-bottom: 2rem;
.article__tags h3,
.seo__status h3 {
display: flex;
align-items: center;
}
.section {
box-sizing: border-box;
position: relative;
}
.section h3 svg {
margin-right: 0.5rem;
}
.seo__status__details, .seo__status__keywords {
margin-bottom: 1rem;
}
.collapsible__body h4 {
text-align: center;
font-weight: bold;
}
.not-valid {
color: var(--vscode-errorForeground);
}
.article__actions {
margin-bottom: 2rem;
}
.article__action {
margin-bottom: 1rem;
}
.article__tags {
margin-bottom: 1rem;
}
@@ -118,9 +166,15 @@
.article__tags__input button {
position: absolute;
bottom: 1px;
top: 1px;
right: 1px;
width: 30px;
padding-bottom: 2px;
padding-top: 2px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.article__tags ul {
@@ -151,6 +205,12 @@
margin-top: 1rem;
}
.article__tags__items__item {
display: inline-flex;
margin-bottom: .5rem;
margin-right: .5rem;
}
.article__tags__items__item {
display: inline-block;
margin-bottom: .5rem;
@@ -199,18 +259,251 @@
filter: contrast(60%);
}
.ext_link_block {
margin-bottom: .5rem;
text-align: right;
.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));
}
.base__action label {
display: block;
}
.base__action input {
width: 100% !important;
}
.seo__status__details ul > * + *,
.ext_settings > * + * {
--tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}
.ext_link_block {
display: flex;
align-items: center;
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,
.ext_link_block a {
color: var(--vscode-textLink-foreground);
align-items: center;
color: var(--vscode-button-secondaryForeground);
background-color: var(--vscode-button-secondaryBackground);
border: 0px;
border-radius: 0px;
box-sizing: border-box;
cursor: pointer;
display: inline-flex;
font-size: var(--vscode-font-size);
font-weight: var(--vscode-font-weight);
line-height: 26px;
padding: 0px 14px;
user-select: none;
text-decoration: none;
width: 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,
.ext_link_block a:active,
.ext_link_block a:focus,
.ext_link_block a:visited {
color: var(--vscode-textLink-activeForeground);
color: var(--vscode-button-secondaryForeground);
}
.table__cell {
overflow: hidden;
}
.table__title {
text-transform: capitalize;
}
.table__cell__validation {
text-align: center;
}
.table__cell__validation .valid {
color: #46EC86;
}
.table__cell__validation .warning {
color: #E6AF2E;
}
/* Fields */
.field__toggle {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.field__toggle input {
opacity: 0;
width: 0;
height: 0;
}
.field__toggle__slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--vscode-button-secondaryBackground);
-webkit-transition: .4s;
transition: .4s;
border-radius: 34px;
}
.field__toggle__slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked + .field__toggle__slider {
background-color: var(--vscode-button-background);
}
input:focus + .field__toggle__slider {
box-shadow: 0 0 1px var(--vscode-button-background);
}
input:checked + .field__toggle__slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Metadata */
.metadata_field {
margin-bottom: 1rem;
}
.metadata_field__label {
display: flex;
align-items: center;
margin-bottom: .5rem;
}
.metadata_field__label svg {
margin-right: .5rem;
}
/* File list */
.file_list vscode-label {
border-bottom: 1px solid var(--vscode-foreground);
}
.file_list__items {
padding: 0;
list-style: none;
}
.file_list__items__item {
color: var(--vscode-foreground);
font-size: var(--vscode-font-size);
font-weight: var(--vscode-font-weight);
cursor: pointer;
height: 22px;
line-height: 22px;
margin: 0 -1rem;
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: flex-start;
}
.file_list__items__item:hover {
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-list-hoverForeground);
cursor: pointer;
}
.file_list__items__item svg {
display: block;
flex-shrink: 0;
height: 20px;
width: 20px;
margin-right: .25rem;
}
.file_list__items__item span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* Sponsor */
.sponsor {
opacity: 0.5;
}
.sponsor:hover {
opacity: 1;
}
.sponsor:hover svg {
fill: currentcolor;
}
.sponsor svg {
height: 20px;
width: 20px;
margin-right: .25rem;
}
.sponsor a {
display: flex;
align-items: center;
justify-content: center;
color: var(--vscode-foreground);
text-decoration: none;
}
.sponsor a:hover {
color: var(--vscode-foreground);
text-decoration: none;
}
.sponsor a > span {
margin-right: .25rem;
}

View File

@@ -1,29 +1,26 @@
:root {
--container-paddding: 20px;
--container-padding: 20px;
--input-padding-vertical: 6px;
--input-padding-horizontal: 4px;
--input-margin-vertical: 4px;
--input-margin-horizontal: 0;
}
html, body {
height: 100%;
}
body {
padding: 0 var(--container-paddding);
color: var(--vscode-foreground);
font-size: var(--vscode-font-size);
font-weight: var(--vscode-font-weight);
font-family: var(--vscode-font-family);
background-color: var(--vscode-editor-background);
background-color: var(--vscode-sideBar-background);
}
ol,
ul {
padding-left: var(--container-paddding);
}
body > *,
form > * {
margin-block-start: var(--input-margin-vertical);
margin-block-end: var(--input-margin-vertical);
padding-left: var(--container-padding);
}
*:focus {
@@ -53,6 +50,7 @@ button {
outline-offset: 2px !important;
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
box-sizing: border-box;
}
button:hover {

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/v2.0.0/metadata.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/v2.0.0/seo.png Normal file

Binary file not shown.

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

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

BIN
assets/v3.0.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
assets/v3.0.0/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
assets/v3.0.0/metadata.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
assets/v3.0.0/welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

7937
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{
"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": "1.16.1",
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-128x128.png",
"version": "3.0.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -15,6 +15,11 @@
"description": "version",
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
"href": "https://github.com/estruyf/vscode-front-matter"
},
{
"description": "Buy me a coffee",
"url": "https://img.shields.io/badge/Buy%20me%20a%20coffee-€%203-blue?logo=buy-me-a-coffee&style=flat-square",
"href": "https://www.buymeacoffee.com/zMeFRy9"
}
],
"engines": {
@@ -25,10 +30,8 @@
],
"keywords": [
"Front Matter",
"Hugo",
"Jekyll",
"Gatsby",
"Hexo",
"CMS",
"Markdown",
"Taxonomy"
],
"license": "MIT",
@@ -48,6 +51,13 @@
"onCommand:frontMatter.setLastModifiedDate",
"onCommand:frontMatter.generateSlug",
"onCommand:frontMatter.createFromTemplate",
"onCommand:frontMatter.registerFolder",
"onCommand:frontMatter.unregisterFolder",
"onCommand:frontMatter.createContent",
"onCommand:frontMatter.init",
"onCommand:frontMatter.collapseSections",
"onCommand:frontMatter.preview",
"onCommand:frontMatter.dashboard",
"onView:frontMatter.explorer"
],
"main": "./dist/extension",
@@ -75,6 +85,54 @@
"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.content.publicFolder": {
"type": "string",
"default": "",
"markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder."
},
"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.dashboard.openOnStart": {
"type": [
"boolean",
"null"
],
"default": null,
"description": "Specify if you want to open the dashboard when you start VS Code."
},
"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",
@@ -105,6 +163,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,
@@ -137,6 +200,16 @@
"default": 160,
"description": "Specifies the optimal description length for SEO (set to `-1` to turn it off)."
},
"frontMatter.taxonomy.seoContentLengh": {
"type": "number",
"default": 1760,
"description": "Specifies the optimal minimum length for your articles. Between 1,760 words 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off)."
},
"frontMatter.taxonomy.seoDescriptionField": {
"type": "string",
"default": "description",
"description": "Specifies the name of the SEO description field for your page. Default is 'description'."
},
"frontMatter.templates.folder": {
"type": "string",
"default": ".templates",
@@ -146,59 +219,100 @@
"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"
},
{
"command": "frontMatter.registerFolder",
"title": "Front Matter: Register folder"
},
{
"command": "frontMatter.unregisterFolder",
"title": "Front Matter: Unregister folder"
},
{
"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"
}
},
{
"command": "frontMatter.preview",
"title": "Preview article",
"category": "Front matter"
},
{
"command": "frontMatter.dashboard",
"title": "Open pages dashboard",
"category": "Front matter"
}
],
"menus": {
@@ -207,6 +321,40 @@
"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.preview",
"when": "frontMatterCanOpenPreview"
},
{
"command": "frontMatter.collapseSections",
"when": "false"
}
],
"view/title": [
{
"command": "frontMatter.collapseSections",
"group": "navigation"
}
]
},
@@ -228,29 +376,42 @@
"clean": "rm -rf dist"
},
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
"@iarna/toml": "2.2.3",
"@material-ui/core": "4.11.1",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.56",
"@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",
"@types/react-dom": "17.0.0",
"@types/vscode": "1.51.0",
"date-fns": "2.0.1",
"@vscode/codicons": "0.0.20",
"autoprefixer": "^10.3.2",
"css-loader": "5.2.7",
"date-fns": "2.23.0",
"downshift": "6.0.6",
"fuse.js": "6.4.6",
"glob": "7.1.6",
"gray-matter": "4.0.2",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
"mdast-util-from-markdown": "1.0.0",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"style-loader": "2.0.0",
"tailwindcss": "^2.2.7",
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"wc-react": "github:estruyf/wc-react",
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
}
"webpack-cli": "3.3.12",
"@headlessui/react": "1.4.0",
"@heroicons/react": "1.0.4",
"lodash.uniqby": "4.7.0"
},
"dependencies": {}
}

8
postcss.config.js Normal file
View File

@@ -0,0 +1,8 @@
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [
tailwindcss('./tailwind.config.js'),
require('autoprefixer'),
],
};

10
sample/script-sample.js Normal file
View File

@@ -0,0 +1,10 @@
const arguments = process.argv;
if (arguments && arguments.length > 0) {
const workspaceArg = arguments[2]; // The workspace path
const fileArg = arguments[3]; // The file path
const frontMatterArg = arguments[4]; // Front matter data
console.log(`The content returned for your notification.`);
}

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
@@ -15,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;
}
@@ -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);
}
}
@@ -98,17 +101,14 @@ 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";
article = this.articleDate(article, dateFormat, dateField, forceCreate);
article = this.articleDate(article, dateFormat, modField, false);
if (typeof article.data[dateField] !== "undefined" || forceCreate) {
if (dateFormat && typeof dateFormat === "string") {
article.data[dateField] = format(new Date(), dateFormat);
} else {
article.data[dateField] = new Date();
}
}
return article;
}
@@ -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;
@@ -127,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();
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);
}
}
@@ -146,11 +147,14 @@ export class Article {
/**
* Generate the slug based on the article title
*/
public static generateSlug() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
public static async generateSlug() {
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;
const filePrefix = config.get<string>(SETTING_TEMPLATES_PREFIX);
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
@@ -161,10 +165,41 @@ 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: ${e?.message || e}`);
}
}
}
}
}
@@ -185,4 +220,68 @@ export class Article {
article.data["draft"] = newDraftStatus;
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 = SettingsHelper.getConfig();
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();
}
}
}
/**
* 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
* @param dateFormat
* @param field
* @param forceCreate
*/
private static articleDate(article: matter.GrayMatterFile<string>, dateFormat: string, field: string, forceCreate: boolean) {
if (typeof article.data[field] !== "undefined" || forceCreate) {
if (dateFormat && typeof dateFormat === "string") {
article.data[field] = format(new Date(), dateFormat);
} else {
article.data[field] = new Date().toISOString();
}
}
return article;
}
}

253
src/commands/Dashboard.ts Normal file
View File

@@ -0,0 +1,253 @@
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART } from './../constants/settings';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from "vscode";
import { SettingsHelper } from '../helpers';
import { TaxonomyType } from '../models';
import { Folders } from './Folders';
import { getNonce } from '../helpers/getNonce';
import { DashboardCommand } from '../pagesView/DashboardCommand';
import { DashboardMessage } from '../pagesView/DashboardMessage';
import { Page } from '../pagesView/models/Page';
import { openFileInEditor } from '../helpers/openFileInEditor';
import { COMMAND_NAME } from '../constants/Extension';
import { Template } from './Template';
import { Notifications } from '../helpers/Notifications';
import { Settings } from '../pagesView/models/Settings';
import { Extension } from '../helpers/Extension';
export class Dashboard {
private static webview: WebviewPanel | null = null;
private static isDisposed: boolean = true;
/** 
* Init the dashboard
*/
public static async init() {
const config = SettingsHelper.getConfig();
const openOnStartup = config.get(SETTINGS_DASHBOARD_OPENONSTART);
if (openOnStartup) {
Dashboard.open();
}
}
/**
* Open or reveal the dashboard
*/
public static async open() {
if (Dashboard.isOpen) {
Dashboard.reveal();
} else {
Dashboard.create();
}
}
/**
* Check if the dashboard is still open
*/
public static get isOpen(): boolean {
return !Dashboard.isDisposed;
}
/**
* Reveal the dashboard if it is open
*/
public static reveal() {
if (Dashboard.webview) {
Dashboard.webview.reveal();
}
}
/**
* Create the dashboard webview
*/
public static async create() {
const extensionUri = Extension.getInstance().extensionPath;
// Create the preview webview
Dashboard.webview = window.createWebviewPanel(
'frontMatterDashboard',
'FrontMatter Dashboard',
ViewColumn.One,
{
enableScripts: true
}
);
Dashboard.isDisposed = false;
Dashboard.webview.iconPath = {
dark: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter-dark.svg')),
light: Uri.file(join(extensionUri.fsPath, 'assets/frontmatter.svg'))
};
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
Dashboard.webview.onDidChangeViewState(() => {
if (this.webview?.visible) {
console.log(`Dashboard opened`);
}
});
Dashboard.webview.onDidDispose(() => {
Dashboard.isDisposed = true;
});
workspace.onDidChangeConfiguration(() => {
Dashboard.getSettings();
});
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
switch(msg.command) {
case DashboardMessage.getData:
Dashboard.getSettings();
Dashboard.getPages();
break;
case DashboardMessage.openFile:
openFileInEditor(msg.data);
break;
case DashboardMessage.createContent:
await commands.executeCommand(COMMAND_NAME.createContent);
break;
case DashboardMessage.updateSetting:
Dashboard.updateSetting(msg.data);
break;
case DashboardMessage.InitializeProject:
await commands.executeCommand(COMMAND_NAME.init, Dashboard.getSettings);
break;
case DashboardMessage.Reload:
Dashboard.webview?.dispose();
setTimeout(() => {
Dashboard.open();
}, 100);
break;
}
});
}
/**
* Retrieve the settings for the dashboard
*/
private static async getSettings() {
Dashboard.postWebviewMessage({
command: DashboardCommand.settings,
data: {
folders: Folders.get(),
initialized: await Template.isInitialized(),
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
openOnStart: SettingsHelper.getConfig().get(SETTINGS_DASHBOARD_OPENONSTART),
versionInfo: Extension.getInstance().getVersion()
} as Settings
});
}
/**
* Update a setting from the dashboard
*/
private static async updateSetting(data: { name: string, value: any }) {
await SettingsHelper.updateSetting(data.name, data.value);
Dashboard.getSettings();
}
/**
* Retrieve all the markdown pages
*/
private static async getPages() {
const config = SettingsHelper.getConfig();
const wsFolders = workspace.workspaceFolders;
const crntWsFolder = wsFolders && wsFolders.length > 0 ? wsFolders[0] : null;
const descriptionField = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description";
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
const staticFolder = config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
const folderInfo = await Folders.getInfo();
const pages: Page[] = [];
if (folderInfo) {
for (const folder of folderInfo) {
for (const file of folder.lastModified) {
if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.mdx`)) {
try {
const article = ArticleHelper.getFrontMatterByPath(file.filePath);
if (article?.data.title) {
const page: Page = {
...article.data,
// FrontMatter properties
fmGroup: folder.title,
fmModified: file.mtime,
fmFilePath: file.filePath,
fmFileName: file.fileName,
// Make sure these are always set
title: article?.data.title,
slug: article?.data.slug,
date: article?.data[dateField] || "",
draft: article?.data.draft,
description: article?.data[descriptionField] || "",
};
if (article?.data.preview && crntWsFolder) {
const previewPath = join(crntWsFolder.uri.fsPath, staticFolder || "", article?.data.preview);
const previewUri = Uri.file(previewPath);
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
page.preview = preview?.toString() || "";
}
pages.push(page);
}
} catch (error) {
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
}
}
}
}
}
Dashboard.postWebviewMessage({
command: DashboardCommand.pages,
data: pages
});
}
/**
* Post data to the dashboard
* @param msg
*/
private static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
Dashboard.webview?.webview.postMessage(msg);
}
/**
* Retrieve the webview HTML contents
* @param webView
*/
private static getWebviewContent(webView: Webview, extensionPath: Uri): string {
const scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', 'pages.js'));
const nonce = getNonce();
const version = Extension.getInstance().getVersion();
return `
<!DOCTYPE html>
<html lang="en" style="width:100%;height:100%;margin:0;padding:0;">
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${`vscode-file://vscode-app`} ${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">
<title>Front Matter Dashboard</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden" class="bg-gray-100 text-vulcan-500 dark:bg-vulcan-500 dark:text-whisper-500">
<div id="app" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></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>
`;
}
}

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

@@ -0,0 +1,228 @@
import { commands, Uri, workspace, window } from "vscode";
import { SETTINGS_CONTENT_FOLDERS } from "../constants";
import { basename, join } from "path";
import { ContentFolder, FileInfo, FolderInfo } from "../models";
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 {
/**
* 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;
}
let selectedFolder: string | undefined;
if (folders.length > 1) {
selectedFolder = await window.showQuickPick(folders.map(f => f.title), {
placeHolder: `Select where you want to create your content`
});
} else {
selectedFolder = folders[0].title;
}
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 = "";
const wsFolder = Folders.getWorkspaceFolder();
if (folder && folder.fsPath) {
folderPath = folder.fsPath;
} else if (wsFolder) {
folderPath = wsFolder.fsPath;
}
return folderPath;
}
/**
* Retrieve the workspace folder
*/
public static getWorkspaceFolder(): Uri | undefined {
const folders = workspace.workspaceFolders;
if (folders && folders.length > 0) {
return folders[0].uri;
}
return undefined;
}
/**
* Get the name of the project
*/
public static getProjectFolderName(): string {
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
// const projectFolder = wsFolder?.fsPath.split('\\').join('/').split('/').pop();
return basename(wsFolder.fsPath);
}
return "";
}
/**
* Get the registered folders information
*/
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
const folders = Folders.get();
if (folders && folders.length > 0) {
let folderInfo: FolderInfo[] = [];
for (const folder of folders) {
try {
const projectName = Folders.getProjectFolderName();
let projectStart = folder.fsPath.split(projectName).pop();
if (projectStart) {
projectStart = projectStart.replace(/\\/g, '/');
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
const mdFiles = await workspace.findFiles(join(projectStart, '**/*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, '**/*.mdx'));
let files = [...mdFiles, ...mdxFiles];
if (files) {
let fileStats: FileInfo[] = [];
for (const file of files) {
try {
const fileName = basename(file.fsPath);
const stats = await workspace.fs.stat(file);
fileStats.push({
filePath: file.fsPath,
fileName,
...stats
});
} catch (error) {
// Skip the file
}
}
fileStats = fileStats.sort((a, b) => b.mtime - a.mtime);
if (limit) {
fileStats = fileStats.slice(0, limit);
}
folderInfo.push({
title: folder.title,
files: files.length,
lastModified: fileStats
});
}
}
} catch (e) {
// Skip the current folder
}
}
return folderInfo;
}
return null;
}
/**
* Get the folder settings
* @returns
*/
public static get() {
const config = SettingsHelper.getConfig();
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 = 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
};
}
}

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 {
@@ -21,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) {
@@ -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;
}
@@ -71,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();
@@ -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}.`);
});
}
@@ -154,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"
@@ -171,7 +170,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;
}
@@ -252,7 +251,6 @@ export class Settings {
}
}
} catch (e) {
console.log(file.path);
// Continue with the next file
}
}
@@ -273,7 +271,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

@@ -1,7 +1,6 @@
import { SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings';
import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants/settings';
import * as vscode from 'vscode';
import { CONFIG_KEY } from '../constants';
import { ArticleHelper, SeoHelper } from '../helpers';
import { ArticleHelper, SeoHelper, SettingsHelper } from '../helpers';
import { ExplorerView } from '../webview/ExplorerView';
export class StatusListener {
@@ -17,13 +16,12 @@ export class StatusListener {
const publishMsg = "to publish";
let editor = vscode.window.activeTextEditor;
if (editor && editor.document && editor.document.languageId.toLowerCase() === "markdown") {
if (editor && ArticleHelper.isMarkdownFile()) {
try {
const article = ArticleHelper.getFrontMatter(editor);
// Update the StatusBar based on the article draft state
if (article && typeof article.data["draft"] !== "undefined") {
// console.log(`Draft status: ${article.data["draft"]}`);
if (article.data["draft"] === true) {
frontMatterSB.text = `$(book) ${draftMsg}`;
frontMatterSB.show();
@@ -38,16 +36,17 @@ 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";
if (article.data.title && titleLength > -1) {
SeoHelper.checkLength(editor, collection, article, "title", titleLength);
}
if (article.data.description && descLength > -1) {
SeoHelper.checkLength(editor, collection, article, "description", descLength);
if (article.data[fieldName] && descLength > -1) {
SeoHelper.checkLength(editor, collection, article, fieldName, descLength);
}
}
@@ -60,6 +59,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,35 +1,115 @@
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 { ArticleHelper, SettingsHelper } 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
*/
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);
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 = SettingsHelper.getConfig();
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
return folder;
}
}

View File

@@ -0,0 +1,30 @@
const extensionName = "frontMatter";
export const EXTENSION_ID = 'eliostruyf.vscode-front-matter';
export const EXTENSION_STATE_VERSION = 'frontMatter:Version';
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"),
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
};

4
src/constants/Links.ts Normal file
View File

@@ -0,0 +1,4 @@
export const GITHUB_LINK = "https://github.com/sponsors/estruyf";
export const ISSUE_LINK = "https://github.com/estruyf/vscode-front-matter/issues";
export const SPONSOR_LINK = "https://github.com/estruyf/vscode-front-matter";
export const REVIEW_LINK = "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details";

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

@@ -0,0 +1,6 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
canOpenPreview: "frontMatterCanOpenPreview",
canOpenDashboard: "frontMatterCanOpenDashboard",
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";
@@ -18,10 +19,22 @@ export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType";
export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength";
export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength";
export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh";
export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
export const SETTING_TEMPLATES_FOLDER = "templates.folder";
export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
export const SETTING_PANEL_FREEFORM = "panel.freeform";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTING_PREVIEW_HOST = "preview.host";
export const SETTING_PREVIEW_PATHNAME = "preview.pathName";
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_STATIC_FOLDERS = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";

View File

@@ -1,17 +1,40 @@
import { Dashboard } from './commands/Dashboard';
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';
import { Extension } from './helpers/Extension';
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, extensionPath, globalState }: vscode.ExtensionContext) {
const extension = Extension.getInstance(globalState, extensionUri);
collection = vscode.languages.createDiagnosticCollection('frontMatter');
// Pages dashboard
Dashboard.init();
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, () => {
Dashboard.open();
}));
if (!extension.getVersion().usedVersion) {
vscode.commands.executeCommand(COMMAND_NAME.dashboard);
}
// Register the explorer view
const explorerSidebar = ExplorerView.getInstance(extensionUri);
let explorerView = vscode.window.registerWebviewViewProvider(ExplorerView.viewType, explorerSidebar, {
webviewOptions: {
@@ -19,100 +42,158 @@ 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', () => {
Settings.export();
});
let exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export);
let remap = vscode.commands.registerCommand('frontMatter.remap', () => {
Settings.remap();
});
let remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap);
let setDate = vscode.commands.registerCommand('frontMatter.setDate', () => {
Article.setDate();
});
let setDate = vscode.commands.registerCommand(COMMAND_NAME.setDate, Article.setDate);
let setLastModifiedDate = vscode.commands.registerCommand('frontMatter.setLastModifiedDate', () => {
Article.setLastModifiedDate();
});
let setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate);
let generateSlug = vscode.commands.registerCommand('frontMatter.generateSlug', () => {
Article.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, async (cb: Function) => {
await Project.init();
if (cb) {
cb();
}
});
// 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();
const exView = ExplorerView.getInstance();
exView.getSettings();
exView.getFoldersAndFiles();
MarkdownFoldingProvider.triggerHighlighting();
});
// 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 file saves
subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc: vscode.TextDocument) => {
if (doc.languageId === 'markdown') {
// Optimize the list of recently changed files
ExplorerView.getInstance().getFoldersAndFiles();
}
}));
// Webview for preview
Preview.init();
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.preview, () => Preview.open(extensionPath) ));
// 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) => {
const functionCall = (...args: any[]) => fnc.apply(args);
clearTimeout(timeout);
timeout = setTimeout(functionCall, time);
timeout = setTimeout(functionCall, time) as any;
};
};
};

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();
@@ -96,4 +98,36 @@ 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"));
}
/**
* 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;
}
}

53
src/helpers/Extension.ts Normal file
View File

@@ -0,0 +1,53 @@
import { Memento, extensions, Uri } from "vscode";
import { EXTENSION_ID, EXTENSION_STATE_VERSION } from "../constants/Extension";
export class Extension {
private static instance: Extension;
private constructor(private globalState: Memento, private extPath: Uri) {}
/**
* Creates the singleton instance for the panel
* @param extPath
*/
public static getInstance(globalState?: Memento, extPath?: Uri): Extension {
if (!Extension.instance && globalState && extPath) {
Extension.instance = new Extension(globalState, extPath);
}
return Extension.instance;
}
/**
* Get the current version information for the extension
*/
public getVersion(): { usedVersion: string | undefined, installedVersion: string } {
const frontMatter = extensions.getExtension(EXTENSION_ID)!;
const installedVersion = frontMatter.packageJSON.version;
const usedVersion = this.globalState.get<string>(EXTENSION_STATE_VERSION);
if (!usedVersion) {
this.setVersion(installedVersion);
};
return {
usedVersion,
installedVersion
};
}
/**
* Set the current version information for the extension
*/
public setVersion(installedVersion: string): void {
this.globalState.update(EXTENSION_STATE_VERSION, installedVersion);
}
/**
* Get the path to the extension
*/
public get extensionPath(): Uri {
return this.extPath;
}
}

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { EXTENSION_NAME } from '../constants';
import { Notifications } from './Notifications';
export class FilesHelper {
@@ -9,12 +10,13 @@ export class FilesHelper {
public static async getMdFiles(): Promise<vscode.Uri[] | null> {
const mdFiles = await vscode.workspace.findFiles('**/*.md', "**/node_modules/**,**/archetypes/**");
const markdownFiles = await vscode.workspace.findFiles('**/*.markdown', "**/node_modules/**,**/archetypes/**");
const mdxFiles = await vscode.workspace.findFiles('**/*.mdx', "**/node_modules/**,**/archetypes/**");
if (!mdFiles && !markdownFiles) {
vscode.window.showInformationMessage(`${EXTENSION_NAME}: No MD files found.`);
Notifications.info(`No MD files found.`);
return null;
}
const allMdFiles = mdFiles.concat(markdownFiles);
const allMdFiles = [...mdFiles, ...markdownFiles, ...mdxFiles];
return allMdFiles;
}
}

View File

@@ -1,4 +1,6 @@
import { CommandToCode } from "../CommandToCode";
import { DashboardMessage } from './../pagesView/DashboardMessage';
import { CommandToCode } from "../viewpanel/CommandToCode";
interface ClientVsCode<T> {
getState: () => T;
@@ -16,7 +18,7 @@ export class MessageHelper {
return MessageHelper.vscode;
}
public static sendMessage = (command: CommandToCode, data?: any) => {
public static sendMessage = (command: CommandToCode | DashboardMessage, data?: any) => {
if (data) {
MessageHelper.vscode.postMessage({ command, data });
} else {

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

@@ -4,6 +4,15 @@ import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY } from '
export class SettingsHelper {
public static getConfig(): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(CONFIG_KEY);
}
public static async updateSetting(name: string, value: any) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
await config.update(name, value);
}
/**
* 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();
};

10
src/helpers/getNonce.ts Normal file
View File

@@ -0,0 +1,10 @@
export const getNonce = () => {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};

View File

@@ -0,0 +1,13 @@
import { Uri, workspace, window } from "vscode";
import { Notifications } from "./Notifications";
export const openFileInEditor = async (filePath: string) => {
if (filePath) {
try {
const doc = await workspace.openTextDocument(Uri.file(filePath));
await window.showTextDocument(doc, 1, false);
} catch (e) {
Notifications.error(`Couldn't open the file.`);
}
}
};

28
src/hooks/useDarkMode.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { useState, useEffect } from 'react';
export default function useDarkMode() {
const setTheme = (elm: HTMLElement) => {
if (elm) {
const darkMode = elm.classList.contains('vscode-dark');
document.documentElement.classList.remove(`${darkMode ? "light" : "dark"}`);
document.documentElement.classList.add(`${darkMode ? "dark" : "light"}`);
}
};
useEffect(() => {
const mutationObserver = new MutationObserver((mutationsList, observer) => {
const last = mutationsList.filter(item => item.type === "attributes" || item.attributeName === 'class').pop();
setTheme(last?.target as HTMLElement);
});
setTheme(document.body);
mutationObserver.observe(document.body, { childList: false, attributes: true })
return () => {
mutationObserver.disconnect();
};
}, ['']);
}

25
src/hooks/useDebounce.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay: number) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState<T>(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

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

View File

@@ -1,3 +1,4 @@
import { FileType } from "vscode";
export interface PanelSettings {
seo: SEO;
@@ -6,11 +7,18 @@ export interface PanelSettings {
categories: string[];
freeform: boolean;
scripts: CustomScript[];
isInitialized: boolean;
modifiedDateUpdate: boolean;
writingSettingsEnabled: boolean;
fmHighlighting: boolean;
preview: PreviewSettings;
}
export interface SEO {
title: number;
description: number;
content: number;
descriptionField: string;
}
export interface Slug {
@@ -18,8 +26,28 @@ export interface Slug {
suffix: number;
}
export interface FolderInfo {
title: string;
files: number;
lastModified: FileInfo[];
}
export interface FileInfo {
type: FileType;
ctime: number;
mtime: number;
size: number;
filePath: string;
fileName: string;
};
export interface CustomScript {
title: string;
script: string;
nodeBin?: string;
}
export interface PreviewSettings {
host: string | undefined;
pathname: string | undefined;
}

View File

@@ -0,0 +1,6 @@
export interface VersionInfo {
usedVersion: string | undefined;
installedVersion: string;
}

View File

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

View File

@@ -0,0 +1,5 @@
export enum DashboardCommand {
loading = "loading",
pages = "pages",
settings = "settings"
}

View File

@@ -0,0 +1,9 @@
export enum DashboardMessage {
getData = 'getData',
openFile = 'openFile',
getTheme = 'getTheme',
createContent = 'createContent',
updateSetting = 'updateSetting',
InitializeProject = 'InitializeProject',
Reload = 'Reload',
}

View File

@@ -0,0 +1,19 @@
import * as React from 'react';
export interface IButtonProps {
disabled?: boolean;
onClick: () => void;
}
export const Button: React.FunctionComponent<IButtonProps> = ({onClick, disabled, children}: React.PropsWithChildren<IButtonProps>) => {
return (
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500"
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
};

View File

@@ -0,0 +1,69 @@
import * as React from 'react';
import { Spinner } from './Spinner';
import useMessages from '../hooks/useMessages';
import { Overview } from './Overview';
import { Header } from './Header';
import { Tab } from '../constants/Tab';
import { SortOption } from '../constants/SortOption';
import useDarkMode from '../../hooks/useDarkMode';
import usePages from '../hooks/usePages';
import { SponsorMsg } from './SponsorMsg';
import { WelcomeScreen } from './WelcomeScreen';
export interface IDashboardProps {
showWelcome: boolean;
}
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
const { loading, pages, settings } = useMessages();
const [ tab, setTab ] = React.useState(Tab.All);
const [ sorting, setSorting ] = React.useState(SortOption.LastModified);
const [ group, setGroup ] = React.useState<string | null>(null);
const [ search, setSearch ] = React.useState<string | null>(null);
const [ tag, setTag ] = React.useState<string | null>(null);
const [ category, setCategory ] = React.useState<string | null>(null);
const { pageItems } = usePages(pages, tab, sorting, group, search, tag, category);
useDarkMode();
const pageGroups = [...new Set(pages.map(page => page.fmGroup))];
if (!settings) {
return <Spinner />;
}
if (showWelcome) {
return <WelcomeScreen settings={settings} />;
}
if (!settings.initialized || settings.folders?.length === 0) {
return <WelcomeScreen settings={settings} />;
}
return (
<main className={`h-full w-full`}>
<div className="flex flex-col h-full overflow-auto">
<Header currentTab={tab}
currentSorting={sorting}
groups={pageGroups}
crntGroup={group}
totalPages={pageItems.length}
crntTag={tag}
crntCategory={category}
switchTab={(tabId: Tab) => setTab(tabId)}
switchSorting={(sortId: SortOption) => setSorting(sortId)}
switchGroup={(groupId: string | null) => setGroup(groupId)}
switchTag={(tagId: string | null) => setTag(tagId)}
switchCategory={(categoryId: string | null) => setCategory(categoryId)}
onSearch={(value: string | null) => setSearch(value)}
settings={settings}
/>
<div className="flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg />
</div>
</main>
);
};

View File

@@ -0,0 +1,28 @@
import { format, parseJSON } from 'date-fns';
import * as React from 'react';
export interface IDateFieldProps {
value: Date | string;
}
export const DateField: React.FunctionComponent<IDateFieldProps> = ({value}: React.PropsWithChildren<IDateFieldProps>) => {
const [ dateValue, setDateValue ] = React.useState<string>("");
React.useEffect(() => {
try {
const parsedValue = typeof value === 'string' ? parseJSON(value) : value;
const dateString = format(parsedValue, 'yyyy-MM-dd');
setDateValue(dateString);
} catch (e) {
// Date is invalid
}
}, [value]);
if (!dateValue) {
return null;
}
return (
<span className={`text-vulcan-100 dark:text-whisper-900 text-xs`}>{dateValue}</span>
);
};

View File

@@ -0,0 +1,46 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { MenuButton } from './MenuButton';
import { MenuItem } from './MenuItem';
import { MenuItems } from './MenuItems';
export interface IFilterProps {
label: string;
items: string[];
activeItem: string | null;
onClick: (item: string | null) => void;
}
const DEFAULT_VALUE = "No filter";
export const Filter: React.FunctionComponent<IFilterProps> = ({label, activeItem, items, onClick}: React.PropsWithChildren<IFilterProps>) => {
if (!items || items.length === 0) {
return null;
}
return (
<div className="flex items-center ml-6">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={label} title={activeItem || DEFAULT_VALUE} />
<MenuItems>
<MenuItem
title={DEFAULT_VALUE}
value={null}
isCurrent={!!activeItem}
onClick={() => onClick(null)} />
{items.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === activeItem}
onClick={() => onClick(option)} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,46 @@
import { Menu, Transition } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { Fragment } from 'react';
import { MenuButton } from './MenuButton';
import { MenuItem } from './MenuItem';
import { MenuItems } from './MenuItems';
export interface IGroupingProps {
groups: string[];
crntGroup: string | null;
switchGroup: (group: string | null) => void;
}
const DEFAULT_TYPE = "All types";
export const Grouping: React.FunctionComponent<IGroupingProps> = ({groups, crntGroup, switchGroup}: React.PropsWithChildren<IGroupingProps>) => {
if (groups.length <= 1) {
return null;
}
return (
<div className="flex items-center ml-6">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Showing`} title={crntGroup || DEFAULT_TYPE} />
<MenuItems>
<MenuItem
title={DEFAULT_TYPE}
value={null}
isCurrent={!crntGroup}
onClick={switchGroup} />
{groups.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === crntGroup}
onClick={switchGroup} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,75 @@
import * as React from 'react';
import { Tab } from '../constants/Tab';
import { SortOption } from '../constants/SortOption';
import { Navigation } from './Navigation';
import { Sorting } from './Sorting';
import { Grouping } from './Grouping';
import { MessageHelper } from '../../helpers/MessageHelper';
import { DashboardMessage } from '../DashboardMessage';
import { Searchbox } from './Searchbox';
import { Settings } from '../models/Settings';
import { Startup } from './Startup';
import { Button } from './Button';
import { Filter } from './Filter';
export interface IHeaderProps {
settings: Settings;
// Navigation
currentTab: Tab;
totalPages: number;
switchTab: (tabId: Tab) => void;
// Sorting
currentSorting: SortOption;
switchSorting: (sortId: SortOption) => void;
// Grouping
groups: string[];
crntGroup: string | null;
switchGroup: (group: string | null) => void;
// Searching
onSearch: (value: string | null) => void;
// Tags
crntTag: string | null;
switchTag: (tag: string | null) => void;
// Categories
crntCategory: string | null;
switchCategory: (category: string | null) => void;
}
export const Header: React.FunctionComponent<IHeaderProps> = ({currentTab, currentSorting, switchSorting, switchTab, totalPages, crntGroup, groups, switchGroup, onSearch, settings, switchTag, crntTag, switchCategory, crntCategory}: React.PropsWithChildren<IHeaderProps>) => {
const createContent = () => {
MessageHelper.sendMessage(DashboardMessage.createContent);
};
return (
<div className={`w-full max-w-7xl mx-auto sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
<div className={`px-4 my-2 flex items-center justify-between`}>
<Searchbox onSearch={onSearch} />
<div className={`flex items-center space-x-4`}>
<Startup settings={settings} />
<Button onClick={createContent} disabled={!settings.initialized}>Create content</Button>
</div>
</div>
<div className="px-4 flex items-center border-b border-gray-200 dark:border-whisper-600">
<Navigation currentTab={currentTab} totalPages={totalPages} switchTab={switchTab} />
<Grouping crntGroup={crntGroup} groups={groups} switchGroup={switchGroup} />
<Filter label={`Tag filter`} activeItem={crntTag} items={settings.tags} onClick={switchTag} />
<Filter label={`Category filter`} activeItem={crntCategory} items={settings.categories} onClick={switchCategory} />
<Sorting currentSorting={currentSorting} switchSorting={switchSorting} />
</div>
</div>
);
};

View File

@@ -0,0 +1,47 @@
import * as React from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { MarkdownIcon } from '../../viewpanel/components/Icons/MarkdownIcon';
import { DashboardMessage } from '../DashboardMessage';
import { Page } from '../models/Page';
import { DateField } from './DateField';
import { Status } from './Status';
export interface IItemProps extends Page {}
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren<IItemProps>) => {
const openFile = () => {
MessageHelper.sendMessage(DashboardMessage.openFile, fmFilePath);
};
return (
<li className="relative">
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100`}
onClick={openFile}>
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
{
preview ? (
<img src={`${preview}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
) : (
<div className={`flex items-center justify-center bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100`}>
<MarkdownIcon className={`h-32 text-vulcan-100 dark:text-whisper-100`} />
</div>
)
}
</div>
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<DateField value={date} />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
</div>
</button>
</li>
);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export interface IListProps {}
export const List: React.FunctionComponent<IListProps> = ({children}: React.PropsWithChildren<IListProps>) => {
return (
<ul role="list" className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8">
{children}
</ul>
);
};

View File

@@ -0,0 +1,23 @@
import { Menu } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/solid';
import * as React from 'react';
export interface IMenuButtonProps {
label: string;
title: string;
}
export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({label, title}: React.PropsWithChildren<IMenuButtonProps>) => {
return (
<div>
<span className={`text-gray-500 dark:text-whisper-700 mr-2 font-medium`}>{label}:</span>
<Menu.Button className="group inline-flex justify-center text-sm font-medium text-vulcan-500 hover:text-vulcan-600 dark:text-whisper-500 dark:hover:text-whisper-600">
{title}
<ChevronDownIcon
className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500 dark:text-whisper-600 dark:group-hover:text-whisper-700"
aria-hidden="true"
/>
</Menu.Button>
</div>
);
};

View File

@@ -0,0 +1,22 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
export interface IMenuItemProps {
title: string;
value: any;
isCurrent: boolean;
onClick: (value: any) => void;
}
export const MenuItem: React.FunctionComponent<IMenuItemProps> = ({title, value, isCurrent, onClick}: React.PropsWithChildren<IMenuItemProps>) => {
return (
<Menu.Item>
<button
onClick={() => onClick(value)}
className={`${!isCurrent ? `text-vulcan-500 dark:text-whisper-500` : `text-gray-500 dark:text-whisper-900`} block px-4 py-2 text-sm font-medium w-full text-left hover:bg-gray-100 hover:text-gray-700 dark:hover:text-whisper-600 dark:hover:bg-vulcan-100`}
>
{title}
</button>
</Menu.Item>
);
};

View File

@@ -0,0 +1,25 @@
import { Menu, Transition } from '@headlessui/react';
import * as React from 'react';
import { Fragment } from 'react';
export interface IMenuItemsProps {}
export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({children}: React.PropsWithChildren<IMenuItemsProps>) => {
return (
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 z-10 mt-2 w-40 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto">
<div className="py-1">
{children}
</div>
</Menu.Items>
</Transition>
);
};

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
import { Tab } from '../constants/Tab';
export interface INavigationProps {
currentTab: Tab;
totalPages: number;
switchTab: (tabId: Tab) => void;
}
export const tabs = [
{ name: 'All articles', id: Tab.All},
{ name: 'Published', id: Tab.Published },
{ name: 'In draft', id: Tab.Draft }
];
export const Navigation: React.FunctionComponent<INavigationProps> = ({currentTab, totalPages, switchTab}: React.PropsWithChildren<INavigationProps>) => {
return (
<nav className="flex-1 -mb-px flex space-x-6 xl:space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<button
key={tab.name}
className={`${tab.id === currentTab ? `border-teal-900 dark:border-teal-300 text-teal-900 dark:text-teal-300` : `border-transparent text-gray-500 dark:text-whisper-600 hover:text-gray-700 dark:hover:text-whisper-700 hover:border-gray-300 dark:hover:border-whisper-500`} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
aria-current={tab.id === currentTab ? 'page' : undefined}
onClick={() => switchTab(tab.id)}
>
{tab.name}{(tab.id === currentTab && totalPages) ? ` (${totalPages})` : ''}
</button>
))}
</nav>
);
};

View File

@@ -0,0 +1,42 @@
import * as React from 'react';
import { FrontMatterIcon } from '../../viewpanel/components/Icons/FrontMatterIcon';
import { Page } from '../models/Page';
import { Settings } from '../models/Settings';
import { Item } from './Item';
import { List } from './List';
export interface IOverviewProps {
pages: Page[];
settings: Settings;
}
export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settings}: React.PropsWithChildren<IOverviewProps>) => {
if (!pages || !pages.length) {
return (
<div className={`flex items-center justify-center h-full`}>
<div className={`max-w-xl text-center`}>
<FrontMatterIcon className={`text-vulcan-300 dark:text-whisper-800 h-32 mx-auto opacity-90 mb-8`} />
{
settings?.folders?.length > 0 ? (
<p className={`text-xl font-medium`}>No Markdown to show</p>
) : (
<>
<p className={`text-lg font-medium`}>Make sure you registered a content folder in your project to let Front Matter find the contents.</p>
</>
)
}
</div>
</div>
);
}
return (
<List>
{pages.map(page => (
<Item key={page.slug} {...page} />
))}
</List>
);
};

View File

@@ -0,0 +1,42 @@
import { FilterIcon, SearchIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useDebounce } from '../../hooks/useDebounce';
export interface ISearchboxProps {
onSearch: (searchText: string) => void;
}
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({onSearch}: React.PropsWithChildren<ISearchboxProps>) => {
const [ value, setValue ] = React.useState('');
const debounceSearch = useDebounce<string>(value, 500);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
React.useEffect(() => {
onSearch(debounceSearch);
}, [debounceSearch]);
return (
<div className="flex space-x-4">
<div className="flex-1 min-w-0">
<label htmlFor="search" className="sr-only">Search</label>
<div className="relative flex justify-center">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="search"
name="search"
className={`block w-full py-2 pl-10 pr-3 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none`}
placeholder="Search"
value={value}
autoFocus={true}
onChange={handleChange}
/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,44 @@
import { Menu, Transition } from '@headlessui/react';
import * as React from 'react';
import { SortOption } from '../constants/SortOption';
import { ChevronDownIcon } from '@heroicons/react/solid';
import { Fragment } from 'react';
import { MenuItem } from './MenuItem';
import { MenuItems } from './MenuItems';
import { MenuButton } from './MenuButton';
export interface ISortingProps {
currentSorting: SortOption;
switchSorting: (sortId: SortOption) => void;
}
export const sortOptions = [
{ name: "Last modified", id: SortOption.LastModified },
{ name: "By filename (asc)", id: SortOption.FileNameAsc },
{ name: "By filename (desc)", id: SortOption.FileNameDesc },
];
export const Sorting: React.FunctionComponent<ISortingProps> = ({currentSorting, switchSorting}: React.PropsWithChildren<ISortingProps>) => {
const crntSort = sortOptions.find(x => x.id === currentSorting);
return (
<div className="flex items-center ml-6">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Sort by`} title={crntSort?.name || ""} />
<MenuItems>
{sortOptions.map((option) => (
<MenuItem
key={option.id}
title={option.name}
value={option.id}
isCurrent={option.id === currentSorting}
onClick={switchSorting} />
))}
</MenuItems>
</Menu>
</div>
);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export interface ISpinnerProps {}
export const Spinner: React.FunctionComponent<ISpinnerProps> = (props: React.PropsWithChildren<ISpinnerProps>) => {
return (
<div className={`fixed top-0 left-0 right-0 bottom-0 w-full h-full flex flex-wrap items-center justify-center bg-white bg-opacity-10 z-50`}>
<div className="loader ease-linear rounded-full border-8 border-t-8 border-gray-50 h-32 w-32" />
</div>
);
};

View File

@@ -0,0 +1,19 @@
import { HeartIcon, StarIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { REVIEW_LINK, SPONSOR_LINK } from '../../constants/Links';
export interface ISponsorMsgProps {}
export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = (props: React.PropsWithChildren<ISponsorMsgProps>) => {
return (
<p className={`w-full max-w-7xl mx-auto px-4 text-vulcan-50 dark:text-whisper-900 py-2 text-center space-x-8 flex items-center justify-between`}>
<a className={`group inline-flex justify-center items-center space-x-2 text-vulcan-500 dark:text-whisper-500 hover:text-vulcan-600 dark:hover:text-whisper-300 opacity-50 hover:opacity-100`} href={SPONSOR_LINK} title="Sponsor Front Matter">
<span>Sponsor</span> <HeartIcon className={`h-5 w-5 group-hover:fill-current`} />
</a>
<span>FrontMatter</span>
<a className={`group inline-flex justify-center items-center space-x-2 text-vulcan-500 dark:text-whisper-500 hover:text-vulcan-600 dark:hover:text-whisper-300 opacity-50 hover:opacity-100`} href={REVIEW_LINK} title="Review Front Matter">
<StarIcon className={`h-5 w-5 group-hover:fill-current`} /> <span>Review</span>
</a>
</p>
);
};

View File

@@ -0,0 +1,43 @@
import * as React from 'react';
import { SETTINGS_DASHBOARD_OPENONSTART } from '../../constants';
import { MessageHelper } from '../../helpers/MessageHelper';
import { DashboardMessage } from '../DashboardMessage';
import { Settings } from '../models/Settings';
export interface IStartupProps {
settings: Settings;
}
export const Startup: React.FunctionComponent<IStartupProps> = ({settings}: React.PropsWithChildren<IStartupProps>) => {
const [isChecked, setIsChecked] = React.useState(false);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(e.target.checked);
MessageHelper.sendMessage(DashboardMessage.updateSetting, { name: SETTINGS_DASHBOARD_OPENONSTART, value: e.target.checked });
};
React.useEffect(() => {
setIsChecked(!!settings.openOnStart);
}, [settings?.openOnStart]);
return (
<div className={`relative flex items-start`}>
<div className="flex items-center h-5">
<input
id="startup"
aria-describedby="startup-description"
name="startup"
type="checkbox"
checked={isChecked}
onChange={onChange}
className="focus:outline-none focus:ring-teal-500 h-4 w-4 text-teal-600 border-gray-300 dark:border-vulcan-50 rounded"
/>
</div>
<div className="ml-2 text-sm">
<label id="startup-description" htmlFor="startup" className="font-medium text-vulcan-50 dark:text-whisper-900">
Open on startup?
</label>
</div>
</div>
);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export interface IStatusProps {
draft: boolean;
}
export const Status: React.FunctionComponent<IStatusProps> = ({draft}: React.PropsWithChildren<IStatusProps>) => {
return (
<span className={`inline-block px-2 py-1 leading-none rounded-full font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draft ? "bg-red-500" : "bg-teal-500"}`}>{draft ? "Draft" : "Published"}</span>
);
};

View File

@@ -0,0 +1,60 @@
import { CheckIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { Status } from '../../models/Status';
export interface IStepProps {
name: string;
description: string;
status: Status;
showLine: boolean;
onClick?: () => void;
}
export const Step: React.FunctionComponent<IStepProps> = ({name, description, status, showLine, onClick}: React.PropsWithChildren<IStepProps>) => {
return (
<>
{
showLine ? (
<div className={`-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full ${status === Status.Completed ? "bg-teal-600" : "bg-gray-300"}`} aria-hidden="true" />
) : null
}
<button className={`relative flex items-start group text-left ${onClick ? "" : "cursor-default"}`} onClick={() => { if (onClick) { onClick(); } }} disabled={!onClick}>
{
status === Status.NotStarted && (
<span className="h-9 flex items-center" aria-hidden="true">
<span className={`relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 rounded-full ${onClick ? "group-hover:text-gray-400" : ""}`}>
<span className={`h-2.5 w-2.5 bg-transparent rounded-full ${onClick ? "group-hover:bg-gray-300" : ""}`} />
</span>
</span>
)
}
{
status === Status.Active && (
<span className="h-9 flex items-center" aria-hidden="true">
<span className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-teal-600 rounded-full">
<span className="h-2.5 w-2.5 bg-teal-600 rounded-full" />
</span>
</span>
)
}
{
status === Status.Completed && (
<span className="h-9 flex items-center">
<span className="relative z-10 w-8 h-8 flex items-center justify-center bg-teal-600 rounded-full group-hover:bg-teal-800">
<CheckIcon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
</span>
)
}
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase text-vulcan-500 dark:text-whisper-500">{name}</span>
<span className="text-sm text-vulcan-400 dark:text-whisper-600" dangerouslySetInnerHTML={{__html: description}} />
</span>
</button>
</>
);
};

View File

@@ -0,0 +1,45 @@
import * as React from 'react';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { DashboardMessage } from '../../DashboardMessage';
import { Settings } from '../../models/Settings';
import { Status } from '../../models/Status';
import { Step } from './Step';
export interface IStepsToGetStartedProps {
settings: Settings;
}
export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps> = ({settings}: React.PropsWithChildren<IStepsToGetStartedProps>) => {
const steps = [
{
name: 'Initialize project',
description: 'Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. <b>Start by clicking on this action</b>.',
status: settings.initialized ? Status.Completed : Status.NotStarted,
onClick: settings.initialized ? undefined : () => { MessageHelper.sendMessage(DashboardMessage.InitializeProject); }
},
{
name: 'Register content folders (manual action)',
description: 'Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting <b>register folder</b>. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.',
status: settings.folders && settings.folders.length > 0 ? Status.Completed : Status.NotStarted
},
{
name: 'Show the dashboard',
description: 'Once both actions are completed, click on this action to load the dashboard.',
status: (settings.initialized && settings.folders && settings.folders.length > 0) ? Status.Active : Status.NotStarted,
onClick: (settings.initialized && settings.folders && settings.folders.length > 0) ? () => { MessageHelper.sendMessage(DashboardMessage.Reload); } : undefined
}
];
return (
<nav aria-label="Progress">
<ol role="list" className="overflow-hidden">
{steps.map((step, stepIdx) => (
<li key={step.name} className={`${stepIdx !== steps.length - 1 ? 'pb-10' : ''} relative`}>
<Step name={step.name} description={step.description} status={step.status} showLine={stepIdx !== steps.length - 1} onClick={step.onClick} />
</li>
))}
</ol>
</nav>
);
};

View File

@@ -0,0 +1,82 @@
import { HeartIcon, StarIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { GITHUB_LINK, REVIEW_LINK, SPONSOR_LINK } from '../../constants/Links';
import { FrontMatterIcon } from '../../viewpanel/components/Icons/FrontMatterIcon';
import { GitHubIcon } from '../../viewpanel/components/Icons/GitHubIcon';
import { Settings } from '../models/Settings';
import { StepsToGetStarted } from './Steps/StepsToGetStarted';
export interface IWelcomeScreenProps {
settings: Settings;
}
export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({settings}: React.PropsWithChildren<IWelcomeScreenProps>) => {
return (
<main className="mt-24">
<div className="mx-auto max-w-7xl">
<div className="grid grid-cols-12 gap-8">
<div className="px-6 col-span-8 text-left flex items-center">
<div>
<h1 className="mt-4 text-4xl tracking-tight font-extrabold sm:mt-5 sm:leading-none lg:mt-6 lg:text-5xl xl:text-6xl">
<span className="md:block">Manage your static site with</span>{' '}
<span className="text-teal-500 md:block">Front Matter</span>
</h1>
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
Thank you for taking the time to test out Front Matter!
</p>
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
We try to aim to make Front Matter as easy to use as possible, but if you have any questions, please don't hesitate to reach out to us on GitHub.
</p>
<div className="mt-5 w-full sm:mx-auto sm:max-w-lg lg:ml-0">
<div className="flex flex-wrap items-start justify-between">
<a href={GITHUB_LINK} title={`GitHub`} className="flex items-center px-1 text-vulcan-300 hover:text-vulcan-500 dark:text-whisper-500 dark:hover:text-teal-500">
<GitHubIcon className="w-8 h-8" />
<span className={`text-lg ml-2`}>GitHub / Documentation</span>
</a>
<a href={SPONSOR_LINK} title={`Become a sponsor`} className="flex items-center px-1 text-vulcan-300 hover:text-vulcan-500 dark:text-whisper-500 dark:hover:text-teal-500">
<HeartIcon className="w-8 h-8" />
<span className={`text-lg ml-2`}>Sponsor</span>
</a>
<a href={REVIEW_LINK} title={`Write a quick review`} className="flex items-center px-1 text-vulcan-300 hover:text-vulcan-500 dark:text-whisper-500 dark:hover:text-teal-500">
<StarIcon className="w-8 h-8" />
<span className={`text-lg ml-2`}>Review</span>
</a>
</div>
</div>
</div>
</div>
<div className="col-span-4 flex justify-center items-center">
<FrontMatterIcon className={`h-64 w-64 text-vulcan-500 dark:text-whisper-500`} />
</div>
</div>
</div>
<div className="mx-auto max-w-7xl mt-12 px-6">
<h2 className="text-xl tracking-tight font-extrabold sm:leading-none">
Perform the next steps to get you started with the extension
</h2>
<div className={`grid grid-cols-12 gap-8 mt-5`}>
<div className={`col-span-8`}>
<StepsToGetStarted settings={settings} />
<p className="mt-5 text-sm text-vulcan-300 dark:text-whisper-700">
Once you completed both actions, the dashboard will show its full potential. You can also use the extension from the <b>Front Matter</b> side panel. There you will find the actions you can perform specifically for your pages.
</p>
</div>
</div>
<h2 className="mt-5 text-lg tracking-tight font-extrabold sm:leading-none">
We hope you enjoy Front Matter!
</h2>
</div>
</main>
);
};

View File

@@ -0,0 +1,5 @@
export enum SortOption {
LastModified = 1,
FileNameAsc,
FileNameDesc
}

View File

@@ -0,0 +1,5 @@
export enum Tab {
All = 'all',
Published = 'published',
Draft = 'draft',
};

View File

@@ -0,0 +1,43 @@
import { useState, useEffect } from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { DashboardCommand } from '../DashboardCommand';
import { DashboardMessage } from '../DashboardMessage';
import { Page } from '../models/Page';
import { Settings } from '../models/Settings';
const vscode = MessageHelper.getVsCodeAPI();
export default function useMessages(options?: any) {
const [loading, setLoading] = useState<boolean>(false);
const [pages, setPages] = useState<Page[]>([]);
const [settings, setSettings] = useState<Settings | undefined>(undefined);
window.addEventListener('message', event => {
const message = event.data;
switch (message.command) {
case DashboardCommand.loading:
setLoading(message.data);
break;
case DashboardCommand.settings:
setSettings(message.data);
break;
case DashboardCommand.pages:
setPages(message.data);
setLoading(false);
break;
}
});
useEffect(() => {
setLoading(true);
vscode.postMessage({ command: DashboardMessage.getTheme });
vscode.postMessage({ command: DashboardMessage.getData });
}, ['']);
return {
loading,
pages,
settings
};
}

Some files were not shown because too many files have changed in this diff Show More