Compare commits

...

62 Commits

Author SHA1 Message Date
Elio Struyf
e69c8bbad8 Merge pull request #295 from estruyf/dev 2022-03-21 13:54:45 +01:00
Elio Struyf
12aba4e900 Remove logging 2022-03-21 13:47:08 +01:00
Elio Struyf
9d70521ccf #292 - Lower fuzzy search threshold 2022-03-21 12:58:20 +01:00
Elio Struyf
1d7ff4fbf7 update changelog 2022-03-21 12:48:32 +01:00
Elio Struyf
1eaf04d907 Update wf trigger 2022-03-17 17:03:59 +01:00
Elio Struyf
9839013465 Fix #257 2022-03-17 16:13:32 +01:00
Elio Struyf
65be619d51 Showing data nav by default + empty message update 2022-03-17 16:07:11 +01:00
Elio Struyf
c4331cb140 Update content folder settings 2022-03-16 19:55:43 +01:00
Elio Struyf
c09831e832 #286 - Fix for content types not containing fields 2022-03-16 19:33:39 +01:00
Elio Struyf
2a48e6adf1 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-03-16 09:57:47 +01:00
Elio Struyf
8579d29890 #286 - Refresh button for content page 2022-03-16 09:57:39 +01:00
Elio
4fe9794b10 Additional logging 2022-03-16 08:41:32 +01:00
Elio Struyf
9413a6f878 #290 - Fix for onDidChangeTextEditorSelection listener sending metadata updates 2022-03-15 14:17:09 +01:00
Elio Struyf
e77672dfc7 Added new placeholders 2022-03-14 16:46:57 +01:00
Elio Struyf
bb9795952d Updated changelog + smaller command enhancements 2022-03-14 16:07:20 +01:00
Elio Struyf
36b3efafd4 Moved assets to docs site 2022-03-14 11:46:46 +01:00
Elio Struyf
b9ba9768ea Allow variable encoding in snippets 2022-03-14 10:18:55 +01:00
Elio Struyf
09c2c44c0a Hide the deprecation message 2022-03-14 10:18:44 +01:00
Elio Struyf
99ebbab100 Custom scripts 2022-03-14 09:06:29 +01:00
Elio Struyf
6ab6dda1da #175 - Simplified snippets 2022-03-11 13:53:33 +01:00
Elio Struyf
f77dce3566 Updated readme's 2022-03-11 12:24:46 +01:00
Elio Struyf
67c4355dff #287 - Show folder name on index.md files 2022-03-11 12:05:20 +01:00
Elio Struyf
1038d51e5d updateMetadata logic change 2022-03-10 09:33:19 +01:00
Elio Struyf
eaa61c1ea1 Updated vscodeignore 2022-03-10 08:54:35 +01:00
Elio Struyf
50695b6866 Error boundary for keyword 2022-03-09 20:47:24 +01:00
Elio Struyf
bf9011e23f Fix script 2022-03-09 11:29:22 +01:00
Elio Struyf
ac8a429ac2 Hide devtools webpack 2022-03-09 11:27:04 +01:00
Elio Struyf
f0cf59a1ac Fix for validating team value 2022-03-09 10:31:05 +01:00
Elio Struyf
145abfb026 Updated snippets 2022-03-08 17:11:26 +01:00
Elio Struyf
438160d08f #284 - Set the WYSIWYG on all supported files 2022-03-08 15:40:55 +01:00
Elio Struyf
b867f72fe2 Added collapse and dashboard buttons 2022-03-08 15:23:53 +01:00
Elio Struyf
cad6a2d5b4 #282 - Update relative paths for sub-leafs/bundles 2022-03-08 13:53:52 +01:00
Elio Struyf
3ea28e673f #282 - relative image paths in front matter 2022-03-08 13:06:41 +01:00
Elio Struyf
ea11a3646f #283 - Published date sorting 2022-03-08 08:58:03 +01:00
Elio Struyf
c8e79e75ba #281 - Do not automatically set dates to fm + remove date {{now}} field 2022-03-08 08:24:14 +01:00
Elio Struyf
9340568653 Sponsor updates 2022-03-08 08:23:27 +01:00
Elio Struyf
0d7b55c52f #270 #282 - Enhancements for page bundles and related folders + files 2022-03-07 20:52:21 +01:00
Elio Struyf
5644c0c381 #281 - Introduce new date fields and deprecate settings 2022-03-07 11:39:57 +01:00
Elio Struyf
04390b461f #280 - Fix for date fields 2022-03-07 09:27:38 +01:00
Elio Struyf
3ca6f32628 #279 - Fix for content dashboard updates 2022-03-07 09:20:24 +01:00
Elio Struyf
9fbf962e9f Fix for date parsing 2022-03-07 09:08:16 +01:00
Elio Struyf
b0d3aceecd Updated changelog + new features 2022-03-04 15:38:03 +01:00
Elio Struyf
94d88987ea Merge branch 'issue/175' into dev 2022-03-04 15:05:31 +01:00
Elio Struyf
aaf7a40969 7.0.0 2022-03-04 15:05:17 +01:00
Elio Struyf
4065019525 Snippet variables 2022-03-04 09:08:25 +01:00
Elio Struyf
eeb1fc9cb4 Added schema + array and string type 2022-03-03 21:05:41 +01:00
Elio Struyf
f35d8c8332 Merge branch 'issue/175' of github.com:estruyf/vscode-front-matter into issue/175 2022-03-03 19:57:15 +01:00
Elio Struyf
c8cd435142 Added snippet schema 2022-03-03 19:57:10 +01:00
Elio Struyf
0e42e1ea00 Code snippets changes: add, update, delete, and more 2022-03-03 16:26:36 +01:00
Elio Struyf
a6bdfc3421 Snippet dialog 2022-03-02 22:00:45 +01:00
Elio Struyf
47003754f6 Merge branch 'main' into dev 2022-03-02 18:18:00 +01:00
Elio Struyf
71072d9520 #275 - Invalid markdown syntax tree fix 2022-03-02 18:15:41 +01:00
Elio Struyf
b64dd8f88a Update changelog 2022-03-02 18:13:35 +01:00
Elio Struyf
173c89d86f 6.1.1 2022-03-02 18:12:24 +01:00
Elio Struyf
b1013829d8 #275 - Fix for incorrect markdown syntax 2022-03-02 18:10:44 +01:00
Elio Struyf
48ac869e40 Insert snippet into the content 2022-03-02 17:03:29 +01:00
Elio Struyf
00e590bc67 Snippet parser implementation 2022-03-02 16:03:07 +01:00
Elio Struyf
55053acd38 Remove unnecessary activation events 2022-03-02 13:27:16 +01:00
Elio Struyf
576ee9ca9d First steps to create the snippet dashboard 2022-03-02 08:40:30 +01:00
Elio Struyf
b99c61a0ee #272 - Media details panel 2022-03-01 15:14:29 +01:00
Elio Struyf
8d3b5619cd Sponsor badge 2022-03-01 13:19:57 +01:00
Elio Struyf
fdc7b8e68f 6.2.0 2022-03-01 12:15:16 +01:00
139 changed files with 2790 additions and 10690 deletions

View File

@@ -1 +1 @@
{}
{"assets":{"v7.0.0":{"snippets-dashboard.png":{"caption":"Snippets dashboard","alt":"Snippets dashboard"}}}}

View File

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- dev
workflow_dispatch:
jobs:
build:

View File

@@ -22,4 +22,8 @@ assets/v2.*
assets/v3.*
assets/v4.*
assets/sponsors
dist/*.html
dist/*.html
frontmatter.json
.frontmatter
webpack
README.beta.md

View File

@@ -1,5 +1,41 @@
# Change Log
## [7.0.0] - 2022-03-21 - [Release notes](https://beta.frontmatter.codes/updates/v7.0.0)
### ✨ New Features
- [#175](https://github.com/estruyf/vscode-front-matter/issues/175): New snippet support + dashboard
- [#281](https://github.com/estruyf/vscode-front-matter/issues/281): New `isPublishDate` and `isModifiedDate` datetime field properties
### 🎨 Enhancements
- Light color theme enhancements to media cards
- Light color theme enhancements to folder cards
- Added collapse and dashboard button to the view title of the FM Panel
- Show content commands only when a supported file type is active
- Added `{{year}}`, `{{month}}`, and `{{day}}` placeholders for fields
- [#272](https://github.com/estruyf/vscode-front-matter/issues/272): New slide over panel for showing details of media files
- [#276](https://github.com/estruyf/vscode-front-matter/issues/276): Add a Front Matter walkthrough for VS Code
- [#270](https://github.com/estruyf/vscode-front-matter/issues/270): Only show media files from public folder if `pageBundle` is not enabled on any of the content types
- [#282](https://github.com/estruyf/vscode-front-matter/issues/282): Insert relative paths for media files located in a page bundle (also sub-folders)
- [#283](https://github.com/estruyf/vscode-front-matter/issues/283): Added published date sorting options for the content dashboard
- [#286](https://github.com/estruyf/vscode-front-matter/issues/286): Refresh button added for the content page
- [#287](https://github.com/estruyf/vscode-front-matter/issues/287): Show folder name on `index.md` files for recently modified files
- [#292](https://github.com/estruyf/vscode-front-matter/issues/292): Lower fuzzy search threshold for the content dashboard
### 🐞 Fixes
- [#279](https://github.com/estruyf/vscode-front-matter/issues/279): Fix for content dashboard updates for all registered types
- [#280](https://github.com/estruyf/vscode-front-matter/issues/280): Fix to not automatically set dates on new files that do not contain front matter
- [#284](https://github.com/estruyf/vscode-front-matter/issues/284): Show the WYSIWYG controls on all supported file types
- [#290](https://github.com/estruyf/vscode-front-matter/issues/290): Fix for onDidChangeTextEditorSelection listener sending metadata updates
## [6.1.1] - 2022-03-02
### 🐞 Fixes
- [#275](https://github.com/estruyf/vscode-front-matter/issues/275): Fix for rendering the panel when content contains an invalid markdown syntax tree
## [6.1.0] - 2022-02-28 - [Release notes](https://beta.frontmatter.codes/updates/v6.1.0)
### ✨ New features

View File

@@ -17,8 +17,8 @@
<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 href="https://github.com/sponsors/estruyf" title="Become a sponsor" style="margin-left:10px">
<img src="https://img.shields.io/github/sponsors/estruyf?color=%23CE2E7C&logo=github&style=flat" alt="Sponsor the project" style="display: inline-block" />
</a>
</p>
@@ -49,17 +49,23 @@ A couple of our extension highlights that hopefully get you interested in giving
> Missing something? Let us know by opening an issue on the [GitHub repository](https://github.com/estruyf/vscode-front-matter/issues/new/choose)
<p align="center">
<img src="./assets/v6.0.0/content-preview.png" alt="Site preview" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/content-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 7**
Snippets support for Front Matter has been added!
![Snippets dashboard](https://frontmatter.codes/assets/marketplace/v7.0.0/snippets-dashboard.png)
**Version 6**
In this version, we introduced the new data files/folders dashboard. You can find more information about the release in our [v6.0.0 release notes](https://frontmatter.codes/updates/v6.0.0).
<p align="center">
<img src="./assets/v6.0.0/data-dashboard.png" alt="Data dashboard" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/data-dashboard.png" alt="Data dashboard" style="display: inline-block" />
</p>
> Data files/folders are pieces of content that do not belong to any markdown content, but live on their own. Most of the time, these data files are used to store additional information about your project/blog/website that will be used to render the content.
@@ -69,7 +75,7 @@ In this version, we introduced the new data files/folders dashboard. You can fin
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
<p align="center">
<img src="./assets/v5.9.0/media-dashboard.png" alt="Data dashboard" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v5.9.0/media-dashboard.png" alt="Data dashboard" style="display: inline-block" />
</p>
**Version 4**
@@ -160,12 +166,18 @@ You can open showcase issues for the following things:
## 🖤 Backers & Sponsors 👇 🤘
<p align="center">
<a href="https://github.com/jmatthewpryor" title="Andre Powell">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/850570" />
</a>
<a href="https://github.com/apowell656" title="Andre Powell">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1969515" />
</a>
<a href="https://github.com/timschps" title="Tim Schaeps">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/13098307" />
</a>
<a href="https://github.com/grahampcharles" title="Graham Charles">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/3606679?v=4" />
</a>
<a href="https://github.com/zivbk1" title="Bryan Klein">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
</a>
@@ -178,8 +190,8 @@ You can open showcase issues for the following things:
<p align="center">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="assets/sponsors/powered-by-vercel.png" />
</a>
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" />
</a>
</p>
## 🔑 License
@@ -190,6 +202,6 @@ You can open showcase issues for the following things:
<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" />
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
</a>
</p>

View File

@@ -15,8 +15,8 @@
<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 href="https://github.com/sponsors/estruyf" title="Become a sponsor" style="margin-left:10px">
<img src="https://img.shields.io/github/sponsors/estruyf?color=%23CE2E7C&logo=github&style=flat" alt="Sponsor the project" style="display: inline-block" />
</a>
</p>
@@ -47,17 +47,23 @@ A couple of our extension highlights that hopefully get you interested in giving
> Missing something? Let us know by opening an issue on the [GitHub repository](https://github.com/estruyf/vscode-front-matter/issues/new/choose)
<p align="center">
<img src="./assets/v6.0.0/content-preview.png" alt="Site preview" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/content-preview.png" alt="Site preview" style="display: inline-block" />
</p>
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 7**
Snippets support for Front Matter has been added!
![Snippets dashboard](https://frontmatter.codes/assets/marketplace/v7.0.0/snippets-dashboard.png)
**Version 6**
In this version, we introduced the new data files/folders dashboard. You can find more information about the release in our [v6.0.0 release notes](https://frontmatter.codes/updates/v6.0.0).
<p align="center">
<img src="./assets/v6.0.0/data-dashboard.png" alt="Data dashboard" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v6.0.0/data-dashboard.png" alt="Data dashboard" style="display: inline-block" />
</p>
> Data files/folders are pieces of content that do not belong to any markdown content, but live on their own. Most of the time, these data files are used to store additional information about your project/blog/website that will be used to render the content.
@@ -67,7 +73,7 @@ In this version, we introduced the new data files/folders dashboard. You can fin
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
<p align="center">
<img src="./assets/v5.9.0/media-dashboard.png" alt="Data dashboard" style="display: inline-block" />
<img src="https://frontmatter.codes/assets/marketplace/v5.9.0/media-dashboard.png" alt="Data dashboard" style="display: inline-block" />
</p>
**Version 4**
@@ -158,12 +164,18 @@ You can open showcase issues for the following things:
## 🖤 Backers & Sponsors 👇 🤘
<p align="center">
<a href="https://github.com/jmatthewpryor" title="Andre Powell">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/850570" />
</a>
<a href="https://github.com/apowell656" title="Andre Powell">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1969515" />
</a>
<a href="https://github.com/timschps" title="Tim Schaeps">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/13098307" />
</a>
<a href="https://github.com/grahampcharles" title="Graham Charles">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/3606679?v=4" />
</a>
<a href="https://github.com/zivbk1" title="Bryan Klein">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
</a>
@@ -176,7 +188,7 @@ You can open showcase issues for the following things:
<p align="center">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="assets/sponsors/powered-by-vercel.png" />
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" />
</a>
</p>
@@ -189,6 +201,6 @@ You can open showcase issues for the following things:
<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>
<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.

Before

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

2
assets/empty.svg Normal file
View File

@@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
</svg>

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#C5C5C5" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#424242" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -0,0 +1,3 @@
## Documentation
Our documentation can be found at: [https://frontmatter.codes/docs](https://frontmatter.codes/docs)

View File

@@ -0,0 +1,11 @@
## Getting started
Thanks for installing Front Matter!
To get started, open our dashboard which will guide you through the initialization process of your project.
When you haven't initialized your project yet, you will see the Front Matter's welcome screen on which you will have to perform the following steps:
- Project initialization
- Content folders registration
- Framework initialization

View File

@@ -0,0 +1,8 @@
## Support the project
Front Matter is an open source project and we are always looking for new contributors, supporters, and partners. If you are interested in backing the project, please consider supporting it by donating. You can donate at via the following links:
- [GitHub Sponsors](https://github.com/sponsors/estruyf)
- [Open Collective](https://opencollective.com/frontmatter)
> Each sponsor/backer will be mentioned on the [Front Matter](https://frontmatter.codes) website and on the [GitHub repository](https://github.com/estruyf/vscode-front-matter).

55
frontmatter.json Normal file
View File

@@ -0,0 +1,55 @@
{
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
"frontMatter.framework.id": "other",
"frontMatter.content.publicFolder": "",
"frontMatter.content.pageFolders": [
{
"title": ".vscode",
"path": "[[workspace]]/.vscode"
}
],
"frontMatter.content.snippets": {
"New version": {
"description": "Insert a new version to the changelog",
"body": [
"## [{{version}}] - {{year}}-{{month}}-{{day}}",
"",
"### ✨ New features",
"",
"### 🎨 Enhancements",
"",
"### ⚡️ Optimizations",
"",
"### 🐞 Fixes"
],
"fields": [
{
"type": "string",
"name": "version",
"title": "Version",
"single": true
},
{
"type": "string",
"name": "year",
"title": "Year",
"default": "2022"
},
{
"type": "string",
"name": "month",
"title": "Month",
"default": "xx"
},
{
"type": "string",
"name": "day",
"title": "Day",
"default": "xx"
}
],
"openingTags": "{{",
"closingTags": "}}"
}
}
}

10070
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"displayName": "Front Matter",
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "6.1.0",
"version": "7.0.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -47,11 +47,6 @@
"activationEvents": [
"workspaceContains:**/.frontmatter",
"workspaceContains:**/frontmatter.json",
"onCommand:frontMatter.init",
"onCommand:frontMatter.dashboard",
"onCommand:frontMatter.dashboard.data",
"onCommand:frontMatter.dashboard.media",
"onCommand:workbench.view.extension.frontmatter-explorer",
"onView:frontMatter.explorer",
"onStartupFinished"
],
@@ -224,6 +219,47 @@
"markdownDescription": "Specify the folder name where all your assets are located. For instance in Hugo this is the `static` folder. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.publicfolder)",
"scope": "Content"
},
"frontMatter.content.snippets": {
"type": "object",
"markdownDescription": "Define the snippets you want to use in your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.snippets)",
"additionalProperties": {
"type": "object",
"required": [
"body",
"fields"
],
"properties": {
"body": {
"markdownDescription": "The snippet content.",
"type": [
"string",
"array"
],
"items": {
"type": "string"
}
},
"description": {
"description": "The snippet description.",
"type": "string"
},
"fields": {
"$ref": "#contenttypefield"
},
"openingTags": {
"description": "The snippet opening tags.",
"type": "string",
"default": "[["
},
"closingTags": {
"description": "The snippet closing tags.",
"type": "string",
"default": "]]"
}
},
"additionalProperties": false
}
},
"frontMatter.content.sorting": {
"type": "array",
"default": [],
@@ -741,6 +777,16 @@
"type": "number",
"default": 0,
"description": "Limit the number of taxonomies to select. Set to 0 to allow unlimited."
},
"isPublishDate": {
"type": "boolean",
"default": false,
"description": "Specify if the field is the publish date field"
},
"isModifiedDate": {
"type": "boolean",
"default": false,
"description": "Specify if the field is the modified date field"
}
},
"additionalProperties": false,
@@ -861,7 +907,8 @@
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}"
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
@@ -917,7 +964,8 @@
"frontMatter.taxonomy.dateField": {
"type": "string",
"default": "date",
"markdownDescription": "This setting is used to define the publishing date field of your articles. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.datefield)"
"markdownDescription": "This setting is used to define the publishing date field of your articles. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.datefield)",
"deprecationMessage": "This setting is deprecated and will be removed in the next major version. Please use the new `isPublishDate` settings instead in your content types date fields."
},
"frontMatter.taxonomy.dateFormat": {
"type": "string",
@@ -971,7 +1019,8 @@
"frontMatter.taxonomy.modifiedField": {
"type": "string",
"default": "lastmod",
"markdownDescription": "This setting is used to define the modified date field of your articles. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.modifiedfield)"
"markdownDescription": "This setting is used to define the modified date field of your articles. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.modifiedfield)",
"deprecationMessage": "This setting is deprecated and will be removed in the next major version. Please use the new `isModifiedDate` settings instead in your content types date fields."
},
"frontMatter.taxonomy.noPropertyValueQuotes": {
"type": "array",
@@ -1094,7 +1143,7 @@
},
{
"command": "frontMatter.markup.blockquote",
"title": "Codeblock",
"title": "Blockquote",
"category": "Front matter",
"icon": {
"light": "assets/icons/blockquote-light.svg",
@@ -1185,6 +1234,15 @@
"light": "/assets/icons/media-light.svg"
}
},
{
"command": "frontMatter.insertSnippet",
"title": "Insert snippet into your content",
"category": "Front matter",
"icon": {
"dark": "/assets/icons/scissors-dark.svg",
"light": "/assets/icons/scissors-light.svg"
}
},
{
"command": "frontMatter.insertTags",
"title": "Insert tags",
@@ -1217,6 +1275,15 @@
"light": "/assets/icons/frontmatter-small-light.svg"
}
},
{
"command": "frontMatter.dashboard.snippets",
"title": "Open snippets dashboard",
"category": "Front matter",
"icon": {
"dark": "/assets/icons/frontmatter-small-dark.svg",
"light": "/assets/icons/frontmatter-small-light.svg"
}
},
{
"command": "frontMatter.dashboard.media",
"title": "Open media dashboard",
@@ -1292,63 +1359,68 @@
"editor/title": [
{
"command": "frontMatter.markup.heading",
"group": "navigation@-132",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"group": "navigation@-133",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.bold",
"group": "navigation@-131",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"group": "navigation@-132",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.italic",
"group": "navigation@-130",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"group": "navigation@-131",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.strikethrough",
"group": "navigation@-129",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"group": "navigation@-130",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.blockquote",
"group": "navigation@-128",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"command": "frontMatter.insertSnippet",
"group": "navigation@-129",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.insertImage",
"group": "navigation@-127",
"when": "resourceLangId == markdown"
"group": "navigation@-128",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.markup.options",
"group": "navigation@-126",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.orderedlist",
"group": "1_markup@1",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.unorderedlist",
"group": "1_markup@2",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.tasklist",
"group": "1_markup@3",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.code",
"group": "1_markup@4",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.codeblock",
"group": "1_markup@5",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.blockquote",
"group": "1_markup@6",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.dashboard",
@@ -1454,12 +1526,58 @@
{
"command": "frontMatter.markup.options",
"when": "false"
},
{
"command": "frontMatter.insertSnippet",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.insertImage",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.createCategory",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.createTag",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.insertCategories",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.insertTags",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.createTemplate",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.preview",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.setLastModifiedDate",
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.generateSlug",
"when": "frontMatter:file:isValid == true"
}
],
"view/title": [
{
"command": "frontMatter.collapseSections",
"group": "frontmatter-explorer"
"group": "navigation@0",
"when": "view == frontMatter.explorer"
},
{
"command": "frontMatter.dashboard",
"group": "navigation@1",
"when": "view == frontMatter.explorer"
}
]
},
@@ -1471,6 +1589,48 @@
"text.html.markdown"
]
}
],
"walkthroughs": [
{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [
{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
"completionEvents": [
"onContext:frontMatterInitialized"
]
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}
]
},
"scripts": {
@@ -1493,7 +1653,7 @@
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
"@estruyf/vscode": "0.0.2",
"@headlessui/react": "^1.4.1",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "1.0.4",
"@iarna/toml": "2.2.3",
"@octokit/rest": "^18.12.0",
@@ -1507,6 +1667,7 @@
"@types/lodash.uniqby": "4.7.6",
"@types/lodash.xor": "^4.5.6",
"@types/mocha": "^5.2.6",
"@types/mustache": "^4.1.2",
"@types/node": "10.17.48",
"@types/node-fetch": "^2.5.12",
"@types/react": "17.0.0",
@@ -1523,7 +1684,7 @@
"css-loader": "5.2.7",
"date-fns": "2.23.0",
"downshift": "6.0.6",
"fuse.js": "6.4.6",
"fuse.js": "6.5.3",
"glob": "7.1.6",
"gray-matter": "4.0.2",
"html-loader": "1.3.2",
@@ -1535,6 +1696,7 @@
"lodash.uniqby": "4.7.0",
"lodash.xor": "^4.5.0",
"mdast-util-from-markdown": "1.0.0",
"mustache": "^4.2.0",
"node-json-db": "^1.3.0",
"npm-run-all": "^4.1.5",
"path-browserify": "^1.0.1",
@@ -1570,4 +1732,4 @@
"dependencies": {
"node-fetch": "^2.6.7"
}
}
}

View File

@@ -1,5 +1,6 @@
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
import { isValidFile } from './../helpers/isValidFile';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTINGS_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
import * as vscode from 'vscode';
import { Field, TaxonomyType } from "../models";
import { format } from "date-fns";
@@ -13,6 +14,8 @@ import { parseWinPath } from '../helpers/parseWinPath';
import { Telemetry } from '../helpers/Telemetry';
import { ParsedFrontMatter } from '../parsers';
import { MediaListener } from '../listeners/panel';
import { NavigationType } from '../dashboardWebView/models';
import { processKnownPlaceholders } from '../helpers/PlaceholderHelper';
export class Article {
@@ -148,12 +151,13 @@ export class Article {
): ParsedFrontMatter | undefined {
const article = ArticleHelper.getFrontMatterFromDocument(document);
if (!article) {
// Only set the date, if there is already front matter set
if (!article || !article.data || Object.keys(article.data).length === 0) {
return;
}
const cloneArticle = Object.assign({}, article);
const dateField = Settings.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified;
const dateField = ArticleHelper.getModifiedDateField(article) || DefaultFields.LastModified;
try {
cloneArticle.data[dateField] = Article.formatDate(new Date());
return cloneArticle;
@@ -200,13 +204,14 @@ export class Article {
}
// Update the fields containing a custom placeholder that depends on slug
const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS);
const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS);
const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}"));
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
for (const customPlaceholder of (customPlaceholders || [])) {
const customPlaceholderFields = contentType.fields.filter(f => f.default === `{{${customPlaceholder.id}}}`);
for (const pField of customPlaceholderFields) {
article.data[pField.name] = customPlaceholder.value;
article.data[pField.name] = ArticleHelper.processKnownPlaceholders(article.data[pField.name], articleTitle);
article.data[pField.name] = processKnownPlaceholders(article.data[pField.name], articleTitle, dateFormat);
}
}
}
@@ -294,7 +299,7 @@ export class Article {
*/
public static async autoUpdate(event: vscode.TextDocumentWillSaveEvent) {
const document = event.document;
if (document && ArticleHelper.isMarkdownFile(document)) {
if (document && ArticleHelper.isSupportedFile(document)) {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
if (autoUpdate) {
@@ -325,11 +330,15 @@ export class Article {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
const contentType = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
const position = editor.selection.active;
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
type: "media",
data: {
pageBundle: !!contentType.pageBundle,
filePath: editor.document.uri.fsPath,
fieldName: basename(editor.document.uri.fsPath),
position
@@ -340,6 +349,32 @@ export class Article {
MediaListener.getMediaSelection();
}
/**
* Insert a snippet into the article
*/
public static async insertSnippet() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const position = editor.selection.active;
const selectionText = editor.document.getText(editor.selection);
const article = ArticleHelper.getFrontMatter(editor);
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
type: NavigationType.Snippets,
data: {
fileTitle: article?.data.title || "",
filePath: editor.document.uri.fsPath,
fieldName: basename(editor.document.uri.fsPath),
position,
selection: selectionText
}
} as DashboardData);
}
/**
* Get the current article
*/

View File

@@ -1,4 +1,4 @@
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { SETTING_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
import { Logger, Settings as SettingsHelper } from '../helpers';
@@ -6,9 +6,8 @@ import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { Extension } from '../helpers/Extension';
import { WebviewHelper } from '@estruyf/vscode';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { MediaLibrary } from '../helpers/MediaLibrary';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener } from '../listeners/dashboard';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener } from '../listeners/dashboard';
import { MediaListener as PanelMediaListener } from '../listeners/panel'
export class Dashboard {
@@ -24,7 +23,7 @@ export class Dashboard {
* Init the dashboard
*/
public static async init() {
const openOnStartup = SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART);
const openOnStartup = SettingsHelper.get(SETTING_DASHBOARD_OPENONSTART);
if (openOnStartup) {
Dashboard.open();
}
@@ -143,6 +142,7 @@ export class Dashboard {
SettingsListener.process(msg);
DataListener.process(msg);
TelemetryListener.process(msg);
SnippetListener.process(msg);
});
}

View File

@@ -1,7 +1,7 @@
import { Questions } from './../helpers/Questions';
import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
import { SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, SETTING_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
import { commands, Uri, workspace, window } from "vscode";
import { basename, join } from "path";
import { basename, dirname, join, sep } from "path";
import { ContentFolder, FileInfo, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
@@ -26,7 +26,7 @@ export class Folders {
*/
public static async addMediaFolder(data?: {selectedFolder?: string}) {
let wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
let startPath = "";
@@ -210,7 +210,7 @@ export class Folders {
* Get the registered folders information
*/
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
const supportedFiles = Settings.get<string[]>(SETTINGS_CONTENT_SUPPORTED_FILETYPES);
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
const folders = Folders.get();
if (folders && folders.length > 0) {
let folderInfo: FolderInfo[] = [];
@@ -238,12 +238,14 @@ export class Folders {
for (const file of files) {
try {
const fileName = basename(file.fsPath);
const folderName = dirname(file.fsPath).split(sep).pop();
const stats = await workspace.fs.stat(file);
fileStats.push({
filePath: file.fsPath,
fileName,
folderName,
...stats
});
} catch (error) {
@@ -281,7 +283,7 @@ export class Folders {
*/
public static get(): ContentFolder[] {
const wsFolder = Folders.getWorkspaceFolder();
const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
return folders.map(folder => ({
...folder,
@@ -293,7 +295,7 @@ export class Folders {
* Update the folder settings
* @param folders
*/
private static async update(folders: ContentFolder[]) {
public static async update(folders: ContentFolder[]) {
const wsFolder = Folders.getWorkspaceFolder();
let folderDetails = folders.map(folder => ({
@@ -301,7 +303,7 @@ export class Folders {
path: Folders.relWsFolder(folder, wsFolder)
}));
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
// Reinitialize the folder listeners
PagesListener.startWatchers();
@@ -339,7 +341,7 @@ export class Folders {
* @param wsFolder
* @returns
*/
private static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
public static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = parseWinPath(folder.path).replace(parseWinPath(wsFolder?.fsPath || ""), WORKSPACE_PLACEHOLDER);
absPath = isWindows ? absPath.split('\\').join('/') : absPath;

View File

@@ -5,8 +5,9 @@ import * as fs from "fs";
import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
import { Folders } from "./Folders";
import { Settings } from "../helpers";
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
import { Logger, Settings } from "../helpers";
import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
import { SettingsListener } from '../listeners/dashboard';
export class Project {
@@ -29,7 +30,7 @@ categories: []
public static async init(sampleTemplate: boolean = true) {
try {
Settings.createTeamSettings();
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
@@ -50,7 +51,10 @@ categories: []
}
Telemetry.send(TelemetryEvent.initialization)
SettingsListener.getSettings();
} catch (err: any) {
Logger.error(`Project::init: ${err?.message || err}`);
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
}
}

View File

@@ -1,10 +1,11 @@
import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants';
import { CONTEXT, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants';
import * as vscode from 'vscode';
import { ArticleHelper, SeoHelper, Settings } from '../helpers';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DefaultFields } from '../constants';
import { ContentType } from '../helpers/ContentType';
import { DataListener } from '../listeners/panel';
import { commands } from 'vscode';
export class StatusListener {
@@ -24,8 +25,10 @@ export class StatusListener {
}
let editor = vscode.window.activeTextEditor;
if (editor && ArticleHelper.isMarkdownFile()) {
if (editor && ArticleHelper.isSupportedFile()) {
try {
commands.executeCommand('setContext', CONTEXT.isValidFile, true);
const article = ArticleHelper.getFrontMatter(editor);
// Update the StatusBar based on the article draft state
@@ -67,6 +70,8 @@ export class StatusListener {
// Nothing to do
}
} else {
commands.executeCommand('setContext', CONTEXT.isValidFile, false);
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
DataListener.pushMetadata(null);

View File

@@ -2,7 +2,7 @@ import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
@@ -23,6 +23,10 @@ export class Template {
public static async init() {
const isInitialized = await Template.isInitialized();
await vscode.commands.executeCommand('setContext', CONTEXT.canInit, !isInitialized);
if (isInitialized) {
await vscode.commands.executeCommand('setContext', CONTEXT.initialized, true);
}
}
/**
@@ -52,9 +56,9 @@ export class Template {
public static async generate() {
const folder = Template.getSettings();
const editor = vscode.window.activeTextEditor;
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
if (folder && editor && ArticleHelper.isMarkdownFile()) {
if (folder && editor && ArticleHelper.isSupportedFile()) {
const article = ArticleHelper.getFrontMatter(editor);
const clonedArticle = Object.assign({}, article);

View File

@@ -1,5 +1,5 @@
import { commands, window, Selection, QuickPickItem } from "vscode";
import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants";
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from "../constants";
import { Settings } from "../helpers";
enum MarkupType {
@@ -24,7 +24,7 @@ export class Wysiwyg {
*/
public static async registerCommands(subscriptions: any) {
const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG);
const wysiwygEnabled = Settings.get(SETTING_CONTENT_WYSIWYG);
if (!wysiwygEnabled) {
return;
@@ -54,6 +54,7 @@ export class Wysiwyg {
{ label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true },
{ label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true },
{ label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true },
{ label: "$(quote) Blockquote", detail: "Add a blockquote", alwaysShow: true },
]
const option = await window.showQuickPick([ ...qpItems ], {
@@ -73,6 +74,8 @@ export class Wysiwyg {
await this.addMarkup(MarkupType.code);
} else if (option.label === qpItems[4].label) {
await this.addMarkup(MarkupType.codeblock);
} else if (option.label === qpItems[5].label) {
await this.addMarkup(MarkupType.blockquote);
}
}
}));

View File

@@ -20,7 +20,9 @@ export const DEFAULT_CONTENT_TYPE: ContentType = {
{
"title": "Publishing date",
"name": "date",
"type": "datetime"
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",

View File

@@ -29,13 +29,17 @@ export const COMMAND_NAME = {
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
dashboardMedia: getCommandName("dashboard.media"),
dashboardSnippets: getCommandName("dashboard.snippets"),
dashboardData: getCommandName("dashboard.data"),
dashboardClose: getCommandName("dashboard.close"),
promote: getCommandName("promoteSettings"),
insertImage: getCommandName("insertImage"),
createFolder: getCommandName("createFolder"),
diagnostics: getCommandName("diagnostics"),
// Insert dashboards
insertImage: getCommandName("insertImage"),
insertSnippet: getCommandName("insertSnippet"),
// WYSIWYG
bold: getCommandName("markup.bold"),
italic: getCommandName("markup.italic"),

View File

@@ -13,5 +13,11 @@ export const ExtensionState = {
Media: {
Sorting: `frontMatter:Dashboard:Media:Sorting`,
}
},
Updates: {
v7_0_0: {
dateFields: `frontMatter:Updates:v7.0.0:dateFields`
}
}
};

View File

@@ -1,17 +1,22 @@
export const TelemetryEvent = {
activate: 'activate',
initialization: 'initialization',
registerFolder: 'registerFolder',
unregisterFolder: 'unregisterFolder',
promoteSettings: 'promoteSettings',
// Commands
openContentDashboard: 'openContentDashboard',
openMediaDashboard: 'openMediaDashboard',
openDataDashboard: 'openDataDashboard',
openSnippetsDashboard: 'openSnippetsDashboard',
closeDashboard: 'closeDashboard',
// Other actions
generateSlug: 'generateSlug',
createContentFromTemplate: 'createContentFromTemplate',
createContentFromContentType: 'createContentFromContentType',
registerFolder: 'registerFolder',
unregisterFolder: 'unregisterFolder',
addMediaFolder: 'addMediaFolder',
promoteSettings: 'promoteSettings',
openPreview: 'openPreview',
uploadMedia: 'uploadMedia',
refreshMedia: 'refreshMedia',
@@ -20,9 +25,14 @@ export const TelemetryEvent = {
updateMediaMetadata: 'updateMediaMetadata',
openExplorerView: 'openExplorerView',
// Custom scripts
runCustomScript: 'runCustomScript',
runMediaScript: 'runMediaScript',
// Webviews
webviewWelcomeScreen: 'webviewWelcomeScreen',
webviewMediaView: 'webviewMediaView',
webviewDataView: 'webviewDataView',
webviewContentsView: 'webviewContentsView',
webviewSnippetsView: 'webviewSnippetsView',
};

439
src/constants/charCode.ts Normal file
View File

@@ -0,0 +1,439 @@
/**
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
*
* SOURCE: https://github.com/microsoft/vscode/blob/32b031eeefc4fd27a21659d35070967bfe965bcc/src/vs/base/common/charCode.ts
*/
export const enum CharCode {
Null = 0,
/**
* The `\b` character.
*/
Backspace = 8,
/**
* The `\t` character.
*/
Tab = 9,
/**
* The `\n` character.
*/
LineFeed = 10,
/**
* The `\r` character.
*/
CarriageReturn = 13,
Space = 32,
/**
* The `!` character.
*/
ExclamationMark = 33,
/**
* The `"` character.
*/
DoubleQuote = 34,
/**
* The `#` character.
*/
Hash = 35,
/**
* The `$` character.
*/
DollarSign = 36,
/**
* The `%` character.
*/
PercentSign = 37,
/**
* The `&` character.
*/
Ampersand = 38,
/**
* The `'` character.
*/
SingleQuote = 39,
/**
* The `(` character.
*/
OpenParen = 40,
/**
* The `)` character.
*/
CloseParen = 41,
/**
* The `*` character.
*/
Asterisk = 42,
/**
* The `+` character.
*/
Plus = 43,
/**
* The `,` character.
*/
Comma = 44,
/**
* The `-` character.
*/
Dash = 45,
/**
* The `.` character.
*/
Period = 46,
/**
* The `/` character.
*/
Slash = 47,
Digit0 = 48,
Digit1 = 49,
Digit2 = 50,
Digit3 = 51,
Digit4 = 52,
Digit5 = 53,
Digit6 = 54,
Digit7 = 55,
Digit8 = 56,
Digit9 = 57,
/**
* The `:` character.
*/
Colon = 58,
/**
* The `;` character.
*/
Semicolon = 59,
/**
* The `<` character.
*/
LessThan = 60,
/**
* The `=` character.
*/
Equals = 61,
/**
* The `>` character.
*/
GreaterThan = 62,
/**
* The `?` character.
*/
QuestionMark = 63,
/**
* The `@` character.
*/
AtSign = 64,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
/**
* The `[` character.
*/
OpenSquareBracket = 91,
/**
* The `\` character.
*/
Backslash = 92,
/**
* The `]` character.
*/
CloseSquareBracket = 93,
/**
* The `^` character.
*/
Caret = 94,
/**
* The `_` character.
*/
Underline = 95,
/**
* The ``(`)`` character.
*/
BackTick = 96,
a = 97,
b = 98,
c = 99,
d = 100,
e = 101,
f = 102,
g = 103,
h = 104,
i = 105,
j = 106,
k = 107,
l = 108,
m = 109,
n = 110,
o = 111,
p = 112,
q = 113,
r = 114,
s = 115,
t = 116,
u = 117,
v = 118,
w = 119,
x = 120,
y = 121,
z = 122,
/**
* The `{` character.
*/
OpenCurlyBrace = 123,
/**
* The `|` character.
*/
Pipe = 124,
/**
* The `}` character.
*/
CloseCurlyBrace = 125,
/**
* The `~` character.
*/
Tilde = 126,
U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent
U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent
U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent
U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde
U_Combining_Macron = 0x0304, // U+0304 Combining Macron
U_Combining_Overline = 0x0305, // U+0305 Combining Overline
U_Combining_Breve = 0x0306, // U+0306 Combining Breve
U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above
U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis
U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above
U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above
U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent
U_Combining_Caron = 0x030C, // U+030C Combining Caron
U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above
U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above
U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent
U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu
U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve
U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above
U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above
U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above
U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right
U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below
U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below
U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below
U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below
U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above
U_Combining_Horn = 0x031B, // U+031B Combining Horn
U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below
U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below
U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below
U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below
U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below
U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below
U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below
U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below
U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below
U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below
U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below
U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla
U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek
U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below
U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below
U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below
U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below
U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below
U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below
U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below
U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below
U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below
U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line
U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line
U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay
U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay
U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay
U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay
U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay
U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below
U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below
U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below
U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below
U_Combining_X_Above = 0x033D, // U+033D Combining X Above
U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde
U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline
U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark
U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark
U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni
U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis
U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos
U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni
U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above
U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below
U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below
U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below
U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above
U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above
U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above
U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below
U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below
U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner
U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above
U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above
U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata
U_Combining_X_Below = 0x0353, // U+0353 Combining X Below
U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below
U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below
U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below
U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above
U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right
U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below
U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below
U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above
U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below
U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve
U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron
U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below
U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde
U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve
U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below
U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A
U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E
U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I
U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O
U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U
U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C
U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D
U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H
U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M
U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R
U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T
U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V
U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X
/**
* Unicode Character 'LINE SEPARATOR' (U+2028)
* http://www.fileformat.info/info/unicode/char/2028/index.htm
*/
LINE_SEPARATOR = 0x2028,
/**
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
* http://www.fileformat.info/info/unicode/char/2029/index.htm
*/
PARAGRAPH_SEPARATOR = 0x2029,
/**
* Unicode Character 'NEXT LINE' (U+0085)
* http://www.fileformat.info/info/unicode/char/0085/index.htm
*/
NEXT_LINE = 0x0085,
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT
U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS
U_MACRON = 0x00AF, // U+00AF MACRON
U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT
U_CEDILLA = 0x00B8, // U+00B8 CEDILLA
U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD
U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD
U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD
U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD
U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING
U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING
U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK
U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK
U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN
U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN
U_BREVE = 0x02D8, // U+02D8 BREVE
U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE
U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE
U_OGONEK = 0x02DB, // U+02DB OGONEK
U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE
U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT
U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK
U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT
U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR
U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR
U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR
U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR
U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR
U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK
U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK
U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED
U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD
U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD
U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD
U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD
U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING
U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT
U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE
U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON
U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE
U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE
U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE
U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE
U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF
U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF
U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW
U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN
U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS
U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS
U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS
U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI
U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI
U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI
U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA
U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA
U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI
U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA
U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA
U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI
U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA
U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA
U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA
U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA
U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA
U_IDEOGRAPHIC_FULL_STOP = 0x3002, // U+3002 IDEOGRAPHIC FULL STOP
U_LEFT_CORNER_BRACKET = 0x300C, // U+300C LEFT CORNER BRACKET
U_RIGHT_CORNER_BRACKET = 0x300D, // U+300D RIGHT CORNER BRACKET
U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010, // U+3010 LEFT BLACK LENTICULAR BRACKET
U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011, // U+3011 RIGHT BLACK LENTICULAR BRACKET
U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
/**
* UTF-8 BOM
* Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
* http://www.fileformat.info/info/unicode/char/feff/index.htm
*/
UTF8_BOM = 65279,
U_FULLWIDTH_SEMICOLON = 0xFF1B, // U+FF1B FULLWIDTH SEMICOLON
U_FULLWIDTH_COMMA = 0xFF0C, // U+FF0C FULLWIDTH COMMA
}

View File

@@ -1,9 +1,11 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
canOpenPreview: "frontMatterCanOpenPreview",
canOpenDashboard: "frontMatterCanOpenDashboard",
canInit: "frontMatter:CanInit",
initialized: "frontMatter:Initialized",
canOpenPreview: "frontMatter:CanOpenPreview",
canOpenDashboard: "frontMatter:CanOpenDashboard",
isEnabled: "frontMatter:enabled",
isDashboardOpen: "frontMatter:dashboard:open",
wysiwyg: "frontMatter:markdown:wysiwyg",
backer: "frontMatter:backers:supporter",
isValidFile: "frontMatter:file:isValid",
};

View File

@@ -8,6 +8,7 @@ export * from './Links';
export * from './LocalStore';
export * from './Navigation';
export * from './TelemetryEvent';
export * from './charCode';
export * from './charMap';
export * from './context';
export * from './settings';

View File

@@ -12,8 +12,6 @@ export const SETTING_TAXONOMY_FIELD_GROUPS = "taxonomy.fieldGroups";
export const SETTING_DATE_FORMAT = "taxonomy.dateFormat";
export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields";
export const SETTING_TAXONOMY_CONTENT_TYPES = "taxonomy.contentTypes";
export const SETTING_DATE_FIELD = "taxonomy.dateField";
export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";
export const SETTING_SLUG_PREFIX = "taxonomy.slugPrefix";
export const SETTING_SLUG_SUFFIX = "taxonomy.slugSuffix";
@@ -43,35 +41,46 @@ 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_PAGE_FOLDERS = "content.pageFolders";
export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTINGS_CONTENT_SORTING = "content.sorting";
export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTINGS_CONTENT_PLACEHOLDERS = "content.placeholders";
export const SETTING_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTING_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTING_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTING_CONTENT_SORTING = "content.sorting";
export const SETTING_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTING_CONTENT_PLACEHOLDERS = "content.placeholders";
export const SETTING_CONTENT_SNIPPETS = "content.snippets";
export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTING_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
export const SETTING_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTINGS_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
export const SETTING_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTINGS_DATA_FILES = "data.files";
export const SETTINGS_DATA_FOLDERS = "data.folders";
export const SETTINGS_DATA_TYPES = "data.types";
export const SETTING_DATA_FILES = "data.files";
export const SETTING_DATA_FOLDERS = "data.folders";
export const SETTING_DATA_TYPES = "data.types";
export const SETTINGS_FILE_PRESERVE_CASING = "file.preserveCasing";
export const SETTING_FILE_PRESERVE_CASING = "file.preserveCasing";
export const SETTINGS_FRAMEWORK_ID = "framework.id";
export const SETTINGS_FRAMEWORK_START = "framework.startCommand";
export const SETTING_FRAMEWORK_ID = "framework.id";
export const SETTING_FRAMEWORK_START = "framework.startCommand";
export const SETTING_SITE_BASEURL = "site.baseURL";
/**
* @deprecated
*/
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTING_CONTENT_FOLDERS = "content.folders";
/**
* @deprecated
* Use the `isPublishDate` property on the content type datetime field instead
*/
export const SETTING_DATE_FIELD = "taxonomy.dateField";
/**
* @deprecated
* Use the `isModifiedDate` property on the content type datetime field instead
*/
export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";

View File

@@ -13,6 +13,7 @@ export enum DashboardMessage {
getMedia = 'getMedia',
copyToClipboard = 'copyToClipboard',
refreshMedia = 'refreshMedia',
refreshPages = 'refreshPages',
uploadMedia = 'uploadMedia',
deleteMedia = 'deleteMedia',
revealMedia = 'revealMedia',
@@ -25,4 +26,7 @@ export enum DashboardMessage {
getDataEntries = 'getDataEntries',
putDataEntries = 'putDataEntries',
sendTelemetry = 'sendTelemetry',
insertSnippet = 'insertSnippet',
addSnippet = 'addSnippet',
updateSnippet = 'updateSnippet',
}

View File

@@ -2,7 +2,6 @@ import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { Page } from '../../models';
import { SettingsSelector } from '../../state';
import { Header } from '../Header';
import { Overview } from './Overview';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
@@ -11,6 +10,7 @@ import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
export interface IContentsProps {
pages: Page[];
@@ -30,17 +30,14 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loadin
}, []);
return (
<div className="flex flex-col h-full overflow-auto">
<Header
folders={pageFolders}
totalPages={pageItems.length}
settings={settings} />
<PageLayout
folders={pageFolders}
totalPages={pageItems.length}>
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
</div>
</PageLayout>
);
};

View File

@@ -23,7 +23,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
if (view === DashboardViewType.Grid) {
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 border border-gray-100 dark:border-vulcan-50`}
<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 dark:shadow-none hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50`}
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">
{

View File

@@ -9,6 +9,7 @@ import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { NavigationType } from '../models';
import { DataView } from './DataView';
import { Snippets } from './SnippetsView/Snippets';
export interface IDashboardProps {
showWelcome: boolean;
@@ -31,6 +32,14 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
return <WelcomeScreen settings={settings} />;
}
if (view === NavigationType.Snippets) {
return (
<main className={`h-full w-full`}>
<Snippets />
</main>
);
}
if (view === NavigationType.Media) {
return (
<main className={`h-full w-full`}>

View File

@@ -15,7 +15,7 @@ import { arrayMoveImmutable } from 'array-move';
import { EmptyView } from './EmptyView';
import { Container } from './SortableContainer';
import { SortableItem } from './SortableItem';
import { ChevronRightIcon } from '@heroicons/react/outline';
import { ChevronRightIcon, DatabaseIcon } from '@heroicons/react/outline';
import { ToastContainer, toast, Slide } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { DataType } from '../../../models/DataType';
@@ -136,96 +136,109 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (props: React.P
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
<div className="relative w-full flex-grow mx-auto overflow-hidden">
{
(settings?.dataFiles && settings.dataFiles.length > 0) ? (
<div className="relative w-full flex-grow mx-auto overflow-hidden">
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside className={`flex flex-col flex-grow overflow-y-auto border-r border-gray-200 dark:border-vulcan-300 py-6 px-4 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Select your data type</h2>
<aside className={`flex flex-col flex-grow overflow-y-auto border-r border-gray-200 dark:border-vulcan-300 py-6 px-4 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Select your data type</h2>
<nav className={`flex-1 py-4 -mx-4 `}>
<div className={`divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>
{
(dataFiles && dataFiles.length > 0) && (
dataFiles.map((dataFile) => (
<button
key={dataFile.id}
type='button'
className={`px-4 py-2 flex items-center text-sm font-medium w-full text-left hover:bg-gray-200 dark:hover:bg-vulcan-400 hover:text-vulcan-500 dark:hover:text-whisper-500 ${selectedData?.id === dataFile.id ? 'bg-gray-300 dark:bg-vulcan-300 text-vulcan-500 dark:text-whisper-500' : 'text-gray-500 dark:text-whisper-900'}`}
onClick={() => setSchema(dataFile)}>
<ChevronRightIcon className='-ml-1 w-5 mr-2' />
<span>{dataFile.title}</span>
</button>
)
))
}
</div>
</nav>
</aside>
</div>
<section className={`pl-64 flex min-w-0 h-full`}>
{
selectedData ? (
<>
<div className={`w-1/3 py-6 px-4 flex-1 border-r border-gray-200 dark:border-vulcan-300 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Your {selectedData.title.toLowerCase()} data items</h2>
<div className='py-4'>
<nav className={`flex-1 py-4 -mx-4 `}>
<div className={`divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>
{
(dataEntries && dataEntries.length > 0) ? (
<>
<Container onSortEnd={onSortEnd} useDragHandle>
{
(dataEntries || []).map((dataEntry, idx) => (
<SortableItem
key={dataEntry[selectedData.labelField] || `entry-${idx}`}
value={dataEntry[selectedData.labelField] || `Entry ${idx+1}`}
index={idx}
crntIndex={idx}
selectedIndex={selectedIndex}
onSelectedIndexChange={(index: number) => setSelectedIndex(index)}
onDeleteItem={deleteItem}
/>
))
}
</Container>
<Button
className='mt-4'
onClick={() => setSelectedIndex(null)}>
Add a new entry
</Button>
</>
) : (
<div className={`flex flex-col items-center justify-center`}>
<p className={`text-gray-500 dark:text-whisper-900`}>No {selectedData.title.toLowerCase()} data entries found</p>
</div>
)
(dataFiles && dataFiles.length > 0) && (
dataFiles.map((dataFile) => (
<button
key={dataFile.id}
type='button'
className={`px-4 py-2 flex items-center text-sm font-medium w-full text-left hover:bg-gray-200 dark:hover:bg-vulcan-400 hover:text-vulcan-500 dark:hover:text-whisper-500 ${selectedData?.id === dataFile.id ? 'bg-gray-300 dark:bg-vulcan-300 text-vulcan-500 dark:text-whisper-500' : 'text-gray-500 dark:text-whisper-900'}`}
onClick={() => setSchema(dataFile)}>
<ChevronRightIcon className='-ml-1 w-5 mr-2' />
<span>{dataFile.title}</span>
</button>
)
))
}
</div>
</div>
<div className={`w-2/3 py-6 px-4 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Create or modify your {selectedData.title.toLowerCase()} data</h2>
{
selectedData ? (
<DataForm
schema={selectedData?.schema}
model={(dataEntries && selectedIndex !== null && selectedIndex !== undefined) ? dataEntries[selectedIndex] : null}
onSubmit={onSubmit}
onClear={() => setSelectedIndex(null)} />
) : (
<p>Select a data type to get started</p>
)
}
</div>
</>
) : (
<EmptyView />
)
}
</section>
</div>
</nav>
</aside>
</div>
<section className={`pl-64 flex min-w-0 h-full`}>
{
selectedData ? (
<>
<div className={`w-1/3 py-6 px-4 flex-1 border-r border-gray-200 dark:border-vulcan-300 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Your {selectedData.title.toLowerCase()} data items</h2>
<div className='py-4'>
{
(dataEntries && dataEntries.length > 0) ? (
<>
<Container onSortEnd={onSortEnd} useDragHandle>
{
(dataEntries || []).map((dataEntry, idx) => (
<SortableItem
key={dataEntry[selectedData.labelField] || `entry-${idx}`}
value={dataEntry[selectedData.labelField] || `Entry ${idx+1}`}
index={idx}
crntIndex={idx}
selectedIndex={selectedIndex}
onSelectedIndexChange={(index: number) => setSelectedIndex(index)}
onDeleteItem={deleteItem}
/>
))
}
</Container>
<Button
className='mt-4'
onClick={() => setSelectedIndex(null)}>
Add a new entry
</Button>
</>
) : (
<div className={`flex flex-col items-center justify-center`}>
<p className={`text-gray-500 dark:text-whisper-900`}>No {selectedData.title.toLowerCase()} data entries found</p>
</div>
)
}
</div>
</div>
<div className={`w-2/3 py-6 px-4 overflow-auto`}>
<h2 className={`text-lg text-gray-500 dark:text-whisper-900`}>Create or modify your {selectedData.title.toLowerCase()} data</h2>
{
selectedData ? (
<DataForm
schema={selectedData?.schema}
model={(dataEntries && selectedIndex !== null && selectedIndex !== undefined) ? dataEntries[selectedIndex] : null}
onSubmit={onSubmit}
onClear={() => setSelectedIndex(null)} />
) : (
<p>Select a data type to get started</p>
)
}
</div>
</>
) : (
<EmptyView />
)
}
</section>
</div>
) : (
<div className='w-full h-full flex items-center justify-center'>
<div className='flex flex-col items-center text-gray-500 dark:text-whisper-900'>
<DatabaseIcon className='w-32 h-32' />
<p className='text-3xl mt-2'>No data files found</p>
<p className='text-xl mt-4'>
<a className={`text-teal-700 hover:text-teal-900`} href={`https://frontmatter.codes/docs/dashboard#data-files-view`} title={`Read read more to get started using data files`}>Read read more to get started using data files</a></p>
</div>
</div>
)
}
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />

View File

@@ -21,6 +21,7 @@ import { CustomScript } from '../../../models';
import { LightningBoltIcon, PlusIcon } from '@heroicons/react/outline';
export interface IHeaderProps {
header?: React.ReactNode;
settings: Settings | null;
// Navigation
@@ -30,7 +31,7 @@ export interface IHeaderProps {
folders?: string[];
}
export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
const [ view, setView ] = useRecoilState(DashboardViewAtom);
@@ -80,7 +81,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
<Searchbox />
<div className={`flex items-center space-x-4`}>
<div className={`flex items-center justify-end space-x-4 flex-1`}>
<Startup settings={settings} />
<ChoiceButton
@@ -148,6 +149,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
</>
)
}
{
header
}
</div>
);
};

View File

@@ -0,0 +1,36 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { RefreshIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilState, useResetRecoilState } from 'recoil';
import { DashboardMessage } from '../../DashboardMessage';
import { CategoryAtom, FolderAtom, LoadingAtom, SearchAtom, SortingAtom, TagAtom } from '../../state';
export interface IRefreshPagesProps {}
export const RefreshPages: React.FunctionComponent<IRefreshPagesProps> = (props: React.PropsWithChildren<IRefreshPagesProps>) => {
const [, setLoading] = useRecoilState(LoadingAtom);
const resetSearch = useResetRecoilState(SearchAtom);
const resetSorting = useResetRecoilState(SortingAtom);
const resetFolder = useResetRecoilState(FolderAtom);
const resetTag = useResetRecoilState(TagAtom);
const resetCategory = useResetRecoilState(CategoryAtom);
const refresh = () => {
setLoading(true);
resetSearch();
resetSorting();
resetFolder();
resetTag();
resetCategory();
Messenger.send(DashboardMessage.refreshPages);
}
return (
<button className={`mr-2 text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500`}
title="Refresh dashboard"
onClick={refresh}>
<RefreshIcon className={`h-5 w-5`} />
<span className="sr-only">Refresh dashboard</span>
</button>
);
};

View File

@@ -3,6 +3,7 @@ import * as React from 'react';
import { useRecoilState } from 'recoil';
import { useDebounce } from '../../../hooks/useDebounce';
import { SearchAtom } from '../../state';
import { RefreshPages } from './RefreshPages';
export interface ISearchboxProps {
placeholder?: string;
@@ -28,7 +29,7 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({placeholder
}, [debounceSearch]);
return (
<div className="flex space-x-4 flex-1">
<div className="flex space-x-4 flex-1">
<div className="min-w-0">
<label htmlFor="search" className="sr-only">Search</label>
<div className="relative flex justify-center">
@@ -46,6 +47,8 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({placeholder
/>
</div>
</div>
<RefreshPages />
</div>
);
};

View File

@@ -18,6 +18,8 @@ export interface ISortingProps {
}
export const sortOptions: SortingOption[] = [
{ name: "Published (asc)", id: SortOption.PublishedAsc, order: SortOrder.asc, type: SortType.date },
{ name: "Published (desc)", id: SortOption.PublishedDesc, order: SortOrder.desc, type: SortType.date },
{ name: "Last modified (asc)", id: SortOption.LastModifiedAsc, order: SortOrder.asc, type: SortType.date },
{ name: "Last modified (desc)", id: SortOption.LastModifiedDesc, order: SortOrder.desc, type: SortType.date },
{ name: "By filename (asc)", id: SortOption.FileNameAsc, order: SortOrder.asc, type: SortType.string },

View File

@@ -1,4 +1,4 @@
import { DatabaseIcon, PhotographIcon } from '@heroicons/react/outline';
import { DatabaseIcon, PhotographIcon, ScissorsIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
@@ -29,17 +29,20 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</Tab>
</li>
{
(settings?.dataFiles && settings.dataFiles.length > 0) && (
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Data}
onNavigate={onNavigate}>
<DatabaseIcon className={`h-6 w-auto mr-2`} /><span>Data</span>
</Tab>
</li>
)
}
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Snippets}
onNavigate={onNavigate}>
<ScissorsIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
</Tab>
</li>
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Data}
onNavigate={onNavigate}>
<DatabaseIcon className={`h-6 w-auto mr-2`} /><span>Data</span>
</Tab>
</li>
</ul>
);
};

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { Header } from '../Header';
export interface IPageLayoutProps {
header?: React.ReactNode;
folders?: string[] | undefined
totalPages?: number | undefined
}
export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({ header, folders, totalPages, children }: React.PropsWithChildren<IPageLayoutProps>) => {
const settings = useRecoilValue(SettingsSelector);
return (
<div className="flex flex-col h-full overflow-auto">
<Header
header={header}
folders={folders}
totalPages={totalPages}
settings={settings} />
<div className="w-full flex justify-between flex-col flex-grow max-w-7xl mx-auto pt-6 px-4">
{ children }
</div>
</div>
);
};

View File

@@ -0,0 +1,268 @@
import { Dialog, Transition } from '@headlessui/react';
import { PencilAltIcon, XIcon } from '@heroicons/react/outline';
import { format } from 'date-fns';
import { basename } from 'path';
import * as React from 'react';
import { Fragment, useMemo } from 'react';
import { DateHelper } from '../../../helpers/DateHelper';
import { MediaInfo } from '../../../models';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { useRecoilValue } from 'recoil';
import { PageSelector, SelectedMediaFolderSelector } from '../../state';
export interface IDetailsSlideOverProps {
imgSrc: string;
size: string;
dimensions: string;
folder: string;
media: MediaInfo;
showForm: boolean;
onEdit: () => void;
onEditClose: () => void;
onDismiss: () => void;
}
export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> = ({ imgSrc, size, dimensions, media, folder, showForm, onEdit, onEditClose, onDismiss }: React.PropsWithChildren<IDetailsSlideOverProps>) => {
const [ filename, setFilename ] = React.useState<string>(media.filename);
const [ caption, setCaption ] = React.useState<string | undefined>(media.caption);
const [ alt, setAlt ] = React.useState(media.alt);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const page = useRecoilValue(PageSelector);
const createdDate = useMemo(() => DateHelper.tryParse(media.ctime), [media]);
const modifiedDate = useMemo(() => DateHelper.tryParse(media.mtime), [media]);
const fileInfo = filename ? basename(filename).split('.') : null;
const extension = fileInfo?.pop();
const name = fileInfo?.join('.');
const onSubmitMetadata = () => {
Messenger.send(DashboardMessage.updateMediaMetadata, {
file: media.fsPath,
filename,
caption,
alt,
folder: selectedFolder,
page
});
onEditClose();
};
React.useEffect(() => {
setAlt(media.alt);
setCaption(media.caption);
setFilename(media.filename);
}, [media]);
return (
<Transition.Root show={true} as={Fragment}>
<Dialog onClose={onDismiss} as={'div' as any} className="fixed inset-0 overflow-hidden z-50">
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0 bg-vulcan-500 bg-opacity-75 transition-opacity" />
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="pointer-events-auto w-screen max-w-md">
<div className="flex h-full flex-col overflow-y-scroll bg-white dark:bg-vulcan-400 border-l border-whisper-900 dark:border-vulcan-50 py-6 shadow-xl">
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<Dialog.Title className="text-lg font-medium text-vulcan-300 dark:text-whisper-900">View details</Dialog.Title>
<div className="ml-3 flex h-7 items-center">
<button
type="button"
className="text-vulcan-300 dark:text-whisper-900 hover:text-vulcan-500 dark:hover:text-whisper-500 focus:outline-none"
onClick={onDismiss}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div className="relative mt-6 flex-1 px-4 sm:px-6">
<div className="absolute inset-0 px-4 sm:px-6 space-y-8">
<div>
<div className="block w-full aspect-w-10 aspect-h-7 overflow-hidden border-gray-200 dark:border-vulcan-200 border">
<img src={imgSrc} alt={media.filename} className="object-cover" />
</div>
<div className="mt-4 flex items-start justify-between">
<div>
<h2 className="text-lg font-medium text-vulcan-300 dark:text-whisper-500"><span className="sr-only">Details for </span>{media.filename}</h2>
<p className="text-sm font-medium text-vulcan-100 dark:text-whisper-900">{size}</p>
</div>
</div>
</div>
<div>
{
showForm && (
<>
<h3 className="text-base text-vulcan-300 dark:text-whisper-500">Update metadata</h3>
<p className="text-sm font-medium text-vulcan-100 dark:text-whisper-900">Please specify the metadata you want to set for the file.</p>
<div className="flex flex-col py-3 space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Filename
</label>
<div className="relative mt-1">
<input
className="py-1 px-2 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 w-full"
value={name}
onChange={(e) => setFilename(`${e.target.value}.${extension}`)} />
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">
.{extension}
</span>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Caption
</label>
<div className="mt-1">
<textarea
rows={3}
className="py-1 px-2 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 w-full"
value={caption || ''}
onChange={(e) => setCaption(e.target.value)}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Alt tag value
</label>
<div className="mt-1">
<input
className="py-1 px-2 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 w-full"
value={alt || ''}
onChange={(e) => setAlt(e.target.value)}
/>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30"
onClick={onSubmitMetadata}
disabled={!filename}
>
Save
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
onClick={onEditClose}
>
Cancel
</button>
</div>
</>
)
}
{
!showForm && (
<>
<h3 className="text-base text-vulcan-300 dark:text-whisper-500 flex items-center">
<span>Metadata</span>
<button onClick={onEdit}>
<PencilAltIcon className='w-4 h-4 ml-2' aria-hidden="true" />
<span className='sr-only'>Edit</span>
</button>
</h3>
<dl className="mt-2 border-t border-b border-gray-200 dark:border-vulcan-200 divide-y divide-gray-200 dark:divide-vulcan-200">
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Filename</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{media.filename}</dd>
</div>
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Caption</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{media.caption || ""}</dd>
</div>
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Alternate text</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{media.alt || ""}</dd>
</div>
</dl>
</>
)
}
</div>
{
!showForm && (
<div>
<h3 className="text-base text-vulcan-300 dark:text-whisper-500">Information</h3>
<dl className="mt-2 border-t border-b border-gray-200 dark:border-vulcan-200 divide-y divide-gray-200 dark:divide-vulcan-200">
{
createdDate && (
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Created</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{format(createdDate, 'MMM dd, yyyy')}</dd>
</div>
)
}
{
modifiedDate && (
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Last modified</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{format(modifiedDate, 'MMM dd, yyyy')}</dd>
</div>
)
}
{
dimensions && (
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Dimensions</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{dimensions}</dd>
</div>
)
}
{
folder && (
<div className="py-3 flex justify-between text-sm font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Folder</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right break-all ml-6">{folder}</dd>
</div>
)
}
</dl>
</div>
)
}
</div>
</div>
</div>
</div>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};

View File

@@ -1,5 +1,5 @@
import {FolderIcon} from '@heroicons/react/solid';
import { basename } from 'path';
import {DocumentIcon, FolderIcon} from '@heroicons/react/solid';
import { basename, join } from 'path';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { SelectedMediaFolderAtom } from '../../state';
@@ -14,12 +14,19 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({ folder,
const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
const isContentFolder = React.useMemo(() => !relFolderPath.includes(join('/', staticFolder || '', '/')), [relFolderPath, staticFolder]);
return (
<li className={`group relative hover:shadow-xl dark:hover:bg-vulcan-100 text-gray-600 hover:text-gray-700 dark:text-whisper-900 dark:hover:text-whisper-800 p-4`}>
<button className={`w-full flex flex-row items-center h-full`} onClick={() => setSelectedFolder(folder)}>
<div>
<FolderIcon className={`h-12 w-12 mr-4`} />
<li className={`group relative hover:bg-gray-200 dark:hover:bg-vulcan-100 text-gray-600 hover:text-gray-700 dark:text-whisper-900 dark:hover:text-whisper-800 p-4`}>
<button title={isContentFolder ? 'Content directory folder' : 'Public directory folder'} className={`w-full flex flex-row items-center h-full`} onClick={() => setSelectedFolder(folder)}>
<div className='relative mr-4'>
<FolderIcon className={`h-12 w-12`} />
{
isContentFolder && (
<span className='text-whisper-800 dark:text-vulcan-500 font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2'>C</span>
)
}
</div>
<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">

View File

@@ -1,6 +1,6 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { Menu } from '@headlessui/react';
import { ClipboardIcon, CodeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import { ClipboardIcon, CodeIcon, EyeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import { basename, dirname } from 'path';
import * as React from 'react';
import { useEffect } from 'react';
@@ -10,10 +10,10 @@ import { parseWinPath } from '../../../helpers/parseWinPath';
import { ScriptType } from '../../../models';
import { MediaInfo } from '../../../models/MediaPaths';
import { DashboardMessage } from '../../DashboardMessage';
import { LightboxAtom, PageSelector, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
import { MenuItem, MenuItems } from '../Menu';
import { Alert } from '../Modals/Alert';
import { Metadata } from '../Modals/Metadata';
import { DetailsSlideOver } from './DetailsSlideOver';
import { MenuButton } from './MenuButton'
import { QuickAction } from './QuickAction';
@@ -25,13 +25,13 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const [ , setLightbox ] = useRecoilState(LightboxAtom);
const [ showAlert, setShowAlert ] = React.useState(false);
const [ showForm, setShowForm ] = React.useState(false);
const [ showDetails, setShowDetails ] = React.useState(false);
const [ caption, setCaption ] = React.useState(media.caption);
const [ alt, setAlt ] = React.useState(media.alt);
const [ filename, setFilename ] = React.useState<string | null>(null);
const settings = useRecoilValue(SettingsSelector);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const viewData = useRecoilValue(ViewDataSelector);
const page = useRecoilValue(PageSelector);
const getFolder = () => {
if (settings?.wsFolder && media.fsPath) {
@@ -125,25 +125,45 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
});
};
const calculateSize = () => {
let sizeDetails = [];
if (media?.dimensions) {
if (media.dimensions.width && media.dimensions.height) {
sizeDetails.push(`${media.dimensions.width}x${media.dimensions.height}`);
}
const getDimensions = () => {
if (media.dimensions) {
return `${media.dimensions.width} x ${media.dimensions.height}`;
}
return "";
};
const getSize = () => {
if (media?.size) {
const size = media.size / (1024*1024);
if (size > 1) {
sizeDetails.push(`${size.toFixed(2)} MB`);
return `${size.toFixed(2)} MB`;
} else {
sizeDetails.push(`${(size * 1024).toFixed(2)} KB`);
return `${(size * 1024).toFixed(2)} KB`;
}
}
return sizeDetails.join(" — ");
return '';
};
const getMediaDetails = () => {
let sizeDetails = [];
const dimensions = getDimensions();
if (dimensions) {
sizeDetails.push(dimensions);
}
const size = getSize();
if (size) {
sizeDetails.push(size);
}
return sizeDetails.join(" - ");
};
const viewMediaDetails = () => {
setShowDetails(true);
};
const openLightbox = () => {
@@ -152,24 +172,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const updateMetadata = () => {
setShowForm(true);
};
const submitMetadata = () => {
Messenger.send(DashboardMessage.updateMediaMetadata, {
file: media.fsPath,
filename,
caption,
alt,
folder: selectedFolder,
page
});
setShowForm(false);
// Reset the values
setAlt(media.alt);
setCaption(media.caption);
setFilename(getFileName());
setShowDetails(true);
};
const customScriptActions = () => {
@@ -200,13 +203,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
}
}, [media.fsPath]);
const fileInfo = filename ? basename(filename).split('.') : null;
const extension = fileInfo?.pop();
const name = fileInfo?.join('.');
return (
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50">
<li className="group relative bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50">
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
@@ -218,9 +217,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
<div className={`relative py-4 pl-4 pr-12`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className="flex items-center border border-transparent group-hover:bg-gray-50 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
<div className='hidden group-hover:inline-block h-5'>
<QuickAction
title='View media details'
onClick={viewMediaDetails}>
<EyeIcon className={`h-5 w-5`} aria-hidden="true" />
</QuickAction>
<QuickAction
title='Edit metadata'
onClick={updateMetadata}>
@@ -325,22 +330,18 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
)
}
{
media.alt && (
(!media.caption && media.alt) && (
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-2`}>Alt:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.alt}</span>
</p>
)
}
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-2`}>Folder:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getFolder()}</span>
</p>
{
(media?.size || media?.dimensions) && (
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-1`}>Size:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{calculateSize()}</span>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getMediaDetails()}</span>
</p>
)
}
@@ -348,60 +349,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</li>
{
showForm && (
<Metadata
title={`Set metadata for: ${basename(parseWinPath(media.fsPath) || "")}`}
description={`Please specify the metadata you want to set for the file.`}
okBtnText={`Save`}
cancelBtnText={`Cancel`}
dismiss={() => setShowForm(false)}
trigger={submitMetadata}
isSaveDisabled={!filename}>
<div className="flex flex-col space-y-2">
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Filename
</label>
<div className="relative mt-1">
<input
className="py-1 px-2 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 w-full"
value={name}
onChange={(e) => setFilename(`${e.target.value}.${extension}`)} />
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">
.{extension}
</span>
</div>
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Caption
</label>
<div className="mt-1">
<textarea
rows={3}
className="py-1 px-2 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 w-full"
value={caption || ''}
onChange={(e) => setCaption(e.target.value)}
/>
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Alt tag value
</label>
<div className="mt-1">
<input
className="py-1 px-2 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 w-full"
value={alt || ''}
onChange={(e) => setAlt(e.target.value)}
/>
</div>
</div>
</div>
</Metadata>
showDetails && (
<DetailsSlideOver
imgSrc={media.vsPath || ""}
size={getSize()}
dimensions={getDimensions()}
folder={getFolder()}
media={media}
showForm={showForm}
onEdit={() => setShowForm(true)}
onEditClose={() => setShowForm(false)}
onDismiss={() => { setShowDetails(false); setShowForm(false); }} />
)
}

View File

@@ -3,7 +3,6 @@ import {UploadIcon} from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { LoadingAtom, MediaFoldersAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { Header } from '../Header';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
import { Item } from './Item';
@@ -16,6 +15,9 @@ import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMat
import { FolderItem } from './FolderItem';
import useMedia from '../../hooks/useMedia';
import { TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { join } from 'path';
export interface IMediaProps {}
@@ -27,6 +29,25 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
const folders = useRecoilValue(MediaFoldersAtom);
const loading = useRecoilValue(LoadingAtom);
const allFolders = React.useMemo(() => {
// Check if content allows page bundle
if (viewData && viewData.data && typeof viewData.data.pageBundle !== "undefined" && !viewData.data.pageBundle) {
return folders.filter(f => parseWinPath(f).includes(join('/', settings?.staticFolder || '', '/')));
}
return folders;
}, [folders, viewData, settings?.staticFolder]);
const allMedia = React.useMemo(() => {
// Check if content allows page bundle
if (viewData && viewData.data && typeof viewData.data.pageBundle !== "undefined" && !viewData.data.pageBundle) {
return media.filter(m => parseWinPath(m.fsPath).includes(join('/', settings?.staticFolder || '', '/')));
}
return media;
}, [media, viewData, settings?.staticFolder]);
const onDrop = useCallback((acceptedFiles: File[]) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
@@ -56,16 +77,13 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
});
return (
<div className="flex flex-col h-full overflow-auto">
<Header settings={settings} />
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4" {...getRootProps()}>
<PageLayout>
<div className="w-full h-full" {...getRootProps()}>
{
viewData?.data?.filePath && (
<div className={`text-lg text-center mb-6`}>
<p>Select the image you want to use for your article.</p>
<p className={`opacity-80 text-base`}>You can also drag and drop images from your desktop and select that once uploaded.</p>
<p>Select the media file to add to your content.</p>
<p className={`opacity-80 text-base`}>You can also drag and drop images from your desktop and select them once uploaded.</p>
</div>
)
}
@@ -82,7 +100,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
}
{
(media.length === 0 && folders.length === 0 && !loading) && (
(allMedia.length === 0 && folders.length === 0 && !loading) && (
<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`} />
@@ -94,11 +112,11 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
}
{
folders && folders.length > 0 && (
allFolders && allFolders.length > 0 && (
<div className={`mb-8`}>
<List gap={0}>
{
folders && folders.map((folder) => (
allFolders.map((folder) => (
<FolderItem key={folder} folder={folder} staticFolder={settings?.staticFolder} wsFolder={settings?.wsFolder} />
))
}
@@ -109,7 +127,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
<List>
{
media.map((file) => (
allMedia.map((file) => (
<Item key={file.fsPath} media={file} />
))
}
@@ -123,6 +141,6 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
<Lightbox />
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
</div>
</PageLayout>
);
};

View File

@@ -2,7 +2,7 @@ import { Dialog, Transition } from '@headlessui/react';
import * as React from 'react';
import { Fragment, useRef } from 'react';
export interface IMetadataProps {
export interface IFormDialogProps {
title: string;
description: string;
okBtnText: string;
@@ -13,11 +13,10 @@ export interface IMetadataProps {
trigger: () => void;
}
export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren<IMetadataProps>) => {
export const FormDialog: React.FunctionComponent<IFormDialogProps> = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren<IFormDialogProps>) => {
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={true} as={Fragment}>
<Dialog className="fixed z-10 inset-0 overflow-y-auto" initialFocus={cancelButtonRef} onClose={() => dismiss()}>
@@ -67,7 +66,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, descri
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30"
className="w-full inline-flex justify-center border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30"
onClick={() => trigger()}
disabled={isSaveDisabled}
>
@@ -75,7 +74,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, descri
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
className="mt-3 w-full inline-flex justify-center border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
onClick={() => dismiss()}
ref={cancelButtonRef}
>

View File

@@ -0,0 +1,193 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { Snippet, SnippetField, Snippets } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { SettingsSelector, ViewDataSelector } from '../../state';
import { Alert } from '../Modals/Alert';
import { FormDialog } from '../Modals/FormDialog';
import { NewForm } from './NewForm';
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
export interface IItemProps {
title: string;
snippet: Snippet;
}
export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: React.PropsWithChildren<IItemProps>) => {
const viewData = useRecoilValue(ViewDataSelector);
const settings = useRecoilValue(SettingsSelector);
const [ showInsertDialog, setShowInsertDialog ] = useState(false);
const [ showEditDialog, setShowEditDialog ] = useState(false);
const [ showAlert, setShowAlert ] = React.useState(false);
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetOriginalBody, setSnippetOriginalBody ] = useState<string>('');
const formRef = useRef<SnippetFormHandle>(null);
const insertToArticle = () => {
formRef.current?.onSave();
setShowInsertDialog(false);
};
const reset = () => {
setShowEditDialog(false);
setSnippetTitle('');
setSnippetDescription('');
setSnippetOriginalBody('');
};
const onOpenEdit = useCallback(() => {
setSnippetTitle(title);
setSnippetDescription(snippet.description);
setSnippetOriginalBody(typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`));
setShowEditDialog(true);
}, [snippet]);
const onSnippetUpdate = useCallback(() => {
if (!snippetTitle || !snippetOriginalBody) {
reset();
return;
}
const snippets: Snippets = Object.assign({}, settings?.snippets || {});
const snippetLines = snippetOriginalBody.split("\n");
const crntSnippet = Object.assign({}, snippets[title]);
const fields = SnippetParser.getFields(snippetLines, crntSnippet.fields || [], crntSnippet?.openingTags, crntSnippet?.closingTags);
const snippetContents: Snippet = {
...crntSnippet,
fields,
description: snippetDescription || '',
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
};
// Check if new or update
if (title === snippetTitle) {
snippets[title] = snippetContents;
} else {
delete snippets[title];
snippets[snippetTitle] = snippetContents;
}
Messenger.send(DashboardMessage.updateSnippet, { snippets });
reset();
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody]);
const onDelete = useCallback(() => {
const snippets = Object.assign({}, settings?.snippets || {});
delete snippets[title];
Messenger.send(DashboardMessage.updateSnippet, { snippets });
setShowAlert(false);
}, [settings?.snippets, title]);
return (
<>
<li className="group relative overflow-hidden bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50 p-4 space-y-2">
<div className='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'>
<CodeIcon className='w-64 h-64 opacity-5 text-vulcan-200 dark:text-gray-400' />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
<div className='group-hover:hidden'>
<DotsHorizontalIcon className="w-4 h-4" />
</div>
<div className='hidden group-hover:flex space-x-2'>
{
viewData?.data?.filePath && (
<>
<button onClick={() => setShowInsertDialog(true)}>
<PlusIcon className='w-4 h-4' />
<span className='sr-only'>Insert snippet</span>
</button>
</>
)
}
<button onClick={onOpenEdit}>
<PencilIcon className='w-4 h-4' />
<span className='sr-only'>Edit snippet</span>
</button>
<button onClick={() => setShowAlert(true)}>
<TrashIcon className='w-4 h-4' />
<span className='sr-only'>Delete snippet</span>
</button>
</div>
</div>
</div>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{snippet.description}</p>
</li>
{
showInsertDialog && (
<FormDialog
title={`Insert snippet: ${title}`}
description={`Insert the ${title.toLowerCase()} snippet into the current article`}
isSaveDisabled={!viewData?.data?.filePath}
trigger={insertToArticle}
dismiss={() => setShowInsertDialog(false)}
okBtnText='Insert'
cancelBtnText='Cancel'>
<SnippetForm
ref={formRef}
snippet={snippet}
selection={viewData?.data?.selection} />
</FormDialog>
)
}
{
showEditDialog && (
<FormDialog
title={`Edit snippet: ${title}`}
description={`Edit the ${title.toLowerCase()} snippet`}
isSaveDisabled={!snippetTitle || !snippetOriginalBody}
trigger={onSnippetUpdate}
dismiss={reset}
okBtnText='Update'
cancelBtnText='Cancel'>
<NewForm
title={snippetTitle}
description={snippetDescription}
body={snippetOriginalBody}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)} />
</FormDialog>
)
}
{
showAlert && (
<Alert
title={`Delete snippet: ${title}`}
description={`Are you sure you want to delete the ${title.toLowerCase()} snippet?`}
okBtnText={`Delete`}
cancelBtnText={`Cancel`}
dismiss={() => setShowAlert(false)}
trigger={onDelete} />
)
}
</>
);
};

View File

@@ -0,0 +1,65 @@
import * as React from 'react';
export interface INewFormProps {
title: string;
description: string;
body: string;
onTitleUpdate: (value: string) => void;
onDescriptionUpdate: (value: string) => void;
onBodyUpdate: (value: string) => void;
}
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
return (
<div className='space-y-4'>
<div>
<label htmlFor={`title`} className="block text-sm font-medium capitalize">
Title <span className='text-red-400' title='Required field'>*</span>
</label>
<div className="mt-1">
<input
type='text'
name={`title`}
value={title || ""}
placeholder={`Snippet title`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onTitleUpdate(e.currentTarget.value)}
/>
</div>
</div>
<div>
<label htmlFor={`description`} className="block text-sm font-medium capitalize">
Description
</label>
<div className="mt-1">
<input
type='text'
name={`description`}
value={description || ""}
placeholder={`Snippet description`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onDescriptionUpdate(e.currentTarget.value)}
/>
</div>
</div>
<div>
<label htmlFor={`snippet`} className="block text-sm font-medium capitalize">
Snippet <span className='text-red-400' title='Required field'>*</span>
</label>
<div className="mt-1">
<textarea
name={`snippet`}
value={body || ""}
placeholder={`Snippet content`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onBodyUpdate(e.currentTarget.value)}
/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,132 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { processKnownPlaceholders } from '../../../helpers/PlaceholderHelper';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { Snippet, SnippetField, SnippetSpecialPlaceholders } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { SettingsAtom, ViewDataSelector } from '../../state';
import { SnippetInputField } from './SnippetInputField';
export interface ISnippetFormProps {
snippet: Snippet;
selection: string | undefined;
}
export interface SnippetFormHandle {
onSave: () => void;
}
const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFormProps> = ({ snippet, selection }, ref) => {
const viewData = useRecoilValue(ViewDataSelector);
const [ fields, setFields ] = useState<SnippetField[]>([]);
const settings = useRecoilValue(SettingsAtom);
const onTextChange = useCallback((field: SnippetField, value: string) => {
setFields(prevFields => prevFields.map(f => f.name === field.name ? { ...f, value } : f));
}, [setFields]);
const insertPlaceholderValues = useCallback((value: SnippetSpecialPlaceholders) => {
if (value === "FM_SELECTED_TEXT") {
return selection || "";
}
value = processKnownPlaceholders(value, viewData?.data?.fileTitle || "", settings?.date.format || "");
return value;
}, [selection]);
const snippetBody = useMemo(() => {
let body = typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`);
const obj: any = {};
for (const field of fields) {
obj[field.name] = field.value;
}
return SnippetParser.render(body, obj, snippet.openingTags, snippet.closingTags);
}, [fields, snippet]);
const shouldShowField = (fieldName: string, idx: number, allFields: SnippetField[]) => {
const crntField = allFields.findIndex(f => f.name === fieldName);
if (crntField < idx) {
return false;
}
return true;
}
useImperativeHandle(ref, () => ({
onSave() {
if (!snippetBody) {
return;
}
Messenger.send(DashboardMessage.insertSnippet, {
file: viewData?.data?.filePath,
snippet: snippetBody
});
}
}));
useEffect(() => {
// Get all placeholder variables from the snippet
const body = typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`);
const placeholders = SnippetParser.getPlaceholders(body, snippet.openingTags, snippet.closingTags);
const allFields: SnippetField[] = [];
const snippetFields = snippet.fields || [];
for (const fieldName of placeholders) {
const field = snippetFields.find(f => f.name === fieldName);
if (field) {
allFields.push({
...field,
value: insertPlaceholderValues(field.default || "")
});
} else {
allFields.push({
name: fieldName,
title: fieldName,
type: "string",
single: true,
value: ""
});
}
}
setFields(allFields);
}, [snippet]);
return (
<div>
<pre className='border border-opacity-40 p-2 whitespace-pre-wrap break-words'>
{snippetBody}
</pre>
<div className='space-y-4 mt-4'>
{
fields.map((field: SnippetField, index: number, allFields: SnippetField[]) => (
shouldShowField(field.name, index, allFields) && (
<div key={index}>
<label htmlFor={field.name} className="block text-sm font-medium capitalize">
{field.title || field.name}
</label>
<div className="mt-1">
<SnippetInputField
field={field}
onValueChange={onTextChange} />
</div>
</div>
)
))
}
</div>
</div>
);
};
export default React.forwardRef(SnippetForm);

View File

@@ -0,0 +1,54 @@
import * as React from 'react';
import { ChevronDownIcon } from '@heroicons/react/outline';
import { Choice, SnippetField } from '../../../models';
export interface ISnippetInputFieldProps {
field: SnippetField;
onValueChange: (field: SnippetField, value: string) => void
}
export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps> = ({ field, onValueChange }: React.PropsWithChildren<ISnippetInputFieldProps>) => {
if (field.type === 'choice') {
return (
<div className='relative'>
<select
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={e => onValueChange(field, e.target.value)}>
{
(field.choices || [])?.map((option: string | Choice, index: number) => (
typeof option === 'string' ?
<option key={index} value={option}>{option}</option> :
<option key={index} value={option.id}>{option.title}</option>
))
}
</select>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
)
}
if (field.type === 'string' && !field.single) {
return (
<textarea
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onValueChange(field, e.currentTarget.value)}
/>
)
}
return (
<input
type="text"
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onValueChange(field, e.currentTarget.value)}
/>
);
};

View File

@@ -0,0 +1,135 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, PlusSmIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { DashboardMessage } from '../../DashboardMessage';
import { SettingsSelector, ViewDataSelector } from '../../state';
import { PageLayout } from '../Layout/PageLayout';
import { FormDialog } from '../Modals/FormDialog';
import { SponsorMsg } from '../SponsorMsg';
import { Item } from './Item';
import { NewForm } from './NewForm';
export interface ISnippetsProps {}
export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.PropsWithChildren<ISnippetsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetBody, setSnippetBody ] = useState<string>('');
const [ showCreateDialog, setShowCreateDialog ] = useState(false);
const snippets = settings?.snippets || {};
const snippetKeys = useMemo(() => Object.keys(snippets) || [], [settings?.snippets]);
const onSnippetAdd = useCallback(() => {
if (!snippetTitle || !snippetBody) {
reset();
return;
}
const fields = SnippetParser.getFields(snippetBody, []);
Messenger.send(DashboardMessage.addSnippet, {
title: snippetTitle,
description: snippetDescription || '',
body: snippetBody,
fields
});
reset();
}, [snippetTitle, snippetDescription, snippetBody]);
const reset = () => {
setShowCreateDialog(false);
setSnippetTitle('');
setSnippetDescription('');
setSnippetBody('');
};
useEffect(() => {
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSnippetsView
});
}, []);
return (
<PageLayout
header={(
<div
className="py-3 px-4 flex items-center justify-between border-b border-gray-300 dark:border-vulcan-100"
aria-label="Pagination"
>
<div className="flex flex-1 justify-end">
<button
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500`}
title={`Create new snippet`}
onClick={() => setShowCreateDialog(true)}>
<PlusSmIcon className={`mr-2 h-6 w-6`} />
<span className={`text-sm`}>Create new snippet</span>
</button>
</div>
</div>
)}>
{
viewData?.data?.filePath && (
<div className={`text-xl text-center mb-6`}>
<p>Select the snippet to add to your content.</p>
</div>
)
}
{
snippetKeys && snippetKeys.length > 0 ? (
<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`}>
{
snippetKeys.map((snippetKey: any, index: number) => (
<Item
key={index}
title={snippetKey}
snippet={snippets[snippetKey]} />
))
}
</ul>
) : (
<div className='w-full h-full flex items-center justify-center'>
<div className='flex flex-col items-center text-gray-500 dark:text-whisper-900'>
<CodeIcon className='w-32 h-32' />
<p className='text-3xl'>No snippets found</p>
</div>
</div>
)
}
{
showCreateDialog && (
<FormDialog
title={`Create a snippet`}
description={``}
isSaveDisabled={!snippetTitle || !snippetBody}
trigger={onSnippetAdd}
dismiss={reset}
okBtnText='Save'
cancelBtnText='Cancel'>
<NewForm
title={snippetTitle}
description={snippetDescription}
body={snippetBody}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)} />
</FormDialog>
)
}
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
</PageLayout>
);
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { SETTINGS_DASHBOARD_OPENONSTART } from '../../constants';
import { SETTING_DASHBOARD_OPENONSTART } from '../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../DashboardMessage';
import { Settings } from '../models/Settings';
@@ -13,7 +13,7 @@ export const Startup: React.FunctionComponent<IStartupProps> = ({settings}: Reac
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(e.target.checked);
Messenger.send(DashboardMessage.updateSetting, { name: SETTINGS_DASHBOARD_OPENONSTART, value: e.target.checked });
Messenger.send(DashboardMessage.updateSetting, { name: SETTING_DASHBOARD_OPENONSTART, value: e.target.checked });
};
React.useEffect(() => {

View File

@@ -1,4 +1,6 @@
export enum SortOption {
PublishedAsc = "PublishedAsc",
PublishedDesc = "PublishedDesc",
LastModifiedAsc = "LastModifiedAsc",
LastModifiedDesc = "LastModifiedDesc",
FileNameAsc = "FileNameAsc",

View File

@@ -3,13 +3,13 @@ import { useRecoilState } from 'recoil';
import { DashboardCommand } from '../DashboardCommand';
import { DashboardMessage } from '../DashboardMessage';
import { Page } from '../models/Page';
import { DashboardViewAtom, SettingsAtom, ViewDataAtom } from '../state';
import { DashboardViewAtom, LoadingAtom, SettingsAtom, ViewDataAtom } from '../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { NavigationType } from '../models';
export default function useMessages() {
const [loading, setLoading] = useState<boolean>(false);
const [loading, setLoading] = useRecoilState(LoadingAtom);
const [pages, setPages] = useState<Page[]>([]);
const [settings, setSettings] = useRecoilState(SettingsAtom);
const [viewData, setViewData] = useRecoilState(ViewDataAtom);
@@ -28,6 +28,8 @@ export default function useMessages() {
setView(NavigationType.Contents);
} else if (message.data.data?.type === NavigationType.Data) {
setView(NavigationType.Data);
} else if (message.data.data?.type === NavigationType.Snippets) {
setView(NavigationType.Snippets);
}
break;
case DashboardCommand.settings:

View File

@@ -13,7 +13,9 @@ const fuseOptions: Fuse.IFuseOptions<Page> = {
{ name: 'title', weight: 0.8 },
{ name: 'slug', weight: 0.8 },
{ name: 'description', weight: 0.5 }
]
],
includeScore: true,
threshold: 0.1
};
export default function usePages(pages: Page[]) {
@@ -73,8 +75,12 @@ export default function usePages(pages: Page[]) {
pagesSorted = pagesSorted.sort(Sorting.alphabetically("fmFileName"));
} else if (sorting && sorting.id === SortOption.FileNameDesc) {
pagesSorted = pagesSorted.sort(Sorting.alphabetically("fmFileName")).reverse();
} else if (sorting && sorting.id === SortOption.PublishedAsc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmPublished"));
} else if (sorting && sorting.id === SortOption.LastModifiedAsc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmModified"));
} else if (sorting && sorting.id === SortOption.PublishedDesc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmPublished")).reverse();
} else if (sorting && sorting.id === SortOption.LastModifiedDesc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmModified")).reverse();
} else if (sorting && sorting.id && sorting.name) {

View File

@@ -2,4 +2,5 @@ export enum NavigationType {
Contents = "contents",
Media = "media",
Data = "data",
Snippets = "snippets",
}

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