Compare commits

..

64 Commits

Author SHA1 Message Date
Elio Struyf
074de212ce Merge pull request #203 from estruyf/dev 2021-12-07 10:11:07 +01:00
Elio Struyf
98e4490318 updated changelog 2021-12-07 10:06:45 +01:00
Elio Struyf
c79dc3c41f Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2021-12-06 17:32:08 +01:00
Elio Struyf
b45abe88ac Updated changelog 2021-12-06 17:32:04 +01:00
Elio Struyf
2bac596e3d #200 - media file sorting fix 2021-12-05 13:11:22 +01:00
Elio Struyf
15a6ea8d8d #194 - Optimizations for the markup options 2021-12-04 13:55:52 +01:00
Elio Struyf
72d6f8263a Updated changelog 2021-12-04 09:18:21 +01:00
Elio Struyf
7549b3d989 #202 - Fix checkbox label color for light themes 2021-12-04 09:18:04 +01:00
Elio Struyf
ca628b37de #201 - Fix filename overflow 2021-12-03 19:15:20 +01:00
Elio Struyf
db120b5c45 #194 - ordered/unordered/task lists added 2021-12-03 19:06:27 +01:00
Elio Struyf
2973f5d27b #194 - Added heading and blockquote support 2021-12-03 14:11:18 +01:00
Elio Struyf
075c7ad350 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2021-11-30 15:09:05 +01:00
Elio Struyf
23625ed8fa Merge branch 'issue/194' into dev 2021-11-30 15:06:05 +01:00
Elio Struyf
9bd06909c8 #194 - WYSIWYG controls implementation 2021-11-30 15:05:28 +01:00
Elio Struyf
1b45c2af13 Update changelog 2021-11-26 08:42:08 +01:00
Elio Struyf
b4bbf6c6fb #191 - Fix beta settings 2021-11-26 08:41:43 +01:00
Elio Struyf
a5b8d9668d Merge branch 'main' into dev 2021-11-25 15:23:43 +01:00
Elio Struyf
490210da00 Updated changelog 2021-11-25 15:22:56 +01:00
Elio Struyf
6878d440b1 Merge branch 'main' into dev 2021-11-25 15:21:06 +01:00
Elio Struyf
81fa0a7d0f #188 - support for markdown file extension added 2021-11-25 15:20:19 +01:00
Elio Struyf
b7d4e547a1 Total nr of files in workspace 2021-11-25 12:59:49 +01:00
Elio Struyf
2b2e256b8f #190 - More diagnostic values 2021-11-25 12:12:32 +01:00
Elio Struyf
7a7b31c2ae #190 - First implementation of the diagnostic output 2021-11-25 11:00:47 +01:00
Elio Struyf
dea5eb1053 5.7.0 2021-11-25 10:46:35 +01:00
Elio Struyf
df3144ea73 Remove unused reference 2021-11-25 10:46:30 +01:00
Elio Struyf
a170cc3ad9 Updated sponsors 2021-11-24 08:36:56 +01:00
Elio Struyf
35b3813022 Merge pull request #184 from estruyf/dev 2021-11-23 20:02:10 +01:00
Elio Struyf
f0472fe89b Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2021-11-23 20:00:04 +01:00
Elio Struyf
7bdb2fa025 Updated changelog for new 5.6.0 release 2021-11-23 19:59:11 +01:00
Elio Struyf
5ba7ad449a Updated changelog 2021-11-23 19:56:13 +01:00
Elio Struyf
145441d55e Updated package sorting 2021-11-22 17:12:29 +01:00
Elio Struyf
62ff3419c1 #181 - Support for custom taxonomy fields added 2021-11-22 16:43:30 +01:00
Elio Struyf
34aee134d6 #183 - Updated changelog 2021-11-22 08:09:20 +01:00
Elio Struyf
5899120d87 Updated sort setting 2021-11-21 18:58:39 +01:00
Elio Struyf
816a2fefe7 #182 - Support default sort option 2021-11-21 17:56:49 +01:00
Elio Struyf
1f64e59917 Fix sorting object 2021-11-21 17:28:02 +01:00
Elio Struyf
5e54334fb9 #97 - Support for folder scripts added 2021-11-21 12:07:24 +01:00
Elio Struyf
d354af306f Update changelog 2021-11-21 11:35:16 +01:00
Elio Struyf
045cece0ce #97 - Custom scripts to support media management 2021-11-21 11:32:56 +01:00
Elio Struyf
506012200f First steps to allow custom scripts for media files 2021-11-20 16:59:41 +01:00
Elio Struyf
4ad2f0d495 Remove image compress icon 2021-11-19 16:36:32 +01:00
Elio Struyf
b00e3cfd4b Image compression icon test 2021-11-19 16:02:56 +01:00
Elio Struyf
bb05489872 Fix postinstall 2021-11-19 15:49:12 +01:00
Elio Struyf
ede4d417bd Various updates 2021-11-19 15:45:08 +01:00
Elio Struyf
928072fa27 #178 - Type fixes 2021-11-18 19:55:56 +01:00
Elio Struyf
6a313fcc8a #178 - Making it faster 2021-11-18 19:53:37 +01:00
Elio Struyf
725f7f4915 #178 - Revert sorting implementation 2021-11-18 17:23:05 +01:00
Elio Struyf
b9cb0ea16d #180 - Filename placeholder added 2021-11-18 16:42:34 +01:00
Elio
61b80795a4 #178 - Windows support with PowerShell added 2021-11-18 16:24:45 +01:00
Elio Struyf
8daaa23774 #178 - macOS and Linux support for date retrieval 2021-11-18 12:56:23 +01:00
Elio Struyf
8a0d308ceb #178 - Sorting for media files 2021-11-17 14:18:04 +01:00
Elio Struyf
e6a7a9aae7 #179 - New open dashboard icon 2021-11-17 10:03:19 +01:00
Elio Struyf
f3943bd846 5.6.0 2021-11-17 09:07:47 +01:00
Elio Struyf
d2ae94df34 Merge pull request #177 from estruyf/dev 2021-11-15 10:42:15 +01:00
Elio Struyf
f799613b1a Update changelog 2021-11-15 10:38:46 +01:00
Elio Struyf
3f057a01d8 Removed char 2021-11-12 15:36:51 +01:00
Elio Struyf
3ad5136735 #173 - Persistent sorting 2021-11-12 12:11:46 +01:00
Elio Struyf
e201ce6f83 updated changelog 2021-11-12 10:55:26 +01:00
Elio Struyf
383a3a7d4c Updated changelog 2021-11-10 15:19:12 +01:00
Elio Struyf
8261f1de1b #173 - Allow to specify your own sorting 2021-11-10 15:18:35 +01:00
Elio Struyf
5b38e6fa56 Updated changelog 2021-11-10 11:55:33 +01:00
Elio Struyf
717f34bc85 #174 - Enhancement for excluding sub-directories 2021-11-10 11:54:39 +01:00
Elio Struyf
47fb2a90a9 5.5.0 2021-11-10 11:16:24 +01:00
Elio Struyf
85a7221895 Updated flows 2021-11-09 10:02:38 +01:00
94 changed files with 1765 additions and 437 deletions

View File

@@ -25,3 +25,6 @@ jobs:
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}

View File

@@ -26,3 +26,5 @@ jobs:
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }}
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}

View File

@@ -17,4 +17,8 @@ postcss.config.js
.templates
.github
scripts
.all-contributorsrc
.all-contributorsrc
assets/v2.*
assets/v3.*
assets/v4.*
assets/sponsors

View File

@@ -1,5 +1,45 @@
# Change Log
## [5.7.0] - 2021-12-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.7.0)
### 🎨 Enhancements
- [#188](https://github.com/estruyf/vscode-front-matter/issues/188): Support for `.markdown` files added to the dashboard
- [#190](https://github.com/estruyf/vscode-front-matter/issues/190): Diagnostic output for the extension
- [#194](https://github.com/estruyf/vscode-front-matter/issues/194): WYSIWYG controls added for markdown files + configuration to enable/disable the functionality
### 🐞 Fixes
- [#191](https://github.com/estruyf/vscode-front-matter/issues/191): Fix beta settings page
- [#200](https://github.com/estruyf/vscode-front-matter/issues/200): Fix last modified date sorting for media files
- [#201](https://github.com/estruyf/vscode-front-matter/issues/201): Fix overflow issue with the media filename
- [#202](https://github.com/estruyf/vscode-front-matter/issues/202): Fix checkbox label color for light themes
## [5.6.0] - 2021-11-23
### 🎨 Enhancements
- Updated camera icon from VS Code to media icon
- Updated the media card actions to show it within a menu. This will give a better experience with custom scripts.
- [#97](https://github.com/estruyf/vscode-front-matter/issues/97): Custom Script support for media files and folders
- [#178](https://github.com/estruyf/vscode-front-matter/issues/178): Sorting added to the media dashboard
- [#179](https://github.com/estruyf/vscode-front-matter/issues/179): Updated the `open dashboard` icon to make it easier to spot it
- [#180](https://github.com/estruyf/vscode-front-matter/issues/180): Added `{filename}` as placeholder for media snippets
- [#181](https://github.com/estruyf/vscode-front-matter/issues/181): Support for custom taxonomy fields added
### 🐞 Fixes
- [#183](https://github.com/estruyf/vscode-front-matter/issues/183): Fix type error on the `frontMatter.content.sorting` setting
## [5.5.0] - 2021-11-15
As from this version onwards, the extension will be published to [open-vsx.org](https://open-vsx.org/).
### 🎨 Enhancements
- [#173](https://github.com/estruyf/vscode-front-matter/issues/173): Allow to specify your own sorting for the content dashboard
- [#174](https://github.com/estruyf/vscode-front-matter/issues/174): Added option to exclude sub-directories from page/markdown content retrieval
## [5.4.0] - 2021-11-05
### 🎨 Enhancements

View File

@@ -111,7 +111,10 @@ If you have the courage to test out the beta features, we made available a beta
<p align="center">
<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>
<a href="https://github.com/zivbk1" title="Bryan Klein">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
</a>
<a href="https://github.com/flikteoh" title="FlikTeoh">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
</a>

View File

@@ -109,7 +109,10 @@ If you have the courage to test out the beta features, we made available a beta
<p align="center">
<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>
<a href="https://github.com/zivbk1" title="Bryan Klein">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/6154767" />
</a>
<a href="https://github.com/flikteoh" title="FlikTeoh">
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
</a>

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M6 15h15" />
<path d="M21 19h-15" />
<path d="M15 11h6" />
<path d="M21 7h-6" />
<path d="M9 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
<path d="M3 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M6 15h15" />
<path d="M21 19h-15" />
<path d="M15 11h6" />
<path d="M21 7h-6" />
<path d="M9 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
<path d="M3 9h1a1 1 0 1 1 -1 1v-2.5a2 2 0 0 1 2 -2" />
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6z" />
<path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" />
</svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6z" />
<path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" />
</svg>

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<polyline points="7 8 3 12 7 16" />
<polyline points="17 8 21 12 17 16" />
<line x1="14" y1="4" x2="10" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<polyline points="7 8 3 12 7 16" />
<polyline points="17 8 21 12 17 16" />
<line x1="14" y1="4" x2="10" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 12h6" />
<path d="M12 9v6" />
<path d="M6 19a2 2 0 0 1 -2 -2v-4l-1 -1l1 -1v-4a2 2 0 0 1 2 -2" />
<path d="M18 19a2 2 0 0 0 2 -2v-4l1 -1l-1 -1v-4a2 2 0 0 0 -2 -2" />
</svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 12h6" />
<path d="M12 9v6" />
<path d="M6 19a2 2 0 0 1 -2 -2v-4l-1 -1l1 -1v-4a2 2 0 0 1 2 -2" />
<path d="M18 19a2 2 0 0 0 2 -2v-4l1 -1l-1 -1v-4a2 2 0 0 0 -2 -2" />
</svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
<path fill="#C5C5C5" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
<path fill="#C5C5C5" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
<path fill="#424242" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
<path fill="#424242" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve" width="16" height="16">
<path fill="#02aeb7" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
<path fill="#02aeb7" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6c7.9,47.6,15,97.6,21.4,149.8c0.7-6.8,1.3-12.1,1.7-16
c0.2-2.7,0.6-5.5,1.1-8.2l16.6-106.7l14.9-101.3l13.2-66.9l69.2-373.3h102.9l81.2,931.1h-113.6l-19.9-316c-0.8-16.1-1.4-29.9-2-41.6
c-0.6-11.7-0.9-21.3-0.9-29L988.3,571l-2.8-114.6c0-0.8,0-2.5-0.3-5.1s-0.5-6.1-0.9-10.6l-2.8,18.7c-3,22.1-5.8,41.4-8.3,57.9
c-2.5,16.5-4.7,30.3-6.6,41.6l-15.1,84.9l-5.7,32l-74.3,406.4h-80.1l-69.7-351c-9.5-46.2-17.9-93.1-25.4-140.8
c-7.5-47.7-14.2-97.6-20.3-149.9l-34.3,641.6H529.6L602.2,151.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 12h10" />
<path d="M7 4v16" />
<path d="M17 4v16" />
<path d="M15 20h4" />
<path d="M15 4h4" />
<path d="M5 20h4" />
<path d="M5 4h4" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 12h10" />
<path d="M7 4v16" />
<path d="M17 4v16" />
<path d="M15 20h4" />
<path d="M15 4h4" />
<path d="M5 20h4" />
<path d="M5 4h4" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="11" y1="5" x2="17" y2="5" />
<line x1="7" y1="19" x2="13" y2="19" />
<line x1="14" y1="5" x2="10" y2="19" />
</svg>

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="11" y1="5" x2="17" y2="5" />
<line x1="7" y1="19" x2="13" y2="19" />
<line x1="14" y1="5" x2="10" y2="19" />
</svg>

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#C5C5C5" width="24" height="24">
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#424242" width="24" height="24">
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="8" y2="12.01" />
<line x1="12" y1="12" x2="12" y2="12.01" />
<line x1="16" y1="12" x2="16" y2="12.01" />
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="8" y2="12.01" />
<line x1="12" y1="12" x2="12" y2="12.01" />
<line x1="16" y1="12" x2="16" y2="12.01" />
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list-numbers" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M11 6h9" />
<path d="M11 12h9" />
<path d="M12 18h8" />
<path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" />
<path d="M6 10v-6l-2 2" />
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list-numbers" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M11 6h9" />
<path d="M11 12h9" />
<path d="M12 18h8" />
<path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" />
<path d="M6 10v-6l-2 2" />
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 5v9a5 5 0 0 0 10 0v-9" />
<path d="M4 12h16" />
</svg>

After

Width:  |  Height:  |  Size: 301 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M7 5v9a5 5 0 0 0 10 0v-9" />
<path d="M4 12h16" />
</svg>

After

Width:  |  Height:  |  Size: 301 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="9" y1="6" x2="20" y2="6" />
<line x1="9" y1="12" x2="20" y2="12" />
<line x1="9" y1="18" x2="20" y2="18" />
<line x1="5" y1="6" x2="5" y2="6.01" />
<line x1="5" y1="12" x2="5" y2="12.01" />
<line x1="5" y1="18" x2="5" y2="18.01" />
</svg>

After

Width:  |  Height:  |  Size: 533 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#424242" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="9" y1="6" x2="20" y2="6" />
<line x1="9" y1="12" x2="20" y2="12" />
<line x1="9" y1="18" x2="20" y2="18" />
<line x1="5" y1="6" x2="5" y2="6.01" />
<line x1="5" y1="12" x2="5" y2="12.01" />
<line x1="5" y1="18" x2="5" y2="18.01" />
</svg>

After

Width:  |  Height:  |  Size: 533 B

189
package-lock.json generated
View File

@@ -1,145 +1,9 @@
{
"name": "vscode-front-matter-beta",
"version": "5.4.0",
"version": "5.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@algolia/autocomplete-core": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.2.2.tgz",
"integrity": "sha512-JOQaURze45qVa8OOFDh+ozj2a/ObSRsVyz6Zd0aiBeej+RSTqrr1hDVpGNbbXYLW26G5ujuc9QIdH+rBHn95nw==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-preset-algolia": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.2.2.tgz",
"integrity": "sha512-AZkh+bAMaJDzMZTelFOXJTJqkp5VPGH8W3n0B+Ggce7DdozlMRsDLguKTCQAkZ0dJ1EbBPyFL5ztL/JImB137Q==",
"requires": {
"@algolia/autocomplete-shared": "1.2.2"
}
},
"@algolia/autocomplete-shared": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.2.2.tgz",
"integrity": "sha512-mLTl7d2C1xVVazHt/bqh9EE/u2lbp5YOxLDdcjILXmUqOs5HH1D4SuySblXaQG1uf28FhTqMGp35qE5wJQnqAw=="
},
"@algolia/cache-browser-local-storage": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.5.tgz",
"integrity": "sha512-cfX2rEKOtuuljcGI5DMDHClwZHdDqd2nT2Ohsc8aHtBiz6bUxKVyIqxr2gaC6tU8AgPtrTVBzcxCA+UavXpKww==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/cache-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.10.5.tgz",
"integrity": "sha512-1mClwdmTHll+OnHkG+yeRoFM17kSxDs4qXkjf6rNZhoZGXDvfYLy3YcZ1FX4Kyz0DJv8aroq5RYGBDsWkHj6Tw=="
},
"@algolia/cache-in-memory": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.10.5.tgz",
"integrity": "sha512-+ciQnfIGi5wjMk02XhEY8fmy2pzy+oY1nIIfu8LBOglaSipCRAtjk6WhHc7/KIbXPiYzIwuDbM2K1+YOwSGjwA==",
"requires": {
"@algolia/cache-common": "4.10.5"
}
},
"@algolia/client-account": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.10.5.tgz",
"integrity": "sha512-I9UkSS2glXm7RBZYZIALjBMmXSQbw/fI/djPcBHxiwXIheNIlqIFl2SNPkvihpPF979BSkzjqdJNRPhE1vku3Q==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-analytics": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.10.5.tgz",
"integrity": "sha512-h2owwJSkovPxzc+xIsjY1pMl0gj+jdVwP9rcnGjlaTY2fqHbSLrR9yvGyyr6305LvTppxsQnfAbRdE/5Z3eFxw==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.10.5.tgz",
"integrity": "sha512-21FAvIai5qm8DVmZHm2Gp4LssQ/a0nWwMchAx+1hIRj1TX7OcdW6oZDPyZ8asQdvTtK7rStQrRnD8a95SCUnzA==",
"requires": {
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-personalization": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.10.5.tgz",
"integrity": "sha512-nH+IyFKBi8tCyzGOanJTbXC5t4dspSovX3+ABfmwKWUYllYzmiQNFUadpb3qo+MLA3jFx5IwBesjneN6dD5o3w==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/client-search": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.10.5.tgz",
"integrity": "sha512-1eQFMz9uodrc5OM+9HeT+hHcfR1E1AsgFWXwyJ9Q3xejA2c1c4eObGgOgC9ZoshuHHdptaTN1m3rexqAxXRDBg==",
"requires": {
"@algolia/client-common": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"@algolia/logger-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.10.5.tgz",
"integrity": "sha512-gRJo9zt1UYP4k3woEmZm4iuEBIQd/FrArIsjzsL/b+ihNoOqIxZKTSuGFU4UUZOEhvmxDReiA4gzvQXG+TMTmA=="
},
"@algolia/logger-console": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.10.5.tgz",
"integrity": "sha512-4WfIbn4253EDU12u9UiYvz+QTvAXDv39mKNg9xSoMCjKE5szcQxfcSczw2byc6pYhahOJ9PmxPBfs1doqsdTKQ==",
"requires": {
"@algolia/logger-common": "4.10.5"
}
},
"@algolia/requester-browser-xhr": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.5.tgz",
"integrity": "sha512-53/MURQEqtK+bGdfq4ITSPwTh5hnADU99qzvpAINGQveUFNSFGERipJxHjTJjIrjFz3vxj5kKwjtxDnU6ygO9g==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/requester-common": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.10.5.tgz",
"integrity": "sha512-UkVa1Oyuj6NPiAEt5ZvrbVopEv1m/mKqjs40KLB+dvfZnNcj+9Fry4Oxnt15HMy/HLORXsx4UwcthAvBuOXE9Q=="
},
"@algolia/requester-node-http": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.10.5.tgz",
"integrity": "sha512-aNEKVKXL4fiiC+bS7yJwAHdxln81ieBwY3tsMCtM4zF9f5KwCzY2OtN4WKEZa5AAADVcghSAUdyjs4AcGUlO5w==",
"requires": {
"@algolia/requester-common": "4.10.5"
}
},
"@algolia/transporter": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.10.5.tgz",
"integrity": "sha512-F8DLkmIlvCoMwSCZA3FKHtmdjH3o5clbt0pi2ktFStVNpC6ZDmY307HcK619bKP5xW6h8sVJhcvrLB775D2cyA==",
"requires": {
"@algolia/cache-common": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/requester-common": "4.10.5"
}
},
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -184,31 +48,6 @@
"lit-element": "^2.5.1"
}
},
"@docsearch/css": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.40.tgz",
"integrity": "sha512-PrOTPgJMl+Iji1zOH0+J0PEDMriJ1teGxbgll7o4h8JrvJW6sJGqQw7/bLW7enWiFaxbJMK76w1yyPNLFHV7Qg=="
},
"@docsearch/js": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0-alpha.40.tgz",
"integrity": "sha512-0ysRM0jk1KAbw/QsHPHIKoa7OeCm2Mwz0JgcPnhWRvvA28dzP+f6OIsL6eGu3VJR043tH9OrvVf/FnvLtTtZtw==",
"requires": {
"@docsearch/react": "3.0.0-alpha.40",
"preact": "^10.0.0"
}
},
"@docsearch/react": {
"version": "3.0.0-alpha.40",
"resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.40.tgz",
"integrity": "sha512-aKxnu7sgpP1R7jtgOV/pZdJEHXx6Ts+jnS9U/ejSUS2BMUpwQI5SA3oLs1BA5TA9kIViJ5E+rrjh0VsbcsJ6sQ==",
"requires": {
"@algolia/autocomplete-core": "1.2.2",
"@algolia/autocomplete-preset-algolia": "1.2.2",
"@docsearch/css": "3.0.0-alpha.40",
"algoliasearch": "^4.0.0"
}
},
"@estruyf/vscode": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@estruyf/vscode/-/vscode-0.0.2.tgz",
@@ -892,27 +731,6 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"algoliasearch": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.10.5.tgz",
"integrity": "sha512-KmH2XkiN+8FxhND4nWFbQDkIoU6g2OjfeU9kIv4Lb+EiOOs3Gpp7jvd+JnatsCisAZsnWQdjd7zVlW7I/85QvQ==",
"requires": {
"@algolia/cache-browser-local-storage": "4.10.5",
"@algolia/cache-common": "4.10.5",
"@algolia/cache-in-memory": "4.10.5",
"@algolia/client-account": "4.10.5",
"@algolia/client-analytics": "4.10.5",
"@algolia/client-common": "4.10.5",
"@algolia/client-personalization": "4.10.5",
"@algolia/client-search": "4.10.5",
"@algolia/logger-common": "4.10.5",
"@algolia/logger-console": "4.10.5",
"@algolia/requester-browser-xhr": "4.10.5",
"@algolia/requester-common": "4.10.5",
"@algolia/requester-node-http": "4.10.5",
"@algolia/transporter": "4.10.5"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -4849,11 +4667,6 @@
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"preact": {
"version": "10.5.14",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz",
"integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ=="
},
"pretty-error": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz",

View File

@@ -3,7 +3,7 @@
"displayName": "Front Matter",
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "5.4.0",
"version": "5.7.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -97,6 +97,25 @@
"markdownDescription": "Specify if you want to automatically update the modified date of your article/page. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.autoupdatedate)",
"scope": "Content"
},
"frontMatter.content.defaultSorting": {
"type": "string",
"default": "",
"oneOf": [
{
"enum": [
"LastModifiedAsc",
"LastModifiedDesc",
"FileNameAsc",
"FileNameDesc"
]
},
{
"type": "string"
}
],
"markdownDescription": "Specify the default sorting option for the content dashboard. You can use one of the values from the enum or define your own ID. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting.default)",
"scope": "Content"
},
"frontMatter.content.draftField": {
"type": "object",
"markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.draftField)",
@@ -154,6 +173,11 @@
"path": {
"type": "string",
"description": "Path of the folder"
},
"excludeSubdir": {
"type": "boolean",
"default": false,
"description": "Exclude sub-directories"
}
},
"additionalProperties": false,
@@ -170,6 +194,58 @@
"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.sorting": {
"type": "array",
"default": [],
"markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting)",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The ID of the sorting option. This will be used for the storing the last used sorting option or the default option."
},
"title": {
"type": "string",
"description": "Name of the sorting label"
},
"name": {
"type": "string",
"description": "Name of the metadata field to sort by"
},
"order": {
"type": "string",
"enum": [
"asc",
"desc"
],
"description": "Order of the sorting"
},
"type": {
"type": "string",
"default": "string",
"enum": [
"string",
"date"
],
"description": "Type of the field value"
}
},
"additionalProperties": false,
"required": [
"title",
"name",
"order"
]
},
"scope": "Content"
},
"frontMatter.content.wysiwyg": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.wysiwyg)",
"scope": "Content"
},
"frontMatter.custom.scripts": {
"type": "array",
"default": [],
@@ -204,6 +280,16 @@
"outputType": {
"type": "string",
"description": "The type of output for the editor panel. Can be used to change it to 'markdown' for example"
},
"type": {
"type": "string",
"default": "content",
"enum": [
"content",
"mediaFolder",
"mediaFile"
],
"description": "The type for which the script will be used."
}
},
"additionalProperties": false,
@@ -238,6 +324,18 @@
"default": "",
"markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.framework.id)"
},
"frontMatter.media.defaultSorting": {
"type": "string",
"default": "",
"enum": [
"LastModifiedAsc",
"LastModifiedDesc",
"FileNameAsc",
"FileNameDesc"
],
"markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.media.sorting.default)",
"scope": "Content"
},
"frontMatter.panel.freeform": {
"type": "boolean",
"default": true,
@@ -315,6 +413,7 @@
"boolean",
"image",
"choice",
"taxonomy",
"tags",
"categories",
"draft"
@@ -371,12 +470,47 @@
"type": "boolean",
"default": false,
"description": "Do you want to hide the field from the metadata section?"
},
"taxonomyId": {
"type": "string",
"default": "",
"description": "The ID of your taxonomy field"
}
},
"additionalProperties": false,
"required": [
"type",
"name"
],
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "taxonomy"
}
}
},
"then": {
"required": [
"taxonomyId"
]
}
},
{
"if": {
"properties": {
"type": {
"const": "choice"
}
}
},
"then": {
"required": [
"choices"
]
}
}
]
}
},
@@ -445,6 +579,32 @@
],
"scope": "Taxonomy"
},
"frontMatter.taxonomy.customTaxonomy": {
"type": "array",
"markdownDescription": "Specify the custom taxonomy field data. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.tags)",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "ID for your taxonomy field"
},
"options": {
"type": "array",
"description": "Options from which you can pick",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": [
"id",
"options"
]
},
"scope": "Taxonomy"
},
"frontMatter.taxonomy.dateField": {
"type": "string",
"default": "date",
@@ -613,7 +773,10 @@
"command": "frontMatter.insertImage",
"title": "Insert image into your content",
"category": "Front matter",
"icon": "$(device-camera)"
"icon": {
"dark": "/assets/icons/media-dark.svg",
"light": "/assets/icons/media-light.svg"
}
},
{
"command": "frontMatter.insertTags",
@@ -629,7 +792,19 @@
"command": "frontMatter.dashboard",
"title": "Open dashboard",
"category": "Front matter",
"icon": "$(preview)"
"icon": {
"dark": "/assets/icons/frontmatter-small-dark.svg",
"light": "/assets/icons/frontmatter-small-light.svg"
}
},
{
"command": "frontMatter.dashboard.close",
"title": "Close dashboard",
"category": "Front matter",
"icon": {
"dark": "/assets/icons/frontmatter-small-teal.svg",
"light": "/assets/icons/frontmatter-small-teal.svg"
}
},
{
"command": "frontMatter.preview",
@@ -650,19 +825,179 @@
"command": "frontMatter.setLastModifiedDate",
"title": "Set lastmod date",
"category": "Front matter"
},
{
"command": "frontMatter.markup.bold",
"title": "Bold",
"category": "Front matter",
"icon": {
"light": "assets/icons/bold-light.svg",
"dark": "assets/icons/bold-dark.svg"
}
},
{
"command": "frontMatter.markup.italic",
"title": "Italic",
"category": "Front matter",
"icon": {
"light": "assets/icons/italic-light.svg",
"dark": "assets/icons/italic-dark.svg"
}
},
{
"command": "frontMatter.markup.strikethrough",
"title": "Strikethrough",
"category": "Front matter",
"icon": {
"light": "assets/icons/strikethrough-light.svg",
"dark": "assets/icons/strikethrough-dark.svg"
}
},
{
"command": "frontMatter.markup.code",
"title": "Code",
"category": "Front matter",
"icon": {
"light": "assets/icons/code-light.svg",
"dark": "assets/icons/code-dark.svg"
}
},
{
"command": "frontMatter.markup.codeblock",
"title": "Codeblock",
"category": "Front matter",
"icon": {
"light": "assets/icons/codeblock-light.svg",
"dark": "assets/icons/codeblock-dark.svg"
}
},
{
"command": "frontMatter.markup.blockquote",
"title": "Codeblock",
"category": "Front matter",
"icon": {
"light": "assets/icons/blockquote-light.svg",
"dark": "assets/icons/blockquote-dark.svg"
}
},
{
"command": "frontMatter.markup.heading",
"title": "Heading",
"category": "Front matter",
"icon": {
"light": "assets/icons/heading-light.svg",
"dark": "assets/icons/heading-dark.svg"
}
},
{
"command": "frontMatter.markup.unorderedlist",
"title": "Unordered list",
"category": "Front matter",
"icon": {
"light": "assets/icons/unordered-list-light.svg",
"dark": "assets/icons/unordered-list-dark.svg"
}
},
{
"command": "frontMatter.markup.orderedlist",
"title": "Ordered list",
"category": "Front matter",
"icon": {
"light": "assets/icons/ordered-list-light.svg",
"dark": "assets/icons/ordered-list-dark.svg"
}
},
{
"command": "frontMatter.markup.tasklist",
"title": "Task list",
"category": "Front matter"
},
{
"command": "frontMatter.markup.options",
"title": "Other markup options",
"category": "Front matter",
"icon": {
"light": "assets/icons/options-light.svg",
"dark": "assets/icons/options-dark.svg"
}
},
{
"command": "frontMatter.diagnostics",
"title": "Diagnostic logging",
"category": "Front matter"
}
],
"menus": {
"editor/title": [
{
"command": "frontMatter.markup.heading",
"group": "navigation@-132",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.bold",
"group": "navigation@-131",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.italic",
"group": "navigation@-130",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.strikethrough",
"group": "navigation@-129",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.blockquote",
"group": "navigation@-128",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.insertImage",
"group": "navigation@-99",
"group": "navigation@-127",
"when": "resourceLangId == markdown"
},
{
"command": "frontMatter.markup.options",
"group": "navigation@-126",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.orderedlist",
"group": "1_markup@1",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.unorderedlist",
"group": "1_markup@2",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.tasklist",
"group": "1_markup@3",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.code",
"group": "1_markup@4",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.markup.codeblock",
"group": "1_markup@5",
"when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg"
},
{
"command": "frontMatter.dashboard",
"group": "navigation@-98",
"when": "frontMatter:enabled == true"
"when": "frontMatter:enabled == true && frontMatter:dashboard:open == false"
},
{
"command": "frontMatter.dashboard.close",
"group": "navigation@-98",
"when": "frontMatter:enabled == true && frontMatter:dashboard:open == true"
}
],
"explorer/context": [
@@ -710,6 +1045,54 @@
{
"command": "frontMatter.createFromTemplate",
"when": "false"
},
{
"command": "frontMatter.dashboard.close",
"when": "false"
},
{
"command": "frontMatter.markup.bold",
"when": "false"
},
{
"command": "frontMatter.markup.italic",
"when": "false"
},
{
"command": "frontMatter.markup.strikethrough",
"when": "false"
},
{
"command": "frontMatter.markup.code",
"when": "false"
},
{
"command": "frontMatter.markup.codeblock",
"when": "false"
},
{
"command": "frontMatter.markup.blockquote",
"when": "false"
},
{
"command": "frontMatter.markup.heading",
"when": "false"
},
{
"command": "frontMatter.markup.unorderedlist",
"when": "false"
},
{
"command": "frontMatter.markup.orderedlist",
"when": "false"
},
{
"command": "frontMatter.markup.tasklist",
"when": "false"
},
{
"command": "frontMatter.markup.options",
"when": "false"
}
],
"view/title": [
@@ -789,7 +1172,5 @@
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
},
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.40"
}
"dependencies": {}
}

View File

@@ -203,7 +203,7 @@ export class Article {
const file = parseWinPath(editor.document.fileName);
if (!file.endsWith(`.md`) && !file.endsWith(`.mdx`)) {
if (!file.endsWith(`.md`) && !file.endsWith(`.markdown`) && !file.endsWith(`.mdx`)) {
return;
}

View File

@@ -1,10 +1,10 @@
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD } from '../constants';
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, CONTEXT, SETTING_CUSTOM_SCRIPTS, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_MEDIA_SORTING_DEFAULT } from '../constants';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { basename, dirname, extname, join, parse } from "path";
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
import { Settings as SettingsHelper } from '../helpers';
import { DraftField, Framework, TaxonomyType } from '../models';
import { CustomScript as ICustomScript, DraftField, Framework, ScriptType, SortingSetting, SortOrder, SortType, TaxonomyType } from '../models';
import { Folders } from './Folders';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
@@ -21,11 +21,14 @@ import { decodeBase64Image } from '../helpers/decodeBase64Image';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { MediaLibrary } from '../helpers/MediaLibrary';
import imageSize from 'image-size';
import { parseWinPath } from '../helpers/parseWinPath';
import { DateHelper } from '../helpers/DateHelper';
import { FrameworkDetector } from '../helpers/FrameworkDetector';
import { ContentType } from '../helpers/ContentType';
import { SortingOption } from '../dashboardWebView/models';
import { Sorting } from '../helpers/Sorting';
import imageSize from 'image-size';
import { CustomScript } from '../helpers/CustomScript';
export class Dashboard {
private static webview: WebviewPanel | null = null;
@@ -39,7 +42,7 @@ export class Dashboard {
return Dashboard._viewData;
}
/** 
/**
* Init the dashboard
*/
public static async init() {
@@ -62,6 +65,8 @@ export class Dashboard {
} else {
Dashboard.create();
}
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, true);
}
/**
@@ -79,6 +84,10 @@ export class Dashboard {
Dashboard.webview.reveal();
}
}
public static close() {
Dashboard.webview?.dispose();
}
/**
* Create the dashboard webview
@@ -105,19 +114,22 @@ export class Dashboard {
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
Dashboard.webview.onDidChangeViewState(() => {
Dashboard.webview.onDidChangeViewState(async () => {
if (!this.webview?.visible) {
Dashboard._viewData = undefined;
const panel = ExplorerView.getInstance(extensionUri);
panel.getMediaSelection();
}
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, this.webview?.visible);
});
Dashboard.webview.onDidDispose(() => {
Dashboard.webview.onDidDispose(async () => {
Dashboard.isDisposed = true;
Dashboard._viewData = undefined;
const panel = ExplorerView.getInstance(extensionUri);
panel.getMediaSelection();
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false);
});
SettingsHelper.onConfigChange((global?: any) => {
@@ -165,7 +177,7 @@ export class Dashboard {
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
break;
case DashboardMessage.getMedia:
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder);
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder, msg?.data?.sorting);
break;
case DashboardMessage.copyToClipboard:
env.clipboard.writeText(msg.data);
@@ -192,6 +204,14 @@ export class Dashboard {
case DashboardMessage.setFramework:
Dashboard.setFramework(msg?.data);
break;
case DashboardMessage.runCustomScript:
CustomScript.run(msg?.data?.script, msg?.data?.path);
break;
case DashboardMessage.setState:
if (msg?.data?.key && msg?.data?.value) {
Extension.getInstance().setState(msg?.data?.key, msg?.data?.value, "workspace");
}
break;
}
});
}
@@ -207,6 +227,20 @@ export class Dashboard {
Dashboard.resetMedia();
Dashboard.getMedia(0, folderPath);
}
/**
* Post data to the dashboard
* @param msg
*/
public static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
if (Dashboard.isDisposed) {
return;
}
if (Dashboard.webview) {
Dashboard.webview?.webview.postMessage(msg);
}
}
/**
* Insert an image into the front matter or contents
@@ -280,9 +314,21 @@ export class Dashboard {
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
draftField: SettingsHelper.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
contentFolders: Folders.get().map(f => f.path),
customSorting: SettingsHelper.get<SortingSetting[]>(SETTINGS_CONTENT_SORTING),
contentFolders: Folders.get(),
crntFramework: SettingsHelper.get<string>(SETTINGS_FRAMEWORK_ID),
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: (SettingsHelper.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type && s.type !== ScriptType.Content),
dashboardState: {
contents: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
defaultSorting: SettingsHelper.get<string>(SETTINGS_CONTENT_SORTING_DEFAULT)
},
media: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
defaultSorting: SettingsHelper.get<string>(SETTINGS_MEDIA_SORTING_DEFAULT)
}
}
} as Settings
});
}
@@ -316,16 +362,19 @@ export class Dashboard {
/**
* Retrieve all media files
*/
private static async getMedia(page: number = 0, requestedFolder: string = '') {
private static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const contentFolders = Folders.get();
const viewData = Dashboard.viewData;
let selectedFolder = requestedFolder;
const ext = Extension.getInstance();
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
// If the static folder is not set, retreive the last opened location
if (!selectedFolder) {
const stateValue = await Extension.getInstance().getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
@@ -357,13 +406,14 @@ export class Dashboard {
if (relSelectedFolderPath) {
const files = await workspace.findFiles(join(relSelectedFolderPath, '/*'));
const media = Dashboard.filterMedia(files);
const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files));
allMedia = [...media];
} else {
if (staticFolder) {
const folderSearch = join(staticFolder || "", '/*');
const files = await workspace.findFiles(folderSearch);
const media = Dashboard.filterMedia(files);
const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files));
allMedia = [...media];
}
@@ -374,22 +424,24 @@ export class Dashboard {
const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1);
const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*');
const files = await workspace.findFiles(folderSearch);
const media = Dashboard.filterMedia(files);
const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files));
allMedia = [...allMedia, ...media];
}
}
}
allMedia = allMedia.sort((a, b) => {
if (b.fsPath < a.fsPath) {
return -1;
}
if (b.fsPath > a.fsPath) {
return 1;
}
return 0;
});
if (crntSort?.type === SortType.string) {
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
} else if (crntSort?.type === SortType.date) {
allMedia = allMedia.sort(Sorting.dateWithFallback("mtime", "fsPath"));
} else {
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
}
if (crntSort?.order === SortOrder.desc) {
allMedia = allMedia.reverse();
}
Dashboard.media = Object.assign([], allMedia);
@@ -406,15 +458,14 @@ export class Dashboard {
return {
...file,
stats: statSync(file.fsPath),
dimensions: imageSize(file.fsPath),
...metadata
};
} catch (e) {
return {...file, stats: undefined};
return {...file};
}
});
files = files.filter(f => f.stats !== undefined);
files = files.filter(f => f.mtime !== undefined);
// Retrieve all the folders
let allContentFolders: string[] = [];
@@ -441,18 +492,46 @@ export class Dashboard {
// Store the last opened folder
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
let sortedFolders = [...allContentFolders, ...allFolders];
sortedFolders = sortedFolders.sort((a, b) => {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return 0;
});
if (crntSort?.order === SortOrder.desc) {
sortedFolders = sortedFolders.reverse();
}
Dashboard.postWebviewMessage({
command: DashboardCommand.media,
data: {
media: files,
total: total,
folders: [...allContentFolders, ...allFolders],
folders: sortedFolders,
selectedFolder
} as MediaPaths
});
}
/**
* Update the metadata of the retrieved files
* @param files
*/
private static async updateMediaData(files: MediaInfo[]) {
files = files.map((m: MediaInfo) => {
const stats = statSync(m.fsPath);
return Object.assign({}, m, stats);
});
return Object.assign([], files);
}
/**
* Retrieve all the markdown pages
*/
@@ -469,7 +548,7 @@ export class Dashboard {
if (folderInfo) {
for (const folder of folderInfo) {
for (const file of folder.lastModified) {
if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.mdx`)) {
if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.markdown`) || file.fileName.endsWith(`.mdx`)) {
try {
const article = ArticleHelper.getFrontMatterByPath(file.filePath);
@@ -633,20 +712,6 @@ export class Dashboard {
Dashboard.getMedia(page || 0, folder || "");
}
/**
* Post data to the dashboard
* @param msg
*/
private static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
if (Dashboard.isDisposed) {
return;
}
if (Dashboard.webview) {
Dashboard.webview?.webview.postMessage(msg);
}
}
/**
* Retrieve the webview HTML contents

View File

@@ -0,0 +1,63 @@
import { Folders } from "./Folders";
import { ViewColumn, workspace } from "vscode";
import ContentProvider from "../providers/ContentProvider";
import { join } from "path";
import { ContentFolder } from "../models";
export class Diagnostics {
public static async show() {
const folders = Folders.get();
const projectName = Folders.getProjectFolderName();
const wsFolder = Folders.getWorkspaceFolder();
const folderData = [];
for (const folder of folders) {
folderData.push(await Diagnostics.processFolder(folder, projectName));
}
const all = await Diagnostics.allProjectFiles();
const logging = `# Project name
${projectName}
# Folders
${folders.map(f => `- ${f.title}: "${f.path}"`).join("\n")}
# Workspace folder
${wsFolder ? wsFolder.fsPath : "No workspace folder"}
# Total files
${all}
# Folders to search files
${folderData.join("\n")}
`;
ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One);
}
private static async allProjectFiles() {
const allFiles = await workspace.findFiles(`**/*.*`);
return `Total files found: ${allFiles.length}`;
}
private static async processFolder(folder: ContentFolder, projectName: string) {
let projectStart = folder.path.split(projectName).pop();
projectStart = projectStart || "";
projectStart = projectStart?.replace(/\\/g, '/');
projectStart = projectStart?.startsWith('/') ? projectStart.substr(1) : projectStart;
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown'));
return `- Project start length: ${projectStart.length} | Search in: "${join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.*')}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${markdownFiles.length}`;
}
}

View File

@@ -207,19 +207,23 @@ export class Folders {
try {
const projectName = Folders.getProjectFolderName();
let projectStart = folder.path.split(projectName).pop();
if (projectStart) {
projectStart = projectStart.replace(/\\/g, '/');
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
const mdFiles = await workspace.findFiles(join(projectStart, '**/*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, '**/*.mdx'));
let files = [...mdFiles, ...mdxFiles];
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown'));
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
let files = [...mdFiles, ...markdownFiles, ...mdxFiles];
if (files) {
let fileStats: FileInfo[] = [];
for (const file of files) {
try {
const fileName = basename(file.fsPath);
const stats = await workspace.fs.stat(file);
fileStats.push({
filePath: file.fsPath,
fileName,
@@ -263,7 +267,7 @@ export class Folders {
const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
return folders.map(folder => ({
title: folder.title,
...folder,
path: Folders.absWsFolder(folder, wsFolder)
}));
}
@@ -274,10 +278,12 @@ export class Folders {
*/
private static async update(folders: ContentFolder[]) {
const wsFolder = Folders.getWorkspaceFolder();
let folderDetails = folders.map(folder => ({
title: folder.title,
...folder,
path: Folders.relWsFolder(folder, wsFolder)
}));
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
}

230
src/commands/Wysiwyg.ts Normal file
View File

@@ -0,0 +1,230 @@
import { commands, window, Selection, QuickPickItem } from "vscode";
import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants";
import { Settings } from "../helpers";
enum MarkupType {
bold = 1,
italic,
strikethrough,
code,
codeblock,
blockquote,
heading,
unorderedList,
orderedList,
taskList
}
export class Wysiwyg {
/**
* Registers the markup commands for the WYSIWYG controls
* @param subscriptions
* @returns
*/
public static async registerCommands(subscriptions: any) {
const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG);
if (!wysiwygEnabled) {
return;
}
await commands.executeCommand('setContext', CONTEXT.wysiwyg, true);
// Surrounding markup
subscriptions.push(commands.registerCommand(COMMAND_NAME.bold, () => this.addMarkup(MarkupType.bold)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.italic, () => this.addMarkup(MarkupType.italic)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.strikethrough, () => this.addMarkup(MarkupType.strikethrough)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.code, () => this.addMarkup(MarkupType.code)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.codeblock, () => this.addMarkup(MarkupType.codeblock)));
// Prefix markup
subscriptions.push(commands.registerCommand(COMMAND_NAME.heading, () => this.addMarkup(MarkupType.heading)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.blockquote, () => this.addMarkup(MarkupType.blockquote)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.unorderedlist, () => this.addMarkup(MarkupType.unorderedList)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.orderedlist, () => this.addMarkup(MarkupType.orderedList)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.taskList, () => this.addMarkup(MarkupType.taskList)));
// Options
subscriptions.push(commands.registerCommand(COMMAND_NAME.options, async () => {
const qpItems: QuickPickItem[] = [
{ label: "$(list-unordered) Unordered list", detail: "Add an unordered list", alwaysShow: true, },
{ label: "$(list-ordered) Ordered list", detail: "Add an ordered list", alwaysShow: true },
{ 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 },
]
const option = await window.showQuickPick([ ...qpItems ], {
placeHolder: "Which type of markup would you like to insert?",
canPickMany: false,
ignoreFocusOut: false,
});
if (option) {
if (option.label === qpItems[0].label) {
await this.addMarkup(MarkupType.unorderedList);
} else if (option.label === qpItems[1].label) {
await this.addMarkup(MarkupType.orderedList);
} else if (option.label === qpItems[2].label) {
await this.addMarkup(MarkupType.taskList);
} else if (option.label === qpItems[3].label) {
await this.addMarkup(MarkupType.code);
} else if (option.label === qpItems[4].label) {
await this.addMarkup(MarkupType.codeblock);
}
}
}));
}
/**
* Add the markup to the content
* @param type
* @returns
*/
private static async addMarkup(type: MarkupType) {
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const selection = editor.selection;
const hasTextSelection = !selection.isEmpty;
const markers = this.getMarkers(type);
if (!markers) {
return;
}
const crntSelection = selection.active;
if (hasTextSelection) {
// Replace the selection and surround with the markup
const selectionText = editor.document.getText(selection);
const txt = await this.insertText(markers, type, selectionText);
editor.edit(builder => {
builder.replace(selection, txt);
});
} else {
const txt = await this.insertText(markers, type);
// Insert the markers where cursor is located.
const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length;
let newPosition = crntSelection.with(crntSelection.line, crntSelection.character + markerLength);
await editor.edit(builder => {
builder.insert(newPosition, txt);
});
if (type === MarkupType.codeblock) {
newPosition = crntSelection.with(crntSelection.line + 1, 0);
}
editor.selection = new Selection(newPosition, newPosition);
}
}
/**
* Check if the text will be wrapped
* @param type
* @returns
*/
private static isMarkupWrapping(type: MarkupType) {
return (
type === MarkupType.blockquote ||
type === MarkupType.heading ||
type === MarkupType.unorderedList ||
type === MarkupType.orderedList ||
type === MarkupType.taskList
);
}
/**
* Insert text at the current cursor position
*/
private static async insertText(marker: string | undefined, type: MarkupType, text: string | null = null) {
const crntText = text || this.lineBreak(type);
if (this.isMarkupWrapping(type)) {
if (type === MarkupType.heading) {
const headingLvl = await window.showQuickPick([
"Heading 1",
"Heading 2",
"Heading 3",
"Heading 4",
"Heading 5",
"Heading 6"
], {
canPickMany: false,
placeHolder: "Which heading level do you want to insert?",
ignoreFocusOut: false
});
if (headingLvl) {
const headingNr = parseInt(headingLvl.replace("Heading ", ""));
return `${Array(headingNr + 1).join(marker)} ${crntText}`;
}
}
if (type === MarkupType.unorderedList || type === MarkupType.taskList) {
const lines = crntText.split("\n").map(line => `${marker} ${line}`);
return lines.join("\n");
}
if (type === MarkupType.orderedList) {
const lines = crntText.split("\n").map((line, idx) => `${idx+1}. ${line}`);
return lines.join("\n");
}
return `${marker} ${crntText}`;
} else {
return `${marker}${crntText}${marker}`;
}
}
/**
* Check if linebreak needs to be added
* @param type
* @returns
*/
private static lineBreak(type: MarkupType) {
if (type === MarkupType.codeblock) {
return `\n\n`;
}
return "";
}
/**
* Retrieve the type of markers
* @param type
* @returns
*/
private static getMarkers(type: MarkupType) {
switch(type) {
case MarkupType.bold:
return `**`;
case MarkupType.italic:
return `*`;
case MarkupType.strikethrough:
return `~~`;
case MarkupType.code:
return "`";
case MarkupType.codeblock:
return "```";
case MarkupType.blockquote:
return ">";
case MarkupType.heading:
return "#";
case MarkupType.unorderedList:
return "-";
case MarkupType.orderedList:
return "1.";
case MarkupType.taskList:
return "- [ ]";
default:
return;
}
}
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
export interface ICompressIconProps {
className?: string;
}
export const CompressIcon: React.FunctionComponent<ICompressIconProps> = ({className}: React.PropsWithChildren<ICompressIconProps>) => {
return (
<svg className={className || ""} aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z"></path>
</svg>
);
};

View File

@@ -23,7 +23,7 @@ export const DEFAULT_CONTENT_TYPE: ContentType = {
"type": "datetime"
},
{
"title": "Article preview",
"title": "Content preview",
"name": "preview",
"type": "image"
},

View File

@@ -28,7 +28,22 @@ export const COMMAND_NAME = {
collapseSections: getCommandName("collapseSections"),
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
dashboardClose: getCommandName("dashboard.close"),
promote: getCommandName("promoteSettings"),
insertImage: getCommandName("insertImage"),
createFolder: getCommandName("createFolder"),
diagnostics: getCommandName("diagnostics"),
// WYSIWYG
bold: getCommandName("markup.bold"),
italic: getCommandName("markup.italic"),
strikethrough: getCommandName("markup.strikethrough"),
code: getCommandName("markup.code"),
codeblock: getCommandName("markup.codeblock"),
heading: getCommandName("markup.heading"),
blockquote: getCommandName("markup.blockquote"),
unorderedlist: getCommandName("markup.unorderedlist"),
orderedlist: getCommandName("markup.orderedlist"),
taskList: getCommandName("markup.tasklist"),
options: getCommandName("markup.options"),
};

View File

@@ -5,4 +5,13 @@ export const ExtensionState = {
Version: `frontMatter:Version`,
SettingPromoted: `frontMatter:Settings:Promoted`,
MoveTemplatesFolder: `frontMatter:Templates:Move`,
Dashboard: {
Contents: {
Sorting: `frontMatter:Dashboard:Contents:Sorting`,
},
Media: {
Sorting: `frontMatter:Dashboard:Media:Sorting`,
}
}
};

View File

@@ -2,5 +2,7 @@ export const CONTEXT = {
canInit: "frontMatterCanInit",
canOpenPreview: "frontMatterCanOpenPreview",
canOpenDashboard: "frontMatterCanOpenDashboard",
isEnabled: "frontMatter:enabled"
isEnabled: "frontMatter:enabled",
isDashboardOpen: "frontMatter:dashboard:open",
wysiwyg: "frontMatter:markdown:wysiwyg",
};

View File

@@ -4,6 +4,8 @@ export const CONFIG_KEY = "frontMatter";
export const SETTING_TAXONOMY_TAGS = "taxonomy.tags";
export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories";
export const SETTING_TAXONOMY_CUSTOM = "taxonomy.customTaxonomy";
export const SETTING_DATE_FORMAT = "taxonomy.dateFormat";
export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields";
export const SETTING_TAXONOMY_CONTENT_TYPES = "taxonomy.contentTypes";
@@ -40,6 +42,11 @@ 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_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";

View File

@@ -3,5 +3,6 @@ export enum DashboardCommand {
pages = "pages",
settings = "settings",
media = "media",
viewData = "viewData"
viewData = "viewData",
mediaUpdate = "mediaUpdate"
}

View File

@@ -19,4 +19,6 @@ export enum DashboardMessage {
updateMediaMetadata = 'updateMediaMetadata',
createMediaFolder = 'createMediaFolder',
setFramework = 'setFramework',
setState = 'setState',
runCustomScript = 'runCustomScript',
}

View File

@@ -28,7 +28,7 @@ export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({onCli
<Menu as="span" className="-ml-px relative block">
<Menu.Button
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-700 hover:bg-teal-800 focus:outline-none disabled:bg-gray-500"
className="h-full inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-700 hover:bg-teal-800 focus:outline-none disabled:bg-gray-500"
disabled={disabled}>
<span className="sr-only">Open options</span>
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />

View File

@@ -6,6 +6,7 @@ import { Header } from '../Header';
import { Overview } from './Overview';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
import usePages from '../../hooks/usePages';
export interface IContentsProps {
pages: Page[];
@@ -14,19 +15,20 @@ export interface IContentsProps {
export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loading}: React.PropsWithChildren<IContentsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const { pageItems } = usePages(pages);
const pageFolders = [...new Set(pages.map(page => page.fmFolder))];
const pageFolders = [...new Set(pageItems.map(page => page.fmFolder))];
return (
<main className={`h-full w-full`}>
<div className="flex flex-col h-full overflow-auto">
<Header
folders={pageFolders}
totalPages={pages.length}
totalPages={pageItems.length}
settings={settings} />
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pages} settings={settings} /> }
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />

View File

@@ -2,12 +2,12 @@ import * as React from 'react';
import { Spinner } from './Spinner';
import useMessages from '../hooks/useMessages';
import useDarkMode from '../../hooks/useDarkMode';
import usePages from '../hooks/usePages';
import { WelcomeScreen } from './WelcomeScreen';
import { useRecoilValue } from 'recoil';
import { DashboardViewSelector } from '../state';
import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { ViewType } from '../models';
export interface IDashboardProps {
showWelcome: boolean;
@@ -15,7 +15,6 @@ export interface IDashboardProps {
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
const { loading, pages, settings } = useMessages();
const { pageItems } = usePages(pages);
const view = useRecoilValue(DashboardViewSelector);
useDarkMode();
@@ -31,9 +30,9 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
return <WelcomeScreen settings={settings} />;
}
if (view === 'media') {
if (view === ViewType.Media) {
return <Media />;
}
return <Contents pages={pageItems} loading={loading} />;
return <Contents pages={pages} loading={loading} />;
};

View File

@@ -2,8 +2,10 @@ import { CollectionIcon } from '@heroicons/react/outline';
import { basename, join } from 'path';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Sorting } from '.';
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { ViewType } from '../../models';
import { SelectedMediaFolderAtom, SettingsAtom } from '../../state';
export interface IBreadcrumbProps {}
@@ -31,9 +33,10 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
return false;
}
}
1
for (let i = 0; i < contentFolders.length; i++) {
const contentFolder = parseWinPath(contentFolders[i]) as string;
const folder = contentFolders[i];
const contentFolder = parseWinPath(folder.path) as string;
const relContentPath = folderPath.replace(contentFolder, '');
return relContentPath.length > 1 && folderPath.startsWith(contentFolder);
}
@@ -62,8 +65,8 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
}, [selectedFolder]);
return (
<nav className="bg-gray-200 text-vulcan-300 dark:bg-vulcan-400 dark:text-whisper-600 border-b border-gray-300 dark:border-vulcan-100 flex py-2" aria-label="Breadcrumb">
<ol role="list" className="w-full mx-auto flex space-x-4 px-5">
<nav className="w-full bg-gray-200 text-vulcan-300 dark:bg-vulcan-400 dark:text-whisper-600 border-b border-gray-300 dark:border-vulcan-100 flex justify-between py-2" aria-label="Breadcrumb">
<ol role="list" className="flex space-x-4 px-5">
<li className="flex">
<div className="flex items-center">
<button onClick={() => setSelectedFolder(HOME_PAGE_NAVIGATION_ID)} className="text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500">
@@ -94,6 +97,10 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
</li>
))}
</ol>
<div className={`flex px-5`}>
<Sorting view={ViewType.Media} disableCustomSorting />
</div>
</nav>
);
};

View File

@@ -1,7 +1,7 @@
import { XCircleIcon } from '@heroicons/react/solid';
import * as React from 'react';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { SortingSelector, FolderSelector, TagSelector, CategorySelector, SortingAtom, DEFAULT_SORTING_OPTION, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
import { FolderSelector, TagSelector, CategorySelector, SortingAtom, FolderAtom, DEFAULT_FOLDER_STATE, TagAtom, CategoryAtom, DEFAULT_TAG_STATE, DEFAULT_CATEGORY_STATE } from '../../state';
import { DefaultValue } from 'recoil';
@@ -17,7 +17,6 @@ export interface IClearFiltersProps {}
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props: React.PropsWithChildren<IClearFiltersProps>) => {
const [ show, setShow ] = React.useState(false);
const sorting = useRecoilValue(SortingSelector);
const folder = useRecoilValue(FolderSelector);
const tag = useRecoilValue(TagSelector);
const category = useRecoilValue(CategorySelector);
@@ -36,19 +35,19 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (props:
};
React.useEffect(() => {
if (sorting !== DEFAULT_SORTING_OPTION || folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
if (folder !== DEFAULT_FOLDER_STATE || tag !== DEFAULT_TAG_STATE || category !== DEFAULT_CATEGORY_STATE) {
setShow(true);
} else {
setShow(false);
}
}, [sorting, folder, tag, category]);
}, [folder, tag, category]);
if (!show) return null;
return (
<button className="flex items-center hover:text-teal-600" onClick={reset} title={`Clear filters, grouping, and sorting`}>
<XCircleIcon className={`inline-block w-5 h-5 mr-1`} /><span>Clear</span>
<span className={`sr-only`}> filters, grouping, and sorting</span>
<span className={`sr-only`}> filters and grouping</span>
</button>
);
};

View File

@@ -1,19 +1,19 @@
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { FolderAtom } from '../../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { FolderAtom, SettingsSelector } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface IFoldersProps {
folders: string[];
}
export interface IFoldersProps {}
const DEFAULT_TYPE = "All types";
export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React.PropsWithChildren<IFoldersProps>) => {
export const Folders: React.FunctionComponent<IFoldersProps> = ({}: React.PropsWithChildren<IFoldersProps>) => {
const [ crntFolder, setCrntFolder ] = useRecoilState(FolderAtom);
const settings = useRecoilValue(SettingsSelector);
const contentFolders = settings?.contentFolders || [];
if (folders.length <= 1) {
if (contentFolders.length <= 1) {
return null;
}
@@ -29,12 +29,12 @@ export const Folders: React.FunctionComponent<IFoldersProps> = ({folders}: React
isCurrent={!crntFolder}
onClick={(value) => setCrntFolder(value)} />
{folders.map((option) => (
{contentFolders.map((option) => (
<MenuItem
key={option}
title={option}
value={option}
isCurrent={option === crntFolder}
key={option.title}
title={option.title}
value={option.title}
isCurrent={option.title === crntFolder}
onClick={(value) => setCrntFolder(value)} />
))}
</MenuItems>

View File

@@ -3,14 +3,14 @@ import { Sorting } from './Sorting';
import { Searchbox } from './Searchbox';
import { Filter } from './Filter';
import { Folders } from './Folders';
import { Settings } from '../../models';
import { Settings, ViewType } from '../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { Startup } from '../Startup';
import { Navigation } from '../Navigation';
import { Grouping } from '.';
import { ViewSwitch } from './ViewSwitch';
import { useRecoilState } from 'recoil';
import { CategoryAtom, DashboardViewAtom, TagAtom } from '../../state';
import { useRecoilState, useResetRecoilState } from 'recoil';
import { CategoryAtom, DashboardViewAtom, SortingAtom, TagAtom } from '../../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { ClearFilters } from './ClearFilters';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
@@ -33,6 +33,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
const [ view, setView ] = useRecoilState(DashboardViewAtom);
const resetSorting = useResetRecoilState(SortingAtom)
const createContent = () => {
Messenger.send(DashboardMessage.createContent);
@@ -46,22 +47,27 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
Messenger.send(DashboardMessage.createByTemplate);
};
const updateView = (view: ViewType) => {
setView(view);
resetSorting();
}
return (
<div className={`w-full sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
<div className={`px-4 bg-gray-50 dark:bg-vulcan-50 border-b-2 border-gray-200 dark:border-vulcan-200`}>
<div className={`flex items-center justify-start`}>
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("contents")}>
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Contents)}>
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
</button>
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("media")}>
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Media)}>
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</button>
</div>
</div>
{
view === "contents" && (
view === ViewType.Contents && (
<>
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
<Searchbox />
@@ -98,7 +104,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end space-x-4 lg:space-x-6 xl:space-x-8 bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100`}>
<ClearFilters />
<Folders folders={folders || []} />
<Folders />
<Filter label={`Tag`} activeItem={crntTag} items={settings?.tags || []} onClick={(value) => setCrntTag(value)} />
@@ -106,14 +112,14 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
<Grouping />
<Sorting />
<Sorting view={ViewType.Contents} />
</div>
</>
)
}
{
view === "media" && (
view === ViewType.Media && (
<>
<Pagination />
<Breadcrumb />

View File

@@ -1,37 +1,85 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { Menu } from '@headlessui/react';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ExtensionState } from '../../../constants';
import { SortOrder, SortType } from '../../../models';
import { SortOption } from '../../constants/SortOption';
import { SearchSelector, SortingAtom } from '../../state';
import { DashboardMessage } from '../../DashboardMessage';
import { ViewType } from '../../models';
import { SortingOption } from '../../models/SortingOption';
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface ISortingProps {}
export interface ISortingProps {
disableCustomSorting?: boolean;
view: ViewType;
}
export const sortOptions = [
{ name: "Last modified", id: SortOption.LastModified },
{ name: "By filename (asc)", id: SortOption.FileNameAsc },
{ name: "By filename (desc)", id: SortOption.FileNameDesc },
export const sortOptions: SortingOption[] = [
{ 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 },
{ name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string },
];
export const Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsWithChildren<ISortingProps>) => {
export const Sorting: React.FunctionComponent<ISortingProps> = ({disableCustomSorting, view}: React.PropsWithChildren<ISortingProps>) => {
const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom);
const searchValue = useRecoilValue(SearchSelector);
const settings = useRecoilValue(SettingsSelector);
const crntSort = sortOptions.find(x => x.id === crntSorting);
const updateSorting = (value: SortingOption) => {
Messenger.send(DashboardMessage.setState, {
key: `${view === ViewType.Contents ? ExtensionState.Dashboard.Contents.Sorting : ExtensionState.Dashboard.Media.Sorting}`,
value: value
});
setCrntSorting(value)
};
let allOptions = [...sortOptions];
if (settings?.customSorting && !disableCustomSorting) {
allOptions = [...allOptions, ...settings.customSorting.map((s) => ({
title: s.title || s.name,
name: s.name,
id: s.id || `${s.name}-${s.order}`,
order: s.order,
type: s.type
}))];
}
let crntSortingOption = crntSorting;
if (!crntSortingOption) {
if (view === ViewType.Contents) {
crntSortingOption = settings?.dashboardState?.contents?.sorting || null;
} else if (view === ViewType.Media) {
crntSortingOption = settings?.dashboardState?.media?.sorting || null;
}
if (crntSortingOption === null) {
if (view === ViewType.Contents && settings?.dashboardState.contents.defaultSorting) {
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
} else if (view === ViewType.Media && settings?.dashboardState.contents.defaultSorting) {
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
}
}
}
let crntSort = allOptions.find(x => x.id === crntSortingOption?.id) || sortOptions[0];
return (
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton label={`Sort by`} title={crntSort?.name || ""} disabled={!!searchValue} />
<MenuButton label={`Sort by`} title={crntSort?.title || crntSort?.name || ""} disabled={!!searchValue} />
<MenuItems>
{sortOptions.map((option) => (
{allOptions.map((option) => (
<MenuItem
key={option.id}
title={option.name}
value={option.id}
isCurrent={option.id === crntSorting}
onClick={(value) => setCrntSorting(value)} />
title={option.title || option.name}
value={option}
isCurrent={option.id === crntSorting?.id}
onClick={(value) => updateSorting(value)} />
))}
</MenuItems>
</Menu>

View File

@@ -2,13 +2,16 @@ import * as React from 'react';
import { FolderAddIcon } from '@heroicons/react/outline';
import { useRecoilValue } from 'recoil';
import { DashboardMessage } from '../../DashboardMessage';
import { SelectedMediaFolderAtom } from '../../state';
import { SelectedMediaFolderAtom, SettingsSelector } from '../../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { ChoiceButton } from '../ChoiceButton';
import { CustomScript, ScriptType } from '../../../models';
export interface IFolderCreationProps {}
export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (props: React.PropsWithChildren<IFolderCreationProps>) => {
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
const settings = useRecoilValue(SettingsSelector);
const onFolderCreation = () => {
Messenger.send(DashboardMessage.createMediaFolder, {
@@ -16,6 +19,24 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (pr
});
};
const runCustomScript = (script: CustomScript) => {
Messenger.send(DashboardMessage.runCustomScript, {script, path: selectedFolder});
};
const scripts = (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFolder);
if (scripts.length > 0) {
return (
<ChoiceButton
title={`Create new folder`}
choices={scripts.map(s => ({
title: s.title,
onClick: () => runCustomScript(s)
}))}
onClick={onFolderCreation}
disabled={!settings?.initialized} />
)
}
return (
<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`}

View File

@@ -1,16 +1,21 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PencilIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
import { Menu } from '@headlessui/react';
import { PhotographIcon } from '@heroicons/react/outline';
import { basename, dirname } from 'path';
import * as React from 'react';
import { useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { CustomScript } from '../../../helpers/CustomScript';
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 { MenuItem, MenuItems } from '../Menu';
import { Alert } from '../Modals/Alert';
import { Metadata } from '../Modals/Metadata';
import { MenuButton } from './MenuButton'
export interface IItemProps {
media: MediaInfo;
}
@@ -61,6 +66,10 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || "");
};
const runCustomScript = (script: CustomScript) => {
Messenger.send(DashboardMessage.runCustomScript, {script, path: media.fsPath});
};
const insertToArticle = () => {
const relPath = getRelPath();
Messenger.send(DashboardMessage.insertPreviewImage, {
@@ -78,9 +87,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const insertSnippet = () => {
const relPath = getRelPath();
let snippet = settings?.mediaSnippet.join("\n");
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
snippet = snippet?.replace("{alt}", alt || "");
snippet = snippet?.replace("{caption}", caption || "");
snippet = snippet?.replace("{filename}", basename(relPath || ""));
Messenger.send(DashboardMessage.insertPreviewImage, {
image: parseWinPath(relPath) || "",
@@ -111,8 +122,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
}
}
if (media?.stats?.size) {
const size = media.stats.size / (1024*1024);
if (media?.size) {
const size = media.size / (1024*1024);
if (size > 1) {
sizeDetails.push(`${size.toFixed(2)} MB`);
} else {
@@ -149,6 +160,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
setFilename(getFileName());
};
const customScriptActions = () => {
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile).map(script => (
<MenuItem
key={script.title}
title={script.title}
onClick={() => runCustomScript(script)} />
))
}
useEffect(() => {
if (media.alt !== alt) {
setAlt(media.alt);
@@ -185,54 +205,54 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</button>
<div className={`relative py-4 pl-4 pr-12`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<button title={`Edit metadata`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={updateMetadata}>
<PencilIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Edit metadata</span>
</button>
{
viewData?.data?.filePath ? (
<>
<button
title={`Insert into your content`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={insertToArticle}>
<CheckCircleIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Insert into your content</span>
</button>
<div className="flex items-center">
<Menu as="div" className="relative z-10 inline-block text-left">
<MenuButton title={`Menu`} />
<MenuItems>
<MenuItem
title={`Edit metadata`}
onClick={updateMetadata}
/>
{
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
<button
title={`Insert your media snippet`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={insertSnippet}>
<CodeIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Insert your media snippet</span>
</button>
viewData?.data?.filePath ? (
<>
<MenuItem
title={`Insert image markdown`}
onClick={insertToArticle} />
{
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
<MenuItem
title={`Insert snippet`}
onClick={insertSnippet} />
)
}
{ customScriptActions() }
</>
) : (
<>
<MenuItem
title={`Copy media path`}
onClick={copyToClipboard} />
{ customScriptActions() }
<MenuItem
title={`Delete`}
onClick={deleteMedia} />
</>
)
}
</>
) : (
<>
<button title={`Copy media path`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={copyToClipboard}>
<ClipboardCopyIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Copy media path</span>
</button>
<button title={`Delete media`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={deleteMedia}>
<TrashIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Delete media</span>
</button>
</>
)
}
</MenuItems>
</Menu>
</div>
</div>
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center break-all">
{basename(parseWinPath(media.fsPath) || "")}
</p>
{
@@ -256,7 +276,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getFolder()}</span>
</p>
{
(media?.stats?.size || media?.dimensions) && (
(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>

View File

@@ -0,0 +1,20 @@
import { Menu } from '@headlessui/react';
import { DotsVerticalIcon } from '@heroicons/react/outline';
import * as React from 'react';
export interface IMenuButtonProps {
title: string;
disabled?: boolean;
}
export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({ title, disabled }: React.PropsWithChildren<IMenuButtonProps>) => {
return (
<div className={`inline-flex items-center ${disabled ? 'opacity-50' : ''}`}>
<Menu.Button disabled={disabled} className="group inline-flex justify-center text-sm font-medium text-vulcan-400 hover:text-vulcan-600 dark:text-gray-400 dark:hover:text-whisper-600">
<span className="sr-only">{title}</span>
<DotsVerticalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
);
};

View File

@@ -1,9 +1,11 @@
import { EventData } from '@estruyf/vscode';
import { Messenger } from '@estruyf/vscode/dist/client';
import { RefreshIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { DashboardCommand } from '../../DashboardCommand';
import { DashboardMessage } from '../../DashboardMessage';
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector } from '../../state';
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector, SortingSelector } from '../../state';
import { FolderCreation } from './FolderCreation';
import { LIMIT } from './Media';
import { PaginationButton } from './PaginationButton';
@@ -12,6 +14,7 @@ export interface IPaginationProps {}
export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.PropsWithChildren<IPaginationProps>) => {
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const crntSorting = useRecoilValue(SortingSelector);
const totalMedia = useRecoilValue(MediaTotalSelector);
const [ , setLoading ] = useRecoilState(LoadingAtom);
const [ page, setPage ] = useRecoilState(PageAtom);
@@ -46,11 +49,23 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
Messenger.send(DashboardMessage.refreshMedia, { folder: selectedFolder });
}
const mediaUpdate = (message: MessageEvent<EventData<{ key: string, value: any }>>) => {
if (message.data.command === DashboardCommand.mediaUpdate) {
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page,
folder: selectedFolder || '',
sorting: crntSorting
});
}
}
React.useEffect(() => {
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page,
folder: selectedFolder || ''
folder: selectedFolder || '',
sorting: crntSorting
});
}, [page]);
@@ -58,11 +73,25 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page: 0,
folder: selectedFolder || ''
folder: selectedFolder || '',
sorting: crntSorting
});
setPage(0);
}, [selectedFolder]);
React.useEffect(() => {
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page,
folder: selectedFolder || '',
sorting: crntSorting
});
}, [crntSorting]);
React.useEffect(() => {
Messenger.listen(mediaUpdate);
}, []);
return (
<nav
className="py-4 px-5 flex items-center justify-between bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100"

View File

@@ -3,7 +3,7 @@ import * as React from 'react';
export interface IMenuItemProps {
title: string;
value: any;
value?: any;
isCurrent?: boolean;
disabled?: boolean;
onClick: (value: any) => void;

View File

@@ -11,8 +11,8 @@ export interface ISponsorMsgProps {
export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = ({beta, version}: React.PropsWithChildren<ISponsorMsgProps>) => {
return (
<p className={`w-full max-w-7xl mx-auto px-4 text-vulcan-50 dark:text-whisper-900 py-2 text-center space-x-8 flex items-center justify-between`}>
<a className={`group inline-flex justify-center items-center space-x-2 text-vulcan-500 dark:text-whisper-500 hover:text-vulcan-600 dark:hover:text-whisper-300 opacity-50 hover:opacity-100`} href={SPONSOR_LINK} title="Sponsor Front Matter">
<span>Sponsor</span> <HeartIcon className={`h-5 w-5 group-hover:fill-current`} />
<a className={`group inline-flex justify-center items-center space-x-2 text-vulcan-500 dark:text-whisper-500 hover:text-vulcan-600 dark:hover:text-whisper-300 opacity-50 hover:opacity-100`} href={SPONSOR_LINK} title="Support Front Matter">
<span>Support</span> <HeartIcon className={`h-5 w-5 group-hover:fill-current`} />
</a>
<span>Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''}</span>
<a className={`group inline-flex justify-center items-center space-x-2 text-vulcan-500 dark:text-whisper-500 hover:text-vulcan-600 dark:hover:text-whisper-300 opacity-50 hover:opacity-100`} href={REVIEW_LINK} title="Review Front Matter">

View File

@@ -1,5 +1,6 @@
export enum SortOption {
LastModified = 1,
FileNameAsc,
FileNameDesc
LastModifiedAsc = "LastModifiedAsc",
LastModifiedDesc = "LastModifiedDesc",
FileNameAsc = "FileNameAsc",
FileNameDesc = "FileNameDesc"
}

View File

@@ -6,6 +6,7 @@ import { Page } from '../models/Page';
import { DashboardViewAtom, SettingsAtom, ViewDataAtom } from '../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { ViewType } from '../models';
export default function useMessages() {
const [loading, setLoading] = useState<boolean>(false);
@@ -21,8 +22,8 @@ export default function useMessages() {
break;
case DashboardCommand.viewData:
setViewData(message.data.data);
if (message.data.data?.type === 'media') {
setView('media');
if (message.data.data?.type === ViewType.Media) {
setView(ViewType.Media);
}
break;
case DashboardCommand.settings:

View File

@@ -3,8 +3,10 @@ import { SortOption } from '../constants/SortOption';
import { Tab } from '../constants/Tab';
import { Page } from '../models/Page';
import Fuse from 'fuse.js';
import { useRecoilValue } from 'recoil';
import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingSelector, TabSelector, TagSelector } from '../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingAtom, TabSelector, TagSelector } from '../state';
import { SortOrder, SortType } from '../../models';
import { Sorting } from '../../helpers/Sorting';
const fuseOptions: Fuse.IFuseOptions<Page> = {
keys: [
@@ -16,9 +18,9 @@ const fuseOptions: Fuse.IFuseOptions<Page> = {
export default function usePages(pages: Page[]) {
const [ pageItems, setPageItems ] = useState<Page[]>([]);
const [ sorting, setSorting ] = useRecoilState(SortingAtom);
const settings = useRecoilValue(SettingsSelector);
const tab = useRecoilValue(TabSelector);
const sorting = useRecoilValue(SortingSelector);
const folder = useRecoilValue(FolderSelector);
const search = useRecoilValue(SearchSelector);
const tag = useRecoilValue(TagSelector);
@@ -26,6 +28,15 @@ export default function usePages(pages: Page[]) {
useEffect(() => {
const draftField = settings?.draftField;
let usedSorting = sorting;
if (!usedSorting) {
const lastSort = settings?.dashboardState.contents.sorting;
if (lastSort) {
setSorting(lastSort);
return;
}
}
// Check if search needs to be performed
let searchedPages = pages;
@@ -58,12 +69,28 @@ export default function usePages(pages: Page[]) {
// Sort the pages
let pagesSorted: Page[] = Object.assign([], pagesToShow);
if (!search) {
if (sorting === SortOption.FileNameAsc) {
pagesSorted = pagesToShow.sort((a, b) => a.fmFileName.toLowerCase().localeCompare(b.fmFileName.toLowerCase()));
} else if (sorting === SortOption.FileNameDesc) {
pagesSorted = pagesToShow.sort((a, b) => b.fmFileName.toLowerCase().localeCompare(a.fmFileName.toLowerCase()));
if (sorting && sorting.id === SortOption.FileNameAsc) {
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.LastModifiedAsc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmModified"));
} else if (sorting && sorting.id === SortOption.LastModifiedDesc) {
pagesSorted = pagesSorted.sort(Sorting.number("fmModified")).reverse();
} else if (sorting && sorting.id && sorting.name) {
const { order, name, type } = sorting;
if (type === SortType.string) {
pagesSorted = pagesSorted.sort(Sorting.alphabetically(name));
} else if (type === SortType.date) {
pagesSorted = pagesSorted.sort(Sorting.date(name));
}
if (order === SortOrder.desc) {
pagesSorted = pagesSorted.reverse();
}
} else {
pagesSorted = pagesToShow.sort((a, b) => b.fmModified - a.fmModified);
pagesSorted = pagesSorted.sort(Sorting.number("fmModified")).reverse();
}
}

View File

@@ -1,7 +1,8 @@
import { VersionInfo } from '../../models/VersionInfo';
import { ViewType } from '../state';
import { ContentFolder } from '../../models/ContentFolder';
import { ContentType, DraftField, Framework } from '../../models';
import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models';
import { SortingOption } from './SortingOption';
export interface Settings {
beta: boolean;
@@ -16,8 +17,21 @@ export interface Settings {
pageViewType: ViewType | undefined;
mediaSnippet: string[];
contentTypes: ContentType[];
contentFolders: string[];
contentFolders: ContentFolder[];
crntFramework: string;
framework: Framework | null | undefined;
draftField: DraftField | null | undefined;
customSorting: SortingSetting[] | undefined;
dashboardState: DashboardState;
scripts: CustomScript[];
}
export interface DashboardState {
contents: ViewState;
media: ViewState;
}
export interface ViewState {
sorting: SortingOption | null | undefined;
defaultSorting: string | null | undefined;
}

View File

@@ -0,0 +1,10 @@
import { SortOrder, SortType } from "../../models";
import { SortOption } from "../constants/SortOption";
export interface SortingOption {
title?: string;
name: string;
id: SortOption | string;
order: SortOrder,
type: SortType;
}

View File

@@ -0,0 +1,4 @@
export enum ViewType {
Contents = "contents",
Media = "media"
}

View File

@@ -1,3 +1,5 @@
export * from './Page';
export * from './Settings';
export * from './SortingOption';
export * from './Status';
export * from './ViewType';

View File

@@ -1,6 +1,7 @@
import { atom } from 'recoil';
import { ViewType } from '../../models';
export const DashboardViewAtom = atom<"contents" | "media">({
export const DashboardViewAtom = atom<ViewType>({
key: 'DashboardViewAtom',
default: "contents"
default: ViewType.Contents
});

View File

@@ -1,9 +1,10 @@
import { atom } from 'recoil';
import { SortOption } from '../../constants/SortOption';
import { SortingOption } from '../../models/SortingOption';
export const DEFAULT_SORTING_OPTION = SortOption.LastModified;
export const DEFAULT_SORTING_OPTION = SortOption.LastModifiedDesc;
export const SortingAtom = atom<SortOption>({
export const SortingAtom = atom<SortingOption | null>({
key: 'SortingAtom',
default: DEFAULT_SORTING_OPTION
default: null
});

View File

@@ -1,6 +1,6 @@
import { DashboardData } from '../models/DashboardData';
import { Template } from '../commands/Template';
import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL } from '../constants';
import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM } from '../constants';
import * as os from 'os';
import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
@@ -9,11 +9,11 @@ import { Command } from "../panelWebView/Command";
import { CommandToCode } from '../panelWebView/CommandToCode';
import { Article } from '../commands';
import { TagType } from '../panelWebView/TagType';
import { DraftField, TaxonomyType } from '../models';
import { CustomTaxonomyData, DraftField, ScriptType, TaxonomyType } from '../models';
import { exec } from 'child_process';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Content } from 'mdast';
import { COMMAND_NAME } from '../constants/Extension';
import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from '../constants/Extension';
import { Folders } from '../commands/Folders';
import { Preview } from '../commands/Preview';
import { openFileInEditor } from '../helpers/openFileInEditor';
@@ -109,14 +109,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
case CommandToCode.updateKeywords:
this.updateTags(TagType.keywords, msg.data || []);
break;
case CommandToCode.updateCustomTaxonomy:
this.updateCustomTaxonomy(msg.data);
break;
case CommandToCode.addTagToSettings:
this.addTags(TagType.tags, msg.data);
break;
case CommandToCode.addCategoryToSettings:
this.addTags(TagType.categories, msg.data);
break;
case CommandToCode.addToCustomTaxonomy:
this.addCustomTaxonomy(msg.data);
break;
case CommandToCode.openSettings:
commands.executeCommand('workbench.action.openSettings', '@ext:eliostruyf.vscode-front-matter');
const isBeta = Extension.getInstance().isBetaVersion();
commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`);
break;
case CommandToCode.openFile:
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
@@ -405,8 +412,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
},
tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [],
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: Settings.get(SETTING_CUSTOM_SCRIPTS),
scripts: (Settings.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
isInitialized: await Template.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
@@ -464,6 +472,40 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Update the tags in the current document
* @param data
*/
private updateCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.name) {
return;
}
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
article.data[data.name] = data.options || [];
ArticleHelper.update(editor, article);
this.pushMetadata(article!.data);
}
}
/**
* Add tag to the settings
* @param data
*/
private async addCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.option) {
return;
}
await Settings.updateCustomTaxonomy(data.id, data.option);
}
/**
* Add tag to the settings
* @param tagType

View File

@@ -16,6 +16,8 @@ import { DashboardData } from './models/DashboardData';
import { Settings as SettingsHelper } from './helpers';
import { Content } from './commands/Content';
import ContentProvider from './providers/ContentProvider';
import { Wysiwyg } from './commands/Wysiwyg';
import { Diagnostics } from './commands/Diagnostics';
let frontMatterStatusBar: vscode.StatusBarItem;
let statusDebouncer: { (fnc: any, time: number): void; };
@@ -45,6 +47,10 @@ export async function activate(context: vscode.ExtensionContext) {
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
Dashboard.open(data);
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
Dashboard.close();
}));
if (!extension.getVersion().usedVersion) {
vscode.commands.executeCommand(COMMAND_NAME.dashboard);
@@ -52,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext) {
// Register the explorer view
const explorerSidebar = ExplorerView.getInstance(extensionUri);
let explorerView = vscode.window.registerWebviewViewProvider(ExplorerView.viewType, explorerSidebar, {
const explorerView = vscode.window.registerWebviewViewProvider(ExplorerView.viewType, explorerSidebar, {
webviewOptions: {
retainContextWhenHidden: true
}
@@ -61,35 +67,35 @@ export async function activate(context: vscode.ExtensionContext) {
// Folding the front matter of markdown files
vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider());
let insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => {
const insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => {
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.tags);
});
let insertCategories = vscode.commands.registerCommand(COMMAND_NAME.insertCategories, async () => {
const insertCategories = vscode.commands.registerCommand(COMMAND_NAME.insertCategories, async () => {
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
explorerSidebar.triggerInputFocus(TagType.categories);
});
let createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
const createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
Settings.create(TaxonomyType.Tag);
});
let createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
const createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
Settings.create(TaxonomyType.Category);
});
let exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export);
const exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export);
let remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap);
const remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap);
let setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate);
const setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate);
let generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, Article.generateSlug);
const generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, Article.generateSlug);
let createFromTemplate = vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
const createFromTemplate = vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
const folderPath = Folders.getFolderPath(folder);
if (folderPath) {
Template.create(folderPath);
@@ -179,6 +185,12 @@ export async function activate(context: vscode.ExtensionContext) {
// Create the editor experience for bulk scripts
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(ContentProvider.scheme, new ContentProvider()));
// What you see, is what you get
Wysiwyg.registerCommands(subscriptions);
// Diagnostics
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show));
// Subscribe all commands
subscriptions.push(
insertTags,

View File

@@ -91,11 +91,13 @@ export class ArticleHelper {
}
}
}
return matter.stringify(content, data, ({
...TomlEngine,
...langOpts,
noArrayIndent: !indentArray,
skipInvalid: true,
noCompatMode: true,
lineWidth: 500,
indent: spaces || 2
} as DumpOptions as any));

View File

@@ -1,4 +1,4 @@
import { CustomScript as ICustomScript } from '../models/PanelSettings';
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
import { ArticleHelper } from '.';
import { Folders } from '../commands/Folders';
@@ -8,21 +8,27 @@ import * as os from 'os';
import { join } from 'path';
import { Notifications } from './Notifications';
import ContentProvider from '../providers/ContentProvider';
import { Dashboard } from '../commands/Dashboard';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
export class CustomScript {
public static async run(script: ICustomScript): Promise<void> {
public static async run(script: ICustomScript, path: string | null = null): Promise<void> {
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const wsPath = wsFolder.fsPath;
if (script.bulk) {
// Run script on all files
CustomScript.bulkRun(wsPath, script);
if (script.type === ScriptType.MediaFile || script.type === ScriptType.MediaFolder) {
CustomScript.runMediaScript(wsPath, path, script);
} else {
// Run script on current file.
CustomScript.singleRun(wsPath, script);
if (script.bulk) {
// Run script on all files
CustomScript.bulkRun(wsPath, script);
} else {
// Run script on current file.
CustomScript.singleRun(wsPath, script);
}
}
}
}
@@ -79,6 +85,38 @@ export class CustomScript {
});
}
private static async runMediaScript(wsPath: string, path: string | null, script: ICustomScript): Promise<void> {
if (!path) {
Notifications.error(`${script.title}: There was no folder or media path specified.`);
return;
}
return new Promise((resolve, reject) => {
window.withProgress({
location: ProgressLocation.Notification,
title: `Executing: ${script.title}`,
cancellable: false
}, async () => {
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${path}"`, (error, stdout) => {
if (error) {
Notifications.error(`${script.title}: ${error.message}`);
resolve();
return;
}
CustomScript.showOutput(stdout, script);
Dashboard.postWebviewMessage({
command: DashboardCommand.mediaUpdate
});
resolve();
return;
});
});
});
}
private static async runScript(wsPath: string, article: matter.GrayMatterFile<string> | null, contentPath: string, script: ICustomScript): Promise<string | null> {
return new Promise((resolve, reject) => {
let articleData = "";
@@ -106,7 +144,7 @@ export class CustomScript {
if (script.output === "editor") {
ContentProvider.show(output, script.title, script.outputType || "text");
} else {
window.showInformationMessage(`${script.title}: ${output}}`, 'Copy output').then(value => {
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
if (value === 'Copy output') {
vscodeEnv.clipboard.writeText(output);
}

View File

@@ -115,7 +115,7 @@ export class Extension {
const projectFolder = basename(workspace?.fsPath || "");
const paths = folders.map((folder: any) => ({
title: folder.title,
...folder,
path: `${WORKSPACE_PLACEHOLDER}${folder.fsPath.split(projectFolder).slice(1).join('')}`.split('\\').join('/')
}));

View File

@@ -1,15 +1,15 @@
import * as vscode from 'vscode';
import { Notifications } from './Notifications';
import { Uri, workspace } from 'vscode';
export class FilesHelper {
/**
* Retrieve all markdown files from the current project
*/
public static async getMdFiles(): Promise<vscode.Uri[] | null> {
const mdFiles = await vscode.workspace.findFiles('**/*.md', "**/node_modules/**,**/archetypes/**");
const markdownFiles = await vscode.workspace.findFiles('**/*.markdown', "**/node_modules/**,**/archetypes/**");
const mdxFiles = await vscode.workspace.findFiles('**/*.mdx', "**/node_modules/**,**/archetypes/**");
public static async getMdFiles(): Promise<Uri[] | null> {
const mdFiles = await workspace.findFiles('**/*.md', "**/node_modules/**,**/archetypes/**");
const markdownFiles = await workspace.findFiles('**/*.markdown', "**/node_modules/**,**/archetypes/**");
const mdxFiles = await workspace.findFiles('**/*.mdx', "**/node_modules/**,**/archetypes/**");
if (!mdFiles && !markdownFiles) {
Notifications.info(`No MD files found.`);
return null;

View File

@@ -1,3 +1,4 @@
import { Dashboard } from '../commands/Dashboard';
import { workspace } from 'vscode';
import { JsonDB } from 'node-json-db/dist/JsonDB';

View File

@@ -1,8 +1,8 @@
import { Notifications } from './Notifications';
import { commands, Uri, workspace, window } from 'vscode';
import * as vscode from 'vscode';
import { ContentType, TaxonomyType } from '../models';
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, SETTINGS_CONTENT_STATIC_FOLDER, ExtensionState } from '../constants';
import { ContentType, CustomTaxonomy, TaxonomyType } from '../models';
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM } from '../constants';
import { Folders } from '../commands/Folders';
import { join, basename } from 'path';
import { existsSync, readFileSync, watch, writeFileSync } from 'fs';
@@ -203,6 +203,32 @@ export class Settings {
await Settings.update(configSetting, options, true);
}
/**
* Update the custom taxonomy settings
*
* @param config
* @param type
* @param options
*/
public static async updateCustomTaxonomy(id: string, option: string) {
const customTaxonomies = Settings.get<CustomTaxonomy[]>(SETTING_TAXONOMY_CUSTOM, true) || [];
let taxIdx = customTaxonomies?.findIndex(o => o.id === id);
if (taxIdx === -1) {
customTaxonomies.push({
id,
options: []
} as CustomTaxonomy);
taxIdx = customTaxonomies?.findIndex(o => o.id === id);
}
customTaxonomies[taxIdx].options.push(option);
customTaxonomies[taxIdx].options = [...new Set(customTaxonomies[taxIdx].options)];
customTaxonomies[taxIdx].options = customTaxonomies[taxIdx].options.sort().filter(o => !!o);
await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
}
/**
* Promote settings from local to team level
*/

72
src/helpers/Sorting.ts Normal file
View File

@@ -0,0 +1,72 @@
import { DateHelper } from "./DateHelper";
export class Sorting {
/**
* Sort field value alphabetically
* @param property
* @returns
*/
public static alphabetically = (property: string) => {
return (a: any, b: any) => {
if (a[property] < b[property]) {
return -1;
}
if (a[property] > b[property]) {
return 1;
}
return 0;
};
};
/**
* Sort by date
* @param property
* @returns
*/
public static date = (property: string) => {
return (a: any, b: any) => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
return (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
};
};
/**
* Sort by date with a fallback
* @param property
* @returns
*/
public static dateWithFallback = (property: string, fallback: string) => {
return (a: any, b: any) => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
// Sort by date
var dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
if(dCount) return dCount;
// If there is a tie, sort by fallback property
if (a[fallback] < b[fallback]) {
return -1;
}
if (a[fallback] > b[fallback]) {
return 1;
}
return 0;
};
};
/**
* Sort by number
* @param property
* @returns
*/
public static number = (property: string) => {
return (a: any, b: any) => {
return a[property] - b[property];
};
};
}

View File

@@ -1,4 +1,6 @@
export interface ContentFolder {
title: string;
path: string;
excludeSubdir?: boolean;
}

View File

@@ -0,0 +1,7 @@
export interface CustomTaxonomyData {
id: string | undefined;
name: string | undefined;
options?: string[] | undefined;
option?: string | undefined;
}

View File

@@ -1,4 +1,3 @@
import { Stats } from "fs";
import { ISizeCalculationResult } from "image-size/dist/types/interface";
export interface MediaPaths {
@@ -11,8 +10,10 @@ export interface MediaPaths {
export interface MediaInfo {
fsPath: string;
vsPath: string | undefined;
stats: Stats | undefined;
dimensions: ISizeCalculationResult | undefined;
dimensions?: ISizeCalculationResult | undefined;
caption?: string | undefined;
alt?: string | undefined;
mtime?: Date;
ctime?: Date;
size?: number;
}

View File

@@ -1,4 +1,4 @@
import { FileType } from "vscode";
import { FileStat } from "vscode";
import { DraftField } from ".";
import { Choice } from "./Choice";
import { DashboardData } from "./DashboardData";
@@ -9,6 +9,7 @@ export interface PanelSettings {
tags: string[];
date: DateInfo;
categories: string[];
customTaxonomy: CustomTaxonomy[];
freeform: boolean;
scripts: CustomScript[];
isInitialized: boolean;
@@ -32,12 +33,13 @@ export interface ContentType {
export interface Field {
title?: string;
name: string;
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft";
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy";
choices?: string[] | Choice[];
single?: boolean;
multiple?: boolean;
isPreviewImage?: boolean;
hidden?: boolean;
taxonomyId?: string;
}
export interface DateInfo {
@@ -63,11 +65,7 @@ export interface FolderInfo {
lastModified: FileInfo[];
}
export interface FileInfo {
type: FileType;
ctime: number;
mtime: number;
size: number;
export interface FileInfo extends FileStat {
filePath: string;
fileName: string;
};
@@ -79,9 +77,21 @@ export interface CustomScript {
bulk?: boolean;
output?: "notification" | "editor";
outputType?: string;
type?: ScriptType;
}
export interface PreviewSettings {
host: string | undefined;
pathname: string | undefined;
}
export interface CustomTaxonomy {
id: string;
options: string[];
}
export enum ScriptType {
Content = "content",
MediaFolder = "mediaFolder",
MediaFile = "mediaFile"
}

4
src/models/SortOrder.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum SortOrder {
asc = 'asc',
desc = 'desc'
}

4
src/models/SortType.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum SortType {
string = 'string',
date = 'date'
}

View File

@@ -0,0 +1,9 @@
import { SortOrder, SortType } from ".";
export interface SortingSetting {
title: string;
name: string;
order: SortOrder;
type: SortType;
id?: string;
}

View File

@@ -1,9 +1,13 @@
export * from './Choice';
export * from './ContentFolder';
export * from './CustomTaxonomyData';
export * from './DashboardData';
export * from './DraftField';
export * from './Framework';
export * from './MediaPaths';
export * from './PanelSettings';
export * from './SortOrder';
export * from './SortType';
export * from './SortingSetting';
export * from './TaxonomyType';
export * from './VersionInfo';

View File

@@ -26,4 +26,6 @@ export enum CommandToCode {
updateMetadata = "update-metadata",
openDashboard = "open-dashboard",
selectImage = "select-image",
updateCustomTaxonomy = "updateCustomTaxonomy",
addToCustomTaxonomy = "addToCustomTaxonomy",
}

View File

@@ -1,5 +1,6 @@
export enum TagType {
tags = "Tags",
categories = "Categories",
keywords = "Keywords"
keywords = "Keywords",
custom = "Custom"
}

View File

@@ -19,7 +19,7 @@ const FileItem: React.FunctionComponent<IFileItemProps> = ({ name, path }: React
<li className={`file_list__items__item`}
onClick={openFile}>
{
(name.endsWith('.md') || name.endsWith('.mdx')) ? (
(name.endsWith('.md') || name.endsWith('.markdown') || name.endsWith('.mdx')) ? (
<MarkdownIcon />
) : (
<FileIcon />

View File

@@ -159,6 +159,25 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
unsetFocus={unsetFocus} />
</FieldBoundary>
);
} else if (field.type === 'taxonomy') {
const taxonomyData = settings.customTaxonomy.find(ct => ct.id === field.taxonomyId);
const selectedValues = metadata[field.name] || [];
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TagPicker
type={TagType.custom}
label={field.title || field.name}
icon={<ListUnorderedIcon />}
crntSelected={selectedValues as string[] || []}
options={taxonomyData?.options || []}
freeform={settings.freeform}
focussed={focusElm === TagType.custom}
unsetFocus={unsetFocus}
fieldName={field.name}
taxonomyId={field.taxonomyId} />
</FieldBoundary>
);
} else if (field.type === 'categories') {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>

View File

@@ -7,8 +7,8 @@ export interface ISponsorMsgProps {}
const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = (props: React.PropsWithChildren<ISponsorMsgProps>) => {
return (
<p className={`sponsor`}>
<a href={SPONSOR_LINK} title="Sponsor Front Matter">
<span>Sponsor</span> <HeartIcon className={`h-5 w-5 mr-2`} /> <span>FrontMatter</span>
<a href={SPONSOR_LINK} title="Support Front Matter">
<span>Support</span> <HeartIcon className={`h-5 w-5 mr-2`} /> <span>FrontMatter</span>
</a>
</p>
);

View File

@@ -7,9 +7,10 @@ import Downshift from 'downshift';
import { AddIcon } from './Icons/AddIcon';
import { VsLabel } from './VscodeComponents';
import { MessageHelper } from '../../helpers/MessageHelper';
import { CustomTaxonomyData } from '../../models';
export interface ITagPickerProps {
type: string;
type: TagType;
icon: JSX.Element;
label?: string;
crntSelected: string[];
@@ -18,10 +19,12 @@ export interface ITagPickerProps {
focussed: boolean;
unsetFocus: () => void;
disableConfigurable?: boolean;
fieldName?: string;
taxonomyId?: string;
}
const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsWithChildren<ITagPickerProps>) => {
const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable } = props;
const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId } = props;
const [ selected, setSelected ] = React.useState<string[]>([]);
const [ inputValue, setInputValue ] = React.useState<string>("");
const prevSelected = usePrevious(crntSelected);
@@ -43,26 +46,37 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
* @param tag
*/
const onCreate = (tag: string) => {
const cmdType = type === TagType.tags ? CommandToCode.addTagToSettings : CommandToCode.addCategoryToSettings;
MessageHelper.sendMessage(cmdType, tag);
if (type === TagType.tags) {
MessageHelper.sendMessage(CommandToCode.addTagToSettings, tag);
} else if (type === TagType.categories) {
MessageHelper.sendMessage(CommandToCode.addCategoryToSettings, tag);
} else if (type === TagType.custom) {
MessageHelper.sendMessage(CommandToCode.addToCustomTaxonomy, {
id: taxonomyId,
name: fieldName,
option: tag
} as CustomTaxonomyData);
}
};
/**
* Send an update to VSCode
* @param values
*/
const sendUpdate = (values: string[]) => {
let cmdType = CommandToCode.updateCategories;
const sendUpdate = (values: string[]) => {
if (type === TagType.tags) {
cmdType = CommandToCode.updateTags;
MessageHelper.sendMessage(CommandToCode.updateTags, values);
} else if (type === TagType.categories) {
cmdType = CommandToCode.updateCategories;
MessageHelper.sendMessage(CommandToCode.updateCategories, values);
} else if (type === TagType.keywords) {
cmdType = CommandToCode.updateKeywords;
MessageHelper.sendMessage(CommandToCode.updateKeywords, values);
} else if (type === TagType.custom) {
MessageHelper.sendMessage(CommandToCode.updateCustomTaxonomy, {
id: taxonomyId,
name: fieldName,
options: values
} as CustomTaxonomyData);
}
MessageHelper.sendMessage(cmdType, values);
};
/**

View File

@@ -14,8 +14,9 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-row';
import '@bendera/vscode-webview-elements/dist/vscode-table-cell';
import '@bendera/vscode-webview-elements/dist/vscode-collapsible';
import '@bendera/vscode-webview-elements/dist/vscode-label';
import '@bendera/vscode-webview-elements/dist/vscode-checkbox';
import '@vscode/webview-ui-toolkit/dist/esm/checkbox';
// import '@vscode/webview-ui-toolkit/dist/esm/checkbox';
const elm = document.querySelector("#app");
const version = elm?.getAttribute("data-version");

View File

@@ -9,14 +9,14 @@ export default class ContentProvider implements TextDocumentContentProvider {
return uri.query;
}
public static async show(data: string, title: string, outputType?: string) {
public static async show(data: string, title: string, outputType?: string, column: ViewColumn = ViewColumn.Beside) {
const apiData = JSON.stringify(data, null, 2);
const uri = Uri.parse(`${ContentProvider.scheme}:${title} output`);
const doc = await workspace.openTextDocument(uri);
await window.showTextDocument(doc, { preview: true, viewColumn: ViewColumn.Beside, preserveFocus: true });
await window.showTextDocument(doc, { preview: true, viewColumn: column, preserveFocus: true });
const workEdits = new WorkspaceEdit();
workEdits.replace(doc.uri, new Range(new Position(0, 0), new Position(doc.lineCount, 0)), data);