Compare commits

..

107 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
Elio Struyf
9618a89528 Merge pull request #169 from estruyf/dev 2021-11-05 09:25:09 +01:00
Elio Struyf
14f0af2754 Updated changelog 2021-11-05 09:24:26 +01:00
Elio Struyf
ebe248670d 5.4.0 2021-11-03 12:00:56 +01:00
Elio Struyf
511960c4a9 #167 - Allow to set a preview path per content type 2021-11-03 12:00:49 +01:00
Elio Struyf
31fd1f93ce Url joining 2021-11-02 16:32:36 +01:00
Elio Struyf
6625b69170 #166 - Filename and folder logic for slug 2021-11-02 13:48:52 +01:00
Elio Struyf
9e8533fbb8 Updated changelog 2021-11-02 11:58:43 +01:00
Elio Struyf
9c9cbb7dcb #166 - Add preview button to panel when no markdown file is opened 2021-11-02 11:56:40 +01:00
Elio Struyf
079a13e161 Merge pull request #164 from estruyf/dev
Merge for 5.3.1
2021-10-29 10:25:17 +02:00
Elio Struyf
69c1e587d0 5.3.1 2021-10-29 10:18:31 +02:00
Elio Struyf
3996252531 #163 - Set workspace state instead of global 2021-10-29 10:18:20 +02:00
Elio Struyf
4fddda65e6 Merge pull request #162 from estruyf/dev
5.3.0 merge
2021-10-28 15:33:06 +02:00
Elio Struyf
5916344092 added release notes 2021-10-28 12:00:24 +02:00
Elio Struyf
b96722dd69 #158 - Update boolean field check 2021-10-27 11:42:57 +02:00
Elio Struyf
263ccab311 5.3.0 2021-10-26 15:59:45 +02:00
Elio Struyf
3571af82c7 Updated readme 2021-10-26 15:59:31 +02:00
Elio Struyf
c60520c0ff Fix replace in action button 2021-10-26 15:28:01 +02:00
Elio Struyf
b473431eae #159 - SEO enhancements 2021-10-26 14:42:01 +02:00
Elio Struyf
cbf434f741 Updated changelog 2021-10-25 12:54:19 +02:00
Elio Struyf
04c401207f #158 - New draft field setting + choice implementation 2021-10-25 12:53:04 +02:00
Elio Struyf
7291e6aac6 Fix tag replacement 2021-10-25 10:24:08 +02:00
Elio Struyf
a7aab96f0e Fix time formatting 2021-10-25 10:11:31 +02:00
Elio Struyf
f500749644 Fix slug punctuation 2021-10-20 15:21:27 +02:00
Elio Struyf
47e59bc54c Merge pull request #157 from estruyf/dev
Merge for v5.2.0 release
2021-10-19 15:41:12 +02:00
Elio Struyf
8902e25021 Release 5.2.0 2021-10-19 15:40:17 +02:00
Elio Struyf
33093e1eb4 Change useEffect order 2021-10-19 14:56:49 +02:00
Elio Struyf
d36178c44f Added component display names for better error reporting 2021-10-19 14:46:47 +02:00
Elio Struyf
15b09ccc75 #156 - Fix for media files in new folder 2021-10-19 11:04:05 +02:00
Elio Struyf
dffa6c87a0 Fix for rendering less hooks 2021-10-19 08:45:32 +02:00
Elio Struyf
c4a1caee09 Remove console 2021-10-18 14:33:32 +02:00
Elio Struyf
1d9f07b86d #155 - Fallback image 2021-10-18 14:29:54 +02:00
Elio Struyf
a794a95bb8 #154 - Implementation of the bulk script execution 2021-10-18 10:13:34 +02:00
Elio Struyf
40a56f6057 Fix SEO keyword check 2021-10-17 19:51:41 +02:00
Elio Struyf
82353f7b64 validate date 2021-10-17 15:31:19 +02:00
Elio Struyf
82a22da90a check the value of remove puntuation 2021-10-17 15:28:05 +02:00
Elio Struyf
380e40ea05 update changelog 2021-10-17 15:25:22 +02:00
Elio Struyf
2bedb23341 #153 - Support old date-fns format 2021-10-17 15:24:26 +02:00
Elio Struyf
1110b76364 Update changelog 2021-10-15 16:14:42 +02:00
Elio Struyf
d19e632f80 #151 #152 - Framework detection + preset public folder 2021-10-15 16:11:59 +02:00
Elio Struyf
4e040b5f7a 5.2.0 2021-10-15 14:02:08 +02:00
Elio Struyf
7a2a0934c2 Merge pull request #150 from estruyf/dev
Merge for 5.1.1
2021-10-14 16:24:29 +02:00
Elio Struyf
d3eb7b223c 5.1.1 2021-10-14 16:23:51 +02:00
Elio Struyf
f74eec954f #149 - Fix keywords 2021-10-14 16:23:28 +02:00
141 changed files with 3033 additions and 691 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,91 @@
# 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
- [#166](https://github.com/estruyf/vscode-front-matter/issues/166): Added preview button to the panel base view
- [#167](https://github.com/estruyf/vscode-front-matter/issues/167): Allow to set the preview path per content type
## [5.3.1] - 2021-10-29
### 🐞 Fixes
- [#163](https://github.com/estruyf/vscode-front-matter/issues/163): Setting workspace state instead of global state for the media view
## [5.3.0] - 2021-10-28 - [Release Notes](https://beta.frontmatter.codes/updates/v5.3.0)
### 🎨 Enhancements
- [#158](https://github.com/estruyf/vscode-front-matter/issues/158): Add support for non-boolean draft/publish status fields
- [#159](https://github.com/estruyf/vscode-front-matter/issues/159): Enhancements to SEO checks: Slug check, keyword details, more article information
### 🐞 Fixes
- Value check when generating slug from title
- Fix for date time formatting with `DD` and `YYYY` tokens
- Fix in tag space replacing when object is passed
## [5.2.0] - 2021-10-19
### 🎨 Enhancements
- [#151](https://github.com/estruyf/vscode-front-matter/issues/151): Detect which site-generator or framework is used
- [#152](https://github.com/estruyf/vscode-front-matter/issues/152): Automatically set setting based on the used site-generator or framework
- [#154](https://github.com/estruyf/vscode-front-matter/issues/154): Bulk script support added
- [#155](https://github.com/estruyf/vscode-front-matter/issues/155): Fallback image added for the images shown in the editor panel
### 🐞 Fixes
- [#153](https://github.com/estruyf/vscode-front-matter/issues/153): Support old date formatting for date-fns
- [#156](https://github.com/estruyf/vscode-front-matter/issues/156): Fix for uploading media files into a new folder
## [5.1.1] - 2021-10-14
### 🐞 Fixes
- [#149](https://github.com/estruyf/vscode-front-matter/issues/149): Fix panel rendering when incorrect type for keywords is provided
## [5.1.0] - 2021-10-13
### 🎨 Enhancements

View File

@@ -48,6 +48,10 @@ Our main extension features are:
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 5**
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
**Version 4**
Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
@@ -107,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

@@ -46,6 +46,10 @@ Our main extension features are:
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 5**
The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0).
**Version 4**
Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0).
@@ -105,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

View File

@@ -356,8 +356,18 @@
text-transform: capitalize;
}
.table__cell__seo_details {
padding: 10px;
}
.table__cell__validation {
text-align: center;
text-align: left;
}
.table__cell__validation div {
display: flex;
align-items: center;
padding: 2px 0;
}
.table__cell__validation .valid {
@@ -368,6 +378,15 @@
color: #E6AF2E;
}
.table__cell__validation div span + span {
margin-left: .5rem;
}
.seo__status__note {
font-size: 10px;
padding: 3px 0;
}
/* Fields */
.field__toggle {
position: relative;

195
package-lock.json generated
View File

@@ -1,145 +1,9 @@
{
"name": "vscode-front-matter-beta",
"version": "5.1.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",
@@ -6386,6 +6199,12 @@
}
}
},
"url-join-ts": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/url-join-ts/-/url-join-ts-1.0.5.tgz",
"integrity": "sha512-u+5gi7JyOwhj58ZKwkmkzFGHuepTpmwjqfUDGVjsJJstsCz63CJAINixgJaDcMbmuyWPJIxbtBpIfaDgOQ9KMQ==",
"dev": true
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.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.1.0",
"version": "5.7.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -97,6 +97,62 @@
"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)",
"default": {
"name": "draft",
"type": "boolean"
},
"properties": {
"type": {
"type": "string",
"enum": [
"boolean",
"choice"
],
"description": ""
},
"name": {
"type": "string",
"description": "Name of the field to use"
},
"choices": {
"type": "array",
"description": "List of choices for the field",
"items": {
"type": [
"string"
]
}
}
},
"additionalProperties": false,
"required": [
"type",
"name"
],
"scope": "Content"
},
"frontMatter.content.fmHighlight": {
"type": "boolean",
"default": true,
@@ -117,6 +173,11 @@
"path": {
"type": "string",
"description": "Path of the folder"
},
"excludeSubdir": {
"type": "boolean",
"default": false,
"description": "Exclude sub-directories"
}
},
"additionalProperties": false,
@@ -133,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": [],
@@ -151,6 +264,32 @@
"nodeBin": {
"type": "string",
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use."
},
"bulk": {
"type": "boolean",
"description": "Run the script for all content files"
},
"output": {
"type": "string",
"enum": [
"editor",
"notification"
],
"description": "Define where you want to output your script output. Default is a notification, but you can specify to show it in an editor panel."
},
"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,
@@ -180,6 +319,23 @@
"markdownDescription": "Specify if you want to open the dashboard when you start VS Code. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.openonstart)",
"scope": "Dashboard"
},
"frontMatter.framework.id": {
"type": "string",
"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,
@@ -198,6 +354,12 @@
"markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.preview.pathname)",
"scope": "Site preview"
},
"frontMatter.site.baseURL": {
"type": "string",
"default": "",
"markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)",
"scope": "Site"
},
"frontMatter.taxonomy.alignFilename": {
"type": "boolean",
"default": false,
@@ -251,8 +413,10 @@
"boolean",
"image",
"choice",
"taxonomy",
"tags",
"categories"
"categories",
"draft"
],
"description": "Define the type of field"
},
@@ -306,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"
]
}
}
]
}
},
@@ -319,6 +518,14 @@
"type": "boolean",
"default": false,
"description": "Specify if you want to create a folder when creating new content."
},
"previewPath": {
"type": [
"null",
"string"
],
"default": null,
"description": "Defines a custom preview path for the content type."
}
},
"additionalProperties": false,
@@ -372,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",
@@ -435,6 +668,12 @@
"markdownDescription": "Specifies the optimal description length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seodescriptionlength)",
"scope": "Taxonomy"
},
"frontMatter.taxonomy.seoSlugLength": {
"type": "number",
"default": 75,
"markdownDescription": "Specifies the optimal slug length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seoSlugLength)",
"scope": "Taxonomy"
},
"frontMatter.taxonomy.seoTitleLength": {
"type": "number",
"default": 60,
@@ -534,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",
@@ -550,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",
@@ -571,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": [
@@ -631,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": [
@@ -705,11 +1167,10 @@
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"url-join-ts": "^1.0.5",
"wc-react": "github:estruyf/wc-react",
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
},
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.40"
}
"dependencies": {}
}

View File

@@ -5,10 +5,12 @@ import { format } from "date-fns";
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
import matter = require('gray-matter');
import { Notifications } from '../helpers/Notifications';
import { extname, basename } from 'path';
import { extname, basename, parse, dirname } from 'path';
import { COMMAND_NAME, DefaultFields } from '../constants';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DateHelper } from '../helpers/DateHelper';
import { parseWinPath } from '../helpers/parseWinPath';
export class Article {
@@ -171,7 +173,7 @@ export class Article {
let newFileName = `${slugName}${ext}`;
if (filePrefix && typeof filePrefix === "string") {
newFileName = `${format(new Date(), filePrefix)}-${newFileName}`;
newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix) as string)}-${newFileName}`;
}
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
@@ -182,7 +184,7 @@ export class Article {
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
overwrite: false
});
} catch (e) {
} catch (e: any) {
Notifications.error(`Failed to rename file: ${e?.message || e}`);
}
}
@@ -190,6 +192,31 @@ export class Article {
}
}
/**
* Retrieve the slug from the front matter
*/
public static getSlug() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const file = parseWinPath(editor.document.fileName);
if (!file.endsWith(`.md`) && !file.endsWith(`.markdown`) && !file.endsWith(`.mdx`)) {
return;
}
const parsedFile = parse(file);
if (parsedFile.name.toLowerCase() !== "index") {
return parsedFile.name;
}
const folderName = basename(dirname(file));
return folderName;
}
/**
* Toggle the page its draft mode
*/
@@ -243,7 +270,7 @@ export class Article {
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
if (dateFormat && typeof dateFormat === "string") {
return format(dateValue, dateFormat);
return format(dateValue, DateHelper.formatUpdate(dateFormat) as string);
} else {
return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString();
}

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 } 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 { 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,9 +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;
@@ -37,7 +42,7 @@ export class Dashboard {
return Dashboard._viewData;
}
/** 
/**
* Init the dashboard
*/
public static async init() {
@@ -60,6 +65,8 @@ export class Dashboard {
} else {
Dashboard.create();
}
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, true);
}
/**
@@ -77,6 +84,10 @@ export class Dashboard {
Dashboard.webview.reveal();
}
}
public static close() {
Dashboard.webview?.dispose();
}
/**
* Create the dashboard webview
@@ -103,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) => {
@@ -160,10 +174,10 @@ export class Dashboard {
}
break;
case DashboardMessage.setPageViewType:
Extension.getInstance().setState(ExtensionState.PagesView, msg.data);
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);
@@ -187,6 +201,17 @@ export class Dashboard {
case DashboardMessage.createMediaFolder:
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.data);
break;
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;
}
});
}
@@ -202,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
@@ -257,6 +296,7 @@ export class Dashboard {
private static async getSettings() {
const ext = Extension.getInstance();
const wsFolder = Folders.getWorkspaceFolder();
const isInitialized = await Template.isInitialized();
Dashboard.postWebviewMessage({
command: DashboardCommand.settings,
@@ -265,19 +305,52 @@ export class Dashboard {
wsFolder: wsFolder ? wsFolder.fsPath : '',
staticFolder: SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER),
folders: Folders.get(),
initialized: await Template.isInitialized(),
initialized: isInitialized,
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
openOnStart: SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<ViewType | undefined>(ExtensionState.PagesView),
pageViewType: await ext.getState<ViewType | undefined>(ExtensionState.PagesView, "workspace"),
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
contentFolders: Folders.get().map(f => f.path),
draftField: SettingsHelper.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
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
});
}
/**
* Set the current site-generator or framework + related settings
* @param frameworkId
*/
private static setFramework(frameworkId: string | null) {
SettingsHelper.update(SETTINGS_FRAMEWORK_ID, frameworkId, true);
if (frameworkId) {
const allFrameworks = FrameworkDetector.getAll();
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
if (framework) {
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true);
} else {
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true);
}
}
}
/**
* Update a setting from the dashboard
*/
@@ -289,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);
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
@@ -316,19 +392,28 @@ export class Dashboard {
selectedFolder = '';
}
const relSelectedFolderPath = selectedFolder ? selectedFolder.substring((parseWinPath(wsFolder?.fsPath || "")).length + 1) : '';
let relSelectedFolderPath = selectedFolder;
const parsedPath = parseWinPath(wsFolder?.fsPath || "");
if (selectedFolder && selectedFolder.startsWith(parsedPath)) {
relSelectedFolderPath = selectedFolder.replace(parsedPath, '');
}
if (relSelectedFolderPath.startsWith('/')) {
relSelectedFolderPath = relSelectedFolderPath.substring(1);
}
let allMedia: MediaInfo[] = [];
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];
}
@@ -339,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);
@@ -371,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[] = [];
@@ -405,19 +491,47 @@ export class Dashboard {
}
// Store the last opened folder
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder);
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
*/
@@ -434,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);
@@ -446,7 +560,7 @@ export class Dashboard {
fmModified: file.mtime,
fmFilePath: file.filePath,
fmFileName: file.fileName,
fmDraft: article?.data.draft ? "Draft" : "Published",
fmDraft: ContentType.getDraftStatus(article?.data),
fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null,
// Make sure these are always set
title: article?.data.title,
@@ -549,9 +663,9 @@ export class Dashboard {
if (imgData) {
writeFileSync(staticPath, imgData.data);
Notifications.info(`File ${fileName} uploaded to: ${staticFolder}/${folder}`);
Notifications.info(`File ${fileName} uploaded to: ${folder}`);
const folderPath = `${staticFolder}/${folder}`;
const folderPath = `${folder}`;
if (Dashboard.timers[folderPath]) {
clearTimeout(Dashboard.timers[folderPath]);
delete Dashboard.timers[folderPath];
@@ -598,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);
}

View File

@@ -5,6 +5,9 @@ import { commands, env, Uri, ViewColumn, window } from "vscode";
import { Settings } from '../helpers';
import { PreviewSettings } from '../models';
import { format } from 'date-fns';
import { DateHelper } from '../helpers/DateHelper';
import { Article } from '.';
import { urlJoin } from 'url-join-ts';
export class Preview {
@@ -31,12 +34,25 @@ export class Preview {
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
let slug = article?.data ? article.data.slug : "";
if (settings.pathname) {
let pathname = settings.pathname;
if (article?.data) {
const contentType = ArticleHelper.getContentType(article.data);
if (contentType && contentType.previewPath) {
pathname = contentType.previewPath;
}
}
if (!slug) {
slug = Article.getSlug();
}
if (pathname) {
const articleDate = ArticleHelper.getDate(article);
try {
slug = join(format(articleDate || new Date(), settings.pathname), slug);
slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string), slug);
} catch (error) {
slug = join(settings.pathname, slug);
slug = join(pathname, slug);
}
}
@@ -112,9 +128,9 @@ export class Preview {
</head>
<body>
<div class="slug">
<input type="text" value="${join(localhostUrl.toString(), slug)}" disabled />
<input type="text" value="${urlJoin(localhostUrl.toString(), slug || '')}" disabled />
</div>
<iframe src="${join(localhostUrl.toString(), slug)}" >
<iframe src="${urlJoin(localhostUrl.toString(), slug || '')}" >
</body>
</html>`;
}
@@ -131,4 +147,4 @@ export class Preview {
pathname
};
}
}
}

View File

@@ -3,6 +3,7 @@ import * as vscode from 'vscode';
import { ArticleHelper, SeoHelper, Settings } from '../helpers';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DefaultFields } from '../constants';
import { ContentType } from '../helpers/ContentType';
export class StatusListener {
@@ -15,6 +16,11 @@ export class StatusListener {
public static async verify(frontMatterSB: vscode.StatusBarItem, collection: vscode.DiagnosticCollection) {
const draftMsg = "in draft";
const publishMsg = "to publish";
const draft = ContentType.getDraftField();
if (!draft || draft.type !== "boolean") {
frontMatterSB.hide();
}
let editor = vscode.window.activeTextEditor;
if (editor && ArticleHelper.isMarkdownFile()) {

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

@@ -5,6 +5,7 @@ export const DEFAULT_CONTENT_TYPE_NAME = 'default';
export const DEFAULT_CONTENT_TYPE: ContentType = {
"name": "default",
"pageBundle": false,
"previewPath": null,
"fields": [
{
"title": "Title",
@@ -22,14 +23,14 @@ export const DEFAULT_CONTENT_TYPE: ContentType = {
"type": "datetime"
},
{
"title": "Article preview",
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
"type": "draft"
},
{
"title": "Tags",

View File

@@ -2,5 +2,6 @@
export const DefaultFields = {
PublishingDate: `date`,
LastModified: `lastmod`,
Description: `description`
Description: `description`,
Slug: `slug`
};

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

@@ -0,0 +1,21 @@
export const FrameworkDetectors = [
{
"framework": {"name": "gatsby", "dist": "public", "static": "static", "build": "gatsby build"},
"requiredFiles": ["gatsby-config.js"],
"requiredDependencies": ["gatsby"]
},
{
"framework": {"name": "hugo", "dist": "public", "static": "static", "build": "hugo"},
"requiredFiles": ["config.toml", "config.yaml", "config.yml"]
},
{
"framework": {"name": "next", "dist": ".next", "static": "public", "build": "next build"},
"requiredFiles": ["next.config.js"],
"requiredDependencies": ["next"]
},
{
"framework": {"name": "nuxt", "dist": "dist", "static": "static", "build": "nuxt"},
"requiredFiles": ["nuxt.config.js"],
"requiredDependencies": ["nuxt"]
}
];

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";
@@ -20,6 +22,7 @@ export const SETTING_REMOVE_QUOTES = "taxonomy.noPropertyValueQuotes";
export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType";
export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength";
export const SETTING_SEO_SLUG_LENGTH = "taxonomy.seoSlugLength";
export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength";
export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh";
export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
@@ -38,10 +41,20 @@ export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTINGS_CONTENT_SORTING = "content.sorting";
export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTINGS_CONTENT_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";
export const SETTINGS_FRAMEWORK_ID = "framework.id";
export const SETTING_SITE_BASEURL = "site.baseURL";
/**
* @deprecated
*/

View File

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

View File

@@ -17,5 +17,8 @@ export enum DashboardMessage {
deleteMedia = 'deleteMedia',
insertPreviewImage = 'insertPreviewImage',
updateMediaMetadata = 'updateMediaMetadata',
createMediaFolder = 'createMediaFolder'
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

@@ -41,7 +41,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<Status draft={draft} />
<DateField value={date} />
</div>
@@ -64,7 +64,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
<DateField value={date} />
</div>
<div className="col-span-2">
<Status draft={!!draft} />
<Status draft={draft} />
</div>
</button>
</li>

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

@@ -1,7 +1,7 @@
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Tab } from '../constants/Tab';
import { TabAtom } from '../state';
import { SettingsAtom, TabAtom } from '../state';
export interface INavigationProps {
totalPages: number;
@@ -15,19 +15,47 @@ export const tabs = [
export const Navigation: React.FunctionComponent<INavigationProps> = ({totalPages}: React.PropsWithChildren<INavigationProps>) => {
const [ crntTab, setCrntTab ] = useRecoilState(TabAtom);
const settings = useRecoilValue(SettingsAtom);
return (
<nav className="flex-1 -mb-px flex space-x-6 xl:space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<button
key={tab.name}
className={`${tab.id === crntTab ? `border-teal-900 dark:border-teal-300 text-teal-900 dark:text-teal-300` : `border-transparent text-gray-500 dark:text-whisper-600 hover:text-gray-700 dark:hover:text-whisper-700 hover:border-gray-300 dark:hover:border-whisper-500`} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
aria-current={tab.id === crntTab ? 'page' : undefined}
onClick={() => setCrntTab(tab.id)}
>
{tab.name}{(tab.id === crntTab && totalPages) ? ` (${totalPages})` : ''}
</button>
))}
{
settings?.draftField?.type === "boolean" ? (
tabs.map((tab) => (
<button
key={tab.name}
className={`${tab.id === crntTab ? `border-teal-900 dark:border-teal-300 text-teal-900 dark:text-teal-300` : `border-transparent text-gray-500 dark:text-whisper-600 hover:text-gray-700 dark:hover:text-whisper-700 hover:border-gray-300 dark:hover:border-whisper-500`} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
aria-current={tab.id === crntTab ? 'page' : undefined}
onClick={() => setCrntTab(tab.id)}
>
{tab.name}{(tab.id === crntTab && totalPages) ? ` (${totalPages})` : ''}
</button>
))
) : (
<>
<button
className={`${tabs[0].id === crntTab ? `border-teal-900 dark:border-teal-300 text-teal-900 dark:text-teal-300` : `border-transparent text-gray-500 dark:text-whisper-600 hover:text-gray-700 dark:hover:text-whisper-700 hover:border-gray-300 dark:hover:border-whisper-500`} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
aria-current={tabs[0].id === crntTab ? 'page' : undefined}
onClick={() => setCrntTab(tabs[0].id)}
>
{tabs[0].name}{(tabs[0].id === crntTab && totalPages) ? ` (${totalPages})` : ''}
</button>
{
settings?.draftField?.choices?.map((value, idx) => (
<button
key={`${value}-${idx}`}
className={`${value === crntTab ? `border-teal-900 dark:border-teal-300 text-teal-900 dark:text-teal-300` : `border-transparent text-gray-500 dark:text-whisper-600 hover:text-gray-700 dark:hover:text-whisper-700 hover:border-gray-300 dark:hover:border-whisper-500`} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm first-letter:uppercase`}
aria-current={value === crntTab ? 'page' : undefined}
onClick={() => setCrntTab(value)}
>
{value}{(value === crntTab && totalPages) ? ` (${totalPages})` : ''}
</button>
))
}
</>
)
}
</nav>
);
};

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,10 +1,22 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { SettingsAtom } from '../state';
export interface IStatusProps {
draft: boolean;
draft: boolean | string;
}
export const Status: React.FunctionComponent<IStatusProps> = ({draft}: React.PropsWithChildren<IStatusProps>) => {
const settings = useRecoilValue(SettingsAtom);
if (settings?.draftField && settings.draftField.type === "choice") {
if (draft) {
return <span className={`inline-block px-2 py-1 leading-none rounded-full font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 bg-teal-500`}>{draft}</span>;
} else {
return null;
}
}
return (
<span className={`inline-block px-2 py-1 leading-none rounded-full font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draft ? "bg-red-500" : "bg-teal-500"}`}>{draft ? "Draft" : "Published"}</span>
);

View File

@@ -4,22 +4,17 @@ import { Status } from '../../models/Status';
export interface IStepProps {
name: string;
description: string;
description: JSX.Element;
status: Status;
showLine: boolean;
onClick?: () => void;
onClick?: () => void | undefined;
}
export const Step: React.FunctionComponent<IStepProps> = ({name, description, status, showLine, onClick}: React.PropsWithChildren<IStepProps>) => {
return (
<>
{
showLine ? (
<div className={`-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full ${status === Status.Completed ? "bg-teal-600" : "bg-gray-300"}`} aria-hidden="true" />
) : null
}
<button className={`relative flex items-start group text-left ${onClick ? "" : "cursor-default"}`} onClick={() => { if (onClick) { onClick(); } }} disabled={!onClick}>
const renderChildren = () => {
return (
<>
{
status === Status.NotStarted && (
<span className="h-9 flex items-center" aria-hidden="true">
@@ -52,9 +47,31 @@ export const Step: React.FunctionComponent<IStepProps> = ({name, description, st
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase text-vulcan-500 dark:text-whisper-500">{name}</span>
<span className="text-sm text-vulcan-400 dark:text-whisper-600" dangerouslySetInnerHTML={{__html: description}} />
<div className="text-sm text-vulcan-400 dark:text-whisper-600">{description}</div>
</span>
</button>
</>
);
};
return (
<>
{
showLine ? (
<div className={`-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full ${status === Status.Completed ? "bg-teal-600" : "bg-gray-300"}`} aria-hidden="true" />
) : null
}
{
onClick ? (
<button className={`relative flex items-start group text-left`} onClick={() => { if (onClick) { onClick(); } }} disabled={!onClick}>
{renderChildren()}
</button>
) : (
<div className="relative flex items-start group text-left">
{renderChildren()}
</div>
)
}
</>
);
};

View File

@@ -4,36 +4,99 @@ import { DashboardMessage } from '../../DashboardMessage';
import { Settings } from '../../models/Settings';
import { Status } from '../../models/Status';
import { Step } from './Step';
import { useState } from 'react';
import { Menu } from '@headlessui/react';
import { MenuItem } from '../Menu';
import { Framework } from '../../../models';
import { ChevronDownIcon } from '@heroicons/react/outline';
import { FrameworkDetectors } from '../../../constants/FrameworkDetectors';
export interface IStepsToGetStartedProps {
settings: Settings;
}
export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps> = ({settings}: React.PropsWithChildren<IStepsToGetStartedProps>) => {
const [framework, setFramework] = useState<string | null>(null);
const frameworks: Framework[] = FrameworkDetectors.map((detector: any) => detector.framework);
const setFrameworkAndSendMessage = (framework: string) => {
setFramework(framework);
Messenger.send(DashboardMessage.setFramework, framework);
}
const steps = [
{
name: 'Initialize project',
description: 'Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. <b>Start by clicking on this action</b>.',
description: <>Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. <b>Start by clicking on this action</b>.</>,
status: settings.initialized ? Status.Completed : Status.NotStarted,
onClick: settings.initialized ? undefined : () => { Messenger.send(DashboardMessage.initializeProject); }
},
{
name: 'Framework presets',
description: (
<div>
<div>Select your site-generator or framework to prefill some of the recommended settings.</div>
<Menu as="div" className="relative inline-block text-left mt-4">
<div>
<Menu.Button className="group flex justify-center text-vulcan-500 hover:text-vulcan-600 dark:text-whisper-500 dark:hover:text-whisper-600 p-2 rounded-md border border-vulcan-400 dark:border-white">
{framework ? framework : 'Select your framework'}
<ChevronDownIcon
className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500 dark:text-whisper-600 dark:group-hover:text-whisper-700"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Menu.Items className={`w-40 origin-top-left absolute left-0 z-10 mt-2 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`}>
<div className="py-1">
<MenuItem
title={`other`}
value={`other`}
isCurrent={!framework}
onClick={(value) => setFrameworkAndSendMessage(value)} />
<hr />
{frameworks.map((f) => (
<MenuItem
key={f.name}
title={f.name}
value={f.name}
isCurrent={f.name === framework}
onClick={(value) => setFrameworkAndSendMessage(value)} />
))}
</div>
</Menu.Items>
</Menu>
</div>
),
status: settings.crntFramework ? Status.Completed : Status.NotStarted,
onClick: undefined
},
{
name: 'Register content folders (manual action)',
description: 'Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting <b>register folder</b>. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.',
description: <>Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting <b>register folder</b>. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.</>,
status: settings.folders && settings.folders.length > 0 ? Status.Completed : Status.NotStarted
},
{
name: 'Show the dashboard',
description: 'Once both actions are completed, click on this action to load the dashboard.',
description: <>Once both actions are completed, click on this action to load the dashboard.</>,
status: (settings.initialized && settings.folders && settings.folders.length > 0) ? Status.Active : Status.NotStarted,
onClick: (settings.initialized && settings.folders && settings.folders.length > 0) ? () => { Messenger.send(DashboardMessage.reload); } : undefined
}
];
React.useEffect(() => {
if (settings.crntFramework) {
setFramework(settings.crntFramework);
}
}, [settings.crntFramework]);
return (
<nav aria-label="Progress">
<ol role="list" className="overflow-hidden">
<ol role="list">
{steps.map((step, stepIdx) => (
<li key={step.name} className={`${stepIdx !== steps.length - 1 ? 'pb-10' : ''} relative`}>
<Step name={step.name} description={step.description} status={step.status} showLine={stepIdx !== steps.length - 1} onClick={step.onClick} />

View File

@@ -33,11 +33,11 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({set
</h1>
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
Thank you for taking the time to test out Front Matter!
Thank you for using Front Matter!
</p>
<p className="mt-3 text-base text-vulcan-300 dark:text-whisper-700 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
We try to aim to make Front Matter as easy to use as possible, but if you have any questions, please don't hesitate to reach out to us on GitHub.
We try to aim to make Front Matter as easy to use as possible, but if you have any questions or suggestions. Please don't hesitate to reach out to us on GitHub.
</p>
<div className="mt-5 w-full sm:mx-auto sm:max-w-lg lg:ml-0">

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, 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,14 +18,26 @@ 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);
const category = useRecoilValue(CategorySelector);
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;
if (search) {
@@ -34,23 +48,49 @@ export default function usePages(pages: Page[]) {
// Filter the pages
let pagesToShow: Page[] = Object.assign([], searchedPages);
if (tab === Tab.Published) {
pagesToShow = searchedPages.filter(page => !page.draft);
} else if (tab === Tab.Draft) {
pagesToShow = searchedPages.filter(page => !!page.draft);
if (draftField && draftField.type === 'choice') {
if (tab !== Tab.All) {
pagesToShow = pagesToShow.filter(page => page.fmDraft === tab);
} else {
pagesToShow = searchedPages;
}
} else {
pagesToShow = searchedPages;
const draftFieldName = draftField?.name || "draft";
if (tab === Tab.Published) {
pagesToShow = searchedPages.filter(page => !page[draftFieldName]);
} else if (tab === Tab.Draft) {
pagesToShow = searchedPages.filter(page => !!page[draftFieldName]);
} else {
pagesToShow = searchedPages;
}
}
// 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();
}
}
@@ -69,7 +109,7 @@ export default function usePages(pages: Page[]) {
}
setPageItems(pagesSorted);
}, [ pages, tab, sorting, folder, search, tag, category ]);
}, [ settings?.draftField, pages, tab, sorting, folder, search, tag, category ]);
return {
pageItems

View File

@@ -1,7 +1,8 @@
import { VersionInfo } from '../../models/VersionInfo';
import { ViewType } from '../state';
import { ContentFolder } from '../../models/ContentFolder';
import { ContentType } from '../../models';
import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models';
import { SortingOption } from './SortingOption';
export interface Settings {
beta: boolean;
@@ -16,5 +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,7 +1,7 @@
import { atom } from 'recoil';
import { Tab } from '../../constants/Tab';
export const TabAtom = atom<Tab>({
export const TabAtom = atom<Tab | string>({
key: 'TabAtom',
default: Tab.All
});

View File

@@ -1,21 +1,19 @@
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, SETTINGS_CONTENT_STATIC_FOLDER, 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 } 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 } from '../models/PanelSettings';
import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
import { ArticleHelper, Settings } from "../helpers";
import { Command } from "../panelWebView/Command";
import { CommandToCode } from '../panelWebView/CommandToCode';
import { Article } from '../commands';
import { TagType } from '../panelWebView/TagType';
import { TaxonomyType } from '../models';
import { CustomTaxonomyData, DraftField, ScriptType, TaxonomyType } from '../models';
import { exec } from 'child_process';
import * as path from 'path';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Content } from 'mdast';
import { Notifications } from '../helpers/Notifications';
import { COMMAND_NAME } from '../constants/Extension';
import { 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';
@@ -23,6 +21,8 @@ import { WebviewHelper } from '@estruyf/vscode';
import { Extension } from '../helpers/Extension';
import { Dashboard } from '../commands/Dashboard';
import { ImageHelper } from '../helpers/ImageHelper';
import { CustomScript } from '../helpers/CustomScript';
import { Link, Parent } from 'mdast-util-from-markdown/lib';
const FILE_LIMIT = 10;
@@ -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") {
@@ -270,6 +277,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
}
// Check slug
if (!updatedMetadata[DefaultFields.Slug]) {
const slug = Article.getSlug();
if (slug) {
updatedMetadata[DefaultFields.Slug] = slug;
}
}
this.postWebviewMessage({ command: Command.metadata, data: {
...updatedMetadata
@@ -352,38 +368,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* @param msg
*/
private runCustomScript(msg: { command: string, data: any}) {
const scripts: CustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
if (msg?.data?.title && msg?.data?.script && scripts) {
const customScript = scripts.find((s: CustomScript) => s.title === msg.data.title);
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
if (customScript?.script && customScript?.title) {
const editor = window.activeTextEditor;
if (!editor) return;
const article = ArticleHelper.getFrontMatter(editor);
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const wsPath = wsFolder.fsPath;
let articleData = `'${JSON.stringify(article?.data)}'`;
if (os.type() === "Windows_NT") {
articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`;
}
exec(`${customScript.nodeBin || "node"} ${path.join(wsPath, msg.data.script)} "${wsPath}" "${editor?.document.uri.fsPath}" ${articleData}`, (error, stdout) => {
if (error) {
Notifications.error(`${msg?.data?.title}: ${error.message}`);
return;
}
window.showInformationMessage(`${msg?.data?.title}: ${stdout || "Executed your custom script."}`, 'Copy output').then(value => {
if (value === 'Copy output') {
vscodeEnv.clipboard.writeText(stdout);
}
});
});
}
CustomScript.run(customScript);
}
}
}
@@ -407,6 +397,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
data: {
seo: {
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
@@ -421,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,
@@ -430,7 +422,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
preview: Preview.getSettings(),
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
dashboardViewData: Dashboard.viewData
dashboardViewData: Dashboard.viewData,
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD)
} as PanelSettings
});
}
@@ -479,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
@@ -502,6 +529,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* Get article details
*/
private getArticleDetails() {
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
const editor = window.activeTextEditor;
if (!editor) {
return null;
@@ -518,13 +546,36 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
const mdTree = fromMarkdown(content);
const headings = mdTree.children.filter(node => node.type === 'heading').length;
const paragraphs = mdTree.children.filter(node => node.type === 'paragraph').length;
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
const headings = elms.filter(node => node.type === 'heading');
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
const images = elms.filter(node => node.type === 'image').length;
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
let externalLinks = links.filter(link => link.startsWith('http'));
if (baseUrl) {
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
}
const headers = [];
for (const header of headings) {
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
if (text) {
headers.push(text);
}
}
const wordCount = this.wordCount(0, mdTree);
return {
headings,
headings: headings.length,
headingsText: headers,
paragraphs,
images,
internalLinks,
externalLinks: externalLinks.length,
wordCount,
content: article.content
};
@@ -533,6 +584,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
return null;
}
private getAllElms(node: Content | any, allElms?: any[]): any[] {
if (!allElms) {
allElms = [];
}
if (node.children?.length > 0) {
for (const child of node.children) {
allElms.push(Object.assign({}, child));
this.getAllElms(child, allElms);
}
}
return allElms;
}
private counts(acc: any, node: any) {
// add 1 to an initial or existing value
acc[node.type] = (acc[node.type] || 0) + 1;

View File

@@ -15,6 +15,9 @@ import { Extension } from './helpers/Extension';
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; };
@@ -44,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);
@@ -51,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
}
@@ -60,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);
@@ -125,7 +132,7 @@ export async function activate(context: vscode.ExtensionContext) {
});
// Settings promotion command
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, () => { console.log('promote'); SettingsHelper.promote(); }));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote ));
// Collapse all sections in the webview
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
@@ -175,6 +182,15 @@ export async function activate(context: vscode.ExtensionContext) {
// Inserting an image in Markdown
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertImage, Article.insertImage));
// 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

@@ -14,6 +14,7 @@ import { EditorHelper } from '@estruyf/vscode';
import sanitize from '../helpers/Sanitize';
import { existsSync, mkdirSync } from 'fs';
import { ContentType } from '../models';
import { DateHelper } from './DateHelper';
export class ArticleHelper {
@@ -31,9 +32,9 @@ export class ArticleHelper {
* Retrieve the file's front matter by its path
* @param filePath
*/
public static getFrontMatterByPath(filePath: string) {
public static getFrontMatterByPath(filePath: string, surpressNotification: boolean = false) {
const file = fs.readFileSync(filePath, { encoding: "utf-8" });
return ArticleHelper.parseFile(file, filePath);
return ArticleHelper.parseFile(file, filePath, surpressNotification);
}
/**
@@ -90,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));
@@ -203,7 +206,7 @@ export class ArticleHelper {
let newFileName = `${sanitizedName}.md`;
if (prefix && typeof prefix === "string") {
newFileName = `${format(new Date(), prefix)}-${newFileName}`;
newFileName = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}-${newFileName}`;
}
newFilePath = join(folderPath, newFileName);
@@ -222,7 +225,7 @@ export class ArticleHelper {
* @param fileContents
* @returns
*/
private static parseFile(fileContents: string, fileName: string): matter.GrayMatterFile<string> | null {
private static parseFile(fileContents: string, fileName: string, surpressNotification: boolean = false): matter.GrayMatterFile<string> | null {
try {
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
@@ -250,19 +253,20 @@ export class ArticleHelper {
const items = [{
title: "Check file",
action: async () => {
console.log(fileName);
await EditorHelper.showFile(fileName)
}
}];
Notifications.error(`There seems to be an issue parsing the content its front matter. FileName: ${basename(fileName)}. ERROR: ${error.message || error}`, ...items).then((result: any) => {
if (result?.title) {
const item = items.find(i => i.title === result.title);
if (item) {
item.action();
if (!surpressNotification) {
Notifications.error(`There seems to be an issue parsing the content its front matter. FileName: ${basename(fileName)}. ERROR: ${error.message || error}`, ...items).then((result: any) => {
if (result?.title) {
const item = items.find(i => i.title === result.title);
if (item) {
item.action();
}
}
}
});
});
}
}
return null;
}

View File

@@ -1,18 +1,59 @@
import { ArticleHelper, Settings } from ".";
import { SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from "../constants";
import { ContentType as IContentType } from '../models';
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
import { ContentType as IContentType, DraftField } from '../models';
import { Uri, workspace, window } from 'vscode';
import { Folders } from "../commands/Folders";
import { Questions } from "./Questions";
import { format } from "date-fns";
import { join } from "path";
import { existsSync, mkdirSync, writeFileSync } from "fs";
import { writeFileSync } from "fs";
import { Notifications } from "./Notifications";
import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType";
export class ContentType {
/**
* Retrieve the draft field
* @returns
*/
public static getDraftField() {
const draftField = Settings.get<DraftField | null | undefined>(SETTINGS_CONTENT_DRAFT_FIELD);
if (draftField) {
return draftField;
}
return null;
}
/**
* Retrieve the field its status
* @param data
* @returns
*/
public static getDraftStatus(data: { [field: string]: any }) {
const contentType = ArticleHelper.getContentType(data);
const draftSetting = ContentType.getDraftField();
const draftField = contentType.fields.find(f => f.type === "draft");
let fieldValue = null;
if (draftField) {
fieldValue = data[draftField.name];
} else if (draftSetting && data && data[draftSetting.name]) {
fieldValue = data[draftSetting.name];
}
if (draftSetting && fieldValue) {
if (draftSetting.type === "boolean") {
return fieldValue ? "Draft" : "Published";
} else {
return fieldValue;
}
}
return null;
}
/**
* Create content based on content types
* @returns

157
src/helpers/CustomScript.ts Normal file
View File

@@ -0,0 +1,157 @@
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
import { ArticleHelper } from '.';
import { Folders } from '../commands/Folders';
import { exec } from 'child_process';
import matter = require('gray-matter');
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, path: string | null = null): Promise<void> {
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const wsPath = wsFolder.fsPath;
if (script.type === ScriptType.MediaFile || script.type === ScriptType.MediaFolder) {
CustomScript.runMediaScript(wsPath, path, script);
} else {
if (script.bulk) {
// Run script on all files
CustomScript.bulkRun(wsPath, script);
} else {
// Run script on current file.
CustomScript.singleRun(wsPath, script);
}
}
}
}
private static async singleRun(wsPath: string, script: ICustomScript): Promise<void> {
const editor = window.activeTextEditor;
if (!editor) return;
const article = ArticleHelper.getFrontMatter(editor);
if (article) {
const output = await CustomScript.runScript(wsPath, article, editor.document.uri.fsPath, script);
CustomScript.showOutput(output, script);
} else {
Notifications.warning(`${script.title}: Current article couldn't be retrieved.`);
}
}
private static async bulkRun(wsPath: string, script: ICustomScript): Promise<void> {
const folders = await Folders.getInfo();
if (!folders || folders.length === 0) {
Notifications.warning(`${script.title}: No files found.`);
return;
}
let output: string[] = [];
window.withProgress({
location: ProgressLocation.Notification,
title: `Executing: ${script.title}`,
cancellable: false
}, async (progress, token) => {
for await (const folder of folders) {
if (folder.lastModified.length > 0) {
for await (const file of folder.lastModified) {
try {
const article = ArticleHelper.getFrontMatterByPath(file.filePath, true);
if (article) {
const crntOutput = await CustomScript.runScript(wsPath, article, file.filePath, script);
if (crntOutput) {
output.push(crntOutput);
}
}
} catch (error) {
// Skipping file
}
}
}
}
CustomScript.showOutput(output.join(`\n`), script);
});
}
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 = "";
if (os.type() === "Windows_NT") {
articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`;
} else {
articleData = JSON.stringify(article?.data).replace(/'/g, "%27");
articleData = `'${articleData}'`;
}
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${contentPath}" ${articleData}`, (error, stdout) => {
if (error) {
Notifications.error(`${script.title}: ${error.message}`);
resolve(null);
return;
}
resolve(stdout);
});
});
}
private static showOutput(output: string | null, script: ICustomScript): void {
if (output) {
if (script.output === "editor") {
ContentProvider.show(output, script.title, script.outputType || "text");
} else {
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
if (value === 'Copy output') {
vscodeEnv.clipboard.writeText(output);
}
});
}
} else {
Notifications.info(`${script.title}: Executed your custom script.`);
}
}
}

View File

@@ -3,6 +3,16 @@ import { parse, parseISO, parseJSON } from "date-fns";
export class DateHelper {
public static formatUpdate(value: string | null | undefined): string | null {
if (!value) {
return null;
}
value = value.replace(/YYYY/g, 'yyyy');
value = value.replace(/DD/g, 'dd');
return value;
}
public static tryParse(date: any, format?: string): Date | null {
if (!date) {
return null;
@@ -35,7 +45,7 @@ export class DateHelper {
}
public static isValid(date: any): boolean {
return !isNaN(date.getTime());
return date instanceof Date && !isNaN(date?.getTime());
}
public static tryFormatParse(date: string, format: string): Date | null {

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

@@ -0,0 +1,43 @@
import { existsSync } from "fs";
import { resolve } from "path";
import { FrameworkDetectors } from "../constants/FrameworkDetectors";
import { Extension } from "./Extension";
export class FrameworkDetector {
public static get(folder: string) {
return this.check(folder);
}
public static getAll() {
return FrameworkDetectors.map((detector: any) => detector.framework);
}
private static check(folder: string) {
const { dependencies, devDependencies } = Extension.getInstance().packageJson;
for (const detector of FrameworkDetectors) {
if (detector && folder) {
// Verify by dependencies
for (const dependency of detector.requiredDependencies ?? []) {
const inDependencies = dependencies && dependencies[dependency]
const inDevDependencies = devDependencies && devDependencies[dependency]
if (inDependencies || inDevDependencies) {
return detector.framework;
}
}
// Verify by files
for (const filename of detector.requiredFiles ?? []) {
const fileExists = existsSync(resolve(folder, filename));
if (fileExists) {
return detector.framework;
}
}
}
}
return undefined;
}
}

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';
@@ -38,7 +38,7 @@ export class Settings {
* Check if the setting is present in the workspace and ask to promote them to the global settings
*/
public static async checkToPromote() {
const isPromoted = await Extension.getInstance().getState<boolean | undefined>(ExtensionState.SettingPromoted);
const isPromoted = await Extension.getInstance().getState<boolean | undefined>(ExtensionState.SettingPromoted, "workspace");
if (!isPromoted) {
if (Settings.hasSettings()) {
window.showInformationMessage(`You have local settings. Would you like to promote them to the global settings ("frontmatter.json")?`, 'Yes', 'No').then(async (result) => {
@@ -47,7 +47,7 @@ export class Settings {
}
if (result === "No" || result === "Yes") {
Extension.getInstance().setState(ExtensionState.SettingPromoted, true);
Extension.getInstance().setState(ExtensionState.SettingPromoted, true, "workspace");
}
});
}
@@ -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
*/

View File

@@ -14,14 +14,18 @@ export class SlugHelper {
// Remove punctuation from input string, and split it into words.
let cleanTitle = this.removePunctuation(articleTitle);
cleanTitle = cleanTitle.toLowerCase();
// Split into words
let words = cleanTitle.split(/\s/);
// Removing stop words
words = this.removeStopWords(words);
cleanTitle = words.join("-");
cleanTitle = this.replaceCharacters(cleanTitle);
return cleanTitle;
if (cleanTitle) {
cleanTitle = cleanTitle.toLowerCase();
// Split into words
let words = cleanTitle.split(/\s/);
// Removing stop words
words = this.removeStopWords(words);
cleanTitle = words.join("-");
cleanTitle = this.replaceCharacters(cleanTitle);
return cleanTitle;
}
return null;
}
/**
@@ -30,9 +34,13 @@ export class SlugHelper {
* @param value
*/
private static removePunctuation(value: string): string {
const punctuationless = value.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, " ");
if (typeof value !== "string") {
return "";
}
const punctuationless = value?.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, " ");
// Remove double spaces
return punctuationless.replace(/\s{2,}/g," ");
return punctuationless?.replace(/\s{2,}/g," ");
}
/**

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;
}

5
src/models/DraftField.ts Normal file
View File

@@ -0,0 +1,5 @@
export interface DraftField {
name: string;
type: "boolean" | "choice";
choices?: string[];
}

8
src/models/Framework.ts Normal file
View File

@@ -0,0 +1,8 @@
export interface Framework {
name: string;
dist: string;
static: string;
build: string;
}

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,5 @@
import { FileType } from "vscode";
import { FileStat } from "vscode";
import { DraftField } from ".";
import { Choice } from "./Choice";
import { DashboardData } from "./DashboardData";
@@ -8,6 +9,7 @@ export interface PanelSettings {
tags: string[];
date: DateInfo;
categories: string[];
customTaxonomy: CustomTaxonomy[];
freeform: boolean;
scripts: CustomScript[];
isInitialized: boolean;
@@ -17,24 +19,27 @@ export interface PanelSettings {
preview: PreviewSettings;
contentTypes: ContentType[];
dashboardViewData: DashboardData | undefined;
draftField: DraftField;
}
export interface ContentType {
name: string;
fields: Field[];
previewPath?: string | null;
pageBundle?: boolean;
}
export interface Field {
title?: string;
name: string;
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories";
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 {
@@ -43,6 +48,7 @@ export interface DateInfo {
export interface SEO {
title: number;
slug: number;
description: number;
content: number;
descriptionField: string;
@@ -59,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;
};
@@ -72,9 +74,24 @@ export interface CustomScript {
title: string;
script: string;
nodeBin?: string;
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"
}

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