Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
074de212ce | ||
|
|
98e4490318 | ||
|
|
c79dc3c41f | ||
|
|
b45abe88ac | ||
|
|
2bac596e3d | ||
|
|
15a6ea8d8d | ||
|
|
72d6f8263a | ||
|
|
7549b3d989 | ||
|
|
ca628b37de | ||
|
|
db120b5c45 | ||
|
|
2973f5d27b | ||
|
|
075c7ad350 | ||
|
|
23625ed8fa | ||
|
|
9bd06909c8 | ||
|
|
1b45c2af13 | ||
|
|
b4bbf6c6fb | ||
|
|
a5b8d9668d | ||
|
|
490210da00 | ||
|
|
6878d440b1 | ||
|
|
81fa0a7d0f | ||
|
|
b7d4e547a1 | ||
|
|
2b2e256b8f | ||
|
|
7a7b31c2ae | ||
|
|
dea5eb1053 | ||
|
|
df3144ea73 | ||
|
|
a170cc3ad9 | ||
|
|
35b3813022 | ||
|
|
f0472fe89b | ||
|
|
7bdb2fa025 | ||
|
|
5ba7ad449a | ||
|
|
145441d55e | ||
|
|
62ff3419c1 | ||
|
|
34aee134d6 | ||
|
|
5899120d87 | ||
|
|
816a2fefe7 | ||
|
|
1f64e59917 | ||
|
|
5e54334fb9 | ||
|
|
d354af306f | ||
|
|
045cece0ce | ||
|
|
506012200f | ||
|
|
4ad2f0d495 | ||
|
|
b00e3cfd4b | ||
|
|
bb05489872 | ||
|
|
ede4d417bd | ||
|
|
928072fa27 | ||
|
|
6a313fcc8a | ||
|
|
725f7f4915 | ||
|
|
b9cb0ea16d | ||
|
|
61b80795a4 | ||
|
|
8daaa23774 | ||
|
|
8a0d308ceb | ||
|
|
e6a7a9aae7 | ||
|
|
f3943bd846 | ||
|
|
d2ae94df34 | ||
|
|
f799613b1a | ||
|
|
3f057a01d8 | ||
|
|
3ad5136735 | ||
|
|
e201ce6f83 | ||
|
|
383a3a7d4c | ||
|
|
8261f1de1b | ||
|
|
5b38e6fa56 | ||
|
|
717f34bc85 | ||
|
|
47fb2a90a9 | ||
|
|
85a7221895 | ||
|
|
9618a89528 | ||
|
|
14f0af2754 | ||
|
|
ebe248670d | ||
|
|
511960c4a9 | ||
|
|
31fd1f93ce | ||
|
|
6625b69170 | ||
|
|
9e8533fbb8 | ||
|
|
9c9cbb7dcb | ||
|
|
079a13e161 | ||
|
|
69c1e587d0 | ||
|
|
3996252531 | ||
|
|
4fddda65e6 | ||
|
|
5916344092 | ||
|
|
b96722dd69 | ||
|
|
263ccab311 | ||
|
|
3571af82c7 | ||
|
|
c60520c0ff | ||
|
|
b473431eae | ||
|
|
cbf434f741 | ||
|
|
04c401207f | ||
|
|
7291e6aac6 | ||
|
|
a7aab96f0e | ||
|
|
f500749644 | ||
|
|
47e59bc54c | ||
|
|
8902e25021 | ||
|
|
33093e1eb4 | ||
|
|
d36178c44f | ||
|
|
15b09ccc75 | ||
|
|
dffa6c87a0 | ||
|
|
c4a1caee09 | ||
|
|
1d9f07b86d | ||
|
|
a794a95bb8 | ||
|
|
40a56f6057 | ||
|
|
82353f7b64 | ||
|
|
82a22da90a | ||
|
|
380e40ea05 | ||
|
|
2bedb23341 | ||
|
|
1110b76364 | ||
|
|
d19e632f80 | ||
|
|
4e040b5f7a | ||
|
|
7a2a0934c2 | ||
|
|
d3eb7b223c | ||
|
|
f74eec954f |
3
.github/workflows/release-beta.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
@@ -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 }}
|
||||
@@ -17,4 +17,8 @@ postcss.config.js
|
||||
.templates
|
||||
.github
|
||||
scripts
|
||||
.all-contributorsrc
|
||||
.all-contributorsrc
|
||||
assets/v2.*
|
||||
assets/v3.*
|
||||
assets/v4.*
|
||||
assets/sponsors
|
||||
|
||||
86
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
9
assets/icons/blockquote-dark.svg
Normal 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 |
9
assets/icons/blockquote-light.svg
Normal 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 |
5
assets/icons/bold-dark.svg
Normal 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 |
5
assets/icons/bold-light.svg
Normal 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 |
6
assets/icons/code-dark.svg
Normal 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 |
6
assets/icons/code-light.svg
Normal 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 |
7
assets/icons/codeblock-dark.svg
Normal 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 |
7
assets/icons/codeblock-light.svg
Normal 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 |
11
assets/icons/frontmatter-small-dark.svg
Normal 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 |
11
assets/icons/frontmatter-small-light.svg
Normal 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 |
11
assets/icons/frontmatter-small-teal.svg
Normal 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 |
10
assets/icons/heading-dark.svg
Normal 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 |
10
assets/icons/heading-light.svg
Normal 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 |
6
assets/icons/italic-dark.svg
Normal 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 |
6
assets/icons/italic-light.svg
Normal 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 |
3
assets/icons/media-dark.svg
Normal 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 |
3
assets/icons/media-light.svg
Normal 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 |
7
assets/icons/options-dark.svg
Normal 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 |
7
assets/icons/options-light.svg
Normal 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 |
8
assets/icons/ordered-list-dark.svg
Normal 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 |
8
assets/icons/ordered-list-light.svg
Normal 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 |
5
assets/icons/strikethrough-dark.svg
Normal 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 |
5
assets/icons/strikethrough-light.svg
Normal 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 |
9
assets/icons/unordered-list-dark.svg
Normal 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 |
9
assets/icons/unordered-list-light.svg
Normal 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 |
@@ -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
@@ -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",
|
||||
|
||||
479
package.json
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
63
src/commands/Diagnostics.ts
Normal 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}`;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/components/icons/CompressIcon.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
export const DefaultFields = {
|
||||
PublishingDate: `date`,
|
||||
LastModified: `lastmod`,
|
||||
Description: `description`
|
||||
Description: `description`,
|
||||
Slug: `slug`
|
||||
};
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
@@ -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`,
|
||||
}
|
||||
}
|
||||
};
|
||||
21
src/constants/FrameworkDetectors.ts
Normal 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"]
|
||||
}
|
||||
];
|
||||
@@ -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",
|
||||
};
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -3,5 +3,6 @@ export enum DashboardCommand {
|
||||
pages = "pages",
|
||||
settings = "settings",
|
||||
media = "media",
|
||||
viewData = "viewData"
|
||||
viewData = "viewData",
|
||||
mediaUpdate = "mediaUpdate"
|
||||
}
|
||||
@@ -17,5 +17,8 @@ export enum DashboardMessage {
|
||||
deleteMedia = 'deleteMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder'
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
setFramework = 'setFramework',
|
||||
setState = 'setState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`}
|
||||
|
||||
@@ -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>
|
||||
|
||||
20
src/dashboardWebView/components/Media/MenuButton.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum SortOption {
|
||||
LastModified = 1,
|
||||
FileNameAsc,
|
||||
FileNameDesc
|
||||
LastModifiedAsc = "LastModifiedAsc",
|
||||
LastModifiedDesc = "LastModifiedDesc",
|
||||
FileNameAsc = "FileNameAsc",
|
||||
FileNameDesc = "FileNameDesc"
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
10
src/dashboardWebView/models/SortingOption.ts
Normal 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;
|
||||
}
|
||||
4
src/dashboardWebView/models/ViewType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum ViewType {
|
||||
Contents = "contents",
|
||||
Media = "media"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './Page';
|
||||
export * from './Settings';
|
||||
export * from './SortingOption';
|
||||
export * from './Status';
|
||||
export * from './ViewType';
|
||||
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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('/')
|
||||
}));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
43
src/helpers/FrameworkDetector.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { workspace } from 'vscode';
|
||||
import { JsonDB } from 'node-json-db/dist/JsonDB';
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface ContentFolder {
|
||||
title: string;
|
||||
path: string;
|
||||
|
||||
excludeSubdir?: boolean;
|
||||
}
|
||||
7
src/models/CustomTaxonomyData.ts
Normal 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
@@ -0,0 +1,5 @@
|
||||
export interface DraftField {
|
||||
name: string;
|
||||
type: "boolean" | "choice";
|
||||
choices?: string[];
|
||||
}
|
||||
8
src/models/Framework.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
dist: string;
|
||||
static: string;
|
||||
build: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||