Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20a5178326 | ||
|
|
81ad61f89d | ||
|
|
5fc030b4dc | ||
|
|
bea6b181db | ||
|
|
e17819f458 | ||
|
|
db52e9c29a | ||
|
|
e06fcb8ced | ||
|
|
51f43e8bfa | ||
|
|
0f66935385 | ||
|
|
29a6b121bb | ||
|
|
6ed37f9ac2 | ||
|
|
f7c53b9afb | ||
|
|
e6e5c5c881 | ||
|
|
f059b89fa5 | ||
|
|
8d5a678bb8 | ||
|
|
8d901105bf | ||
|
|
637394156f | ||
|
|
a79a987f5e | ||
|
|
b22335d38e | ||
|
|
757ccbddcf | ||
|
|
39a30b320d | ||
|
|
912e436ca8 | ||
|
|
1b0853e23d | ||
|
|
07c4e69319 | ||
|
|
4b3808a9ec | ||
|
|
bd09a10fd8 | ||
|
|
9dbdd092f6 | ||
|
|
ce331f12fb | ||
|
|
cb93370f59 | ||
|
|
7c77dca821 | ||
|
|
17ffed418f | ||
|
|
7abb97e681 | ||
|
|
33b1acddd0 | ||
|
|
e73803f927 | ||
|
|
bbd257e650 | ||
|
|
491b32baaa | ||
|
|
70d8bfe273 |
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
||||
# Change Log
|
||||
|
||||
## [7.1.0] - 2022-04-07 - [Release notes](https://beta.frontmatter.codes/updates/v7.1.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#240](https://github.com/estruyf/vscode-front-matter/issues/240): Capability added to define display modes
|
||||
- [#246](https://github.com/estruyf/vscode-front-matter/issues/246): Support to add multiple tags/keywords/taxonomy via comma separated values
|
||||
- [#293](https://github.com/estruyf/vscode-front-matter/issues/293): Support added for setting preview images in block fields
|
||||
- [#294](https://github.com/estruyf/vscode-front-matter/issues/294): Full-text search allows you to search through all your page content
|
||||
- [#297](https://github.com/estruyf/vscode-front-matter/issues/297): SEO Keywords input got moved to the SEO section
|
||||
- [#301](https://github.com/estruyf/vscode-front-matter/issues/301): Show tags on the content cards
|
||||
- [#303](https://github.com/estruyf/vscode-front-matter/issues/303): Content card actions to quickly view, delete, or run custom scripts
|
||||
- [#310](https://github.com/estruyf/vscode-front-matter/issues/310): Supported mime types for media dashboard
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- [#296](https://github.com/estruyf/vscode-front-matter/issues/296): Loading optimization of the content dashboard
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#302](https://github.com/estruyf/vscode-front-matter/issues/302): Fix for spinner when navigating between tabs
|
||||
- [#304](https://github.com/estruyf/vscode-front-matter/issues/304): Fix yaml stringify which caused additional fields to be added
|
||||
- [#305](https://github.com/estruyf/vscode-front-matter/issues/305): Fix for overflow issue in taxonomy picker
|
||||
- [#306](https://github.com/estruyf/vscode-front-matter/issues/306): Fix for default value of content type fields
|
||||
- [#311](https://github.com/estruyf/vscode-front-matter/issues/311): Fix for updating snippets
|
||||
|
||||
## [7.0.0] - 2022-03-21 - [Release notes](https://beta.frontmatter.codes/updates/v7.0.0)
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
@@ -184,6 +184,9 @@ You can open showcase issues for the following things:
|
||||
<a href="https://github.com/flikteoh" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
|
||||
</a>
|
||||
<a href="https://github.com/themefisher" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/10640964" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -182,6 +182,9 @@ You can open showcase issues for the following things:
|
||||
<a href="https://github.com/flikteoh" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/1472065" />
|
||||
</a>
|
||||
<a href="https://github.com/themefisher" title="FlikTeoh">
|
||||
<img height="64px" style="border-radius:50%" src="https://avatars.githubusercontent.com/u/10640964" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 108 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#AD0670;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#AD0670"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#AD0670;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#AD0670"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#AD0670"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 37 KiB |
@@ -1,16 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#02AEB7;stroke-width:50;stroke-miterlimit:10;}
|
||||
.st1{fill:#02AEB7;}
|
||||
</style>
|
||||
<rect x="25" y="25" class="st0" width="1200" height="1200"/>
|
||||
<path class="st1" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path class="st1" 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
|
||||
<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.6s15,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"/>
|
||||
s-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.8s-14.2-97.6-20.3-149.9
|
||||
l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
<rect x="119.4" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="119.4" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#02aeb7;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#02aeb7"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#02aeb7"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1249.98 1249.98"><rect x="25" y="25" width="1199.98" height="1199.98" style="fill:none;stroke:#000000;stroke-miterlimit:10;stroke-width:50px"/><path d="M171.89,489.56H95.38V127.21H230.6V212.4H171.89v52.8h54.68v81.91H171.89Z" transform="translate(24 24)" style="fill:#000000"/><path d="M461.79,489.56H379l-37.8-129.08c-.37-2.18-1-5.08-1.93-8.68s-2.05-7.9-3.39-12.91l.55,23.94V489.56H260.33V127.21h78.34q51.75,0,77.43,26.05,32.65,33.32,32.66,94.81,0,65.71-43.85,90.82ZM336.84,295H342q13.21,0,22-12.91t8.81-32.86q0-40.59-33.21-40.6h-2.75Z" transform="translate(24 24)" style="fill:#000000"/><path d="M691.68,309.56q0,82.85-29.54,134.71-29.35,51.63-76.51,51.63-41.82,0-71.74-39.66Q476.29,406,476.28,305.57q0-96.23,39.26-147.15,29.18-37.78,69.18-37.79,49,0,78,51.17T691.68,309.56Zm-79.44.7q0-98.32-27.16-98.33-13.58,0-21.65,25.81-7.89,23.94-7.89,70.41,0,45.77,7.43,71t20.65,25.23q13.57,0,20.91-24.88Q612.24,354.62,612.24,310.26Z" transform="translate(24 24)" style="fill:#000000"/><path d="M724.34,489.56V127.21h73l38.35,127.2q3.1,11.27,7.06,25.81t8.72,33.56l7.88,31.92Q855.17,298.52,853,265t-2.2-56.33V127.21h73V489.56h-73l-38.53-133.3q-6.06-21.35-10.92-40t-8.53-35.56q2.38,38.26,3.49,66.65t1.1,49.76v92.46Z" transform="translate(24 24)" style="fill:#000000"/><path d="M1062.31,489.56H985.8V214H943.6V127.21h162.56V214h-43.85Z" transform="translate(24 24)" style="fill:#000000"/><path d="M122.7,730.59h35.82l27.36,133.72q5,25.05,9.16,50.2t7.55,52.74q.39-3.6.6-5.62a25.33,25.33,0,0,1,.4-2.87l5.84-37.56,5.23-35.66L219.29,862l24.35-131.39h36.22l28.57,327.72h-40l-7-111.22q-.41-8.49-.71-14.64c-.2-4.11-.3-7.5-.3-10.19l-1.81-43.94-1-40.33c0-.28,0-.88-.1-1.8s-.17-2.16-.3-3.72l-1,6.58q-1.61,11.69-2.91,20.38t-2.32,14.65L245.65,904l-2,11.25-26.16,143.06H189.3L164.75,934.78q-5-24.4-8.95-49.56t-7.14-52.75l-12.08,225.84H97.14Z" transform="translate(24 24)" style="fill:#000000"/><path d="M395.56,730.59h32.6l66.6,327.72H453.31l-11.67-63.89H380.06l-11.87,63.89H327.94Zm40,229.66L426.35,908q-9.27-53.28-15.1-113.77Q408.43,823.78,404,854t-10.46,64.2l-7.65,42Z" transform="translate(24 24)" style="fill:#000000"/><path d="M496.17,730.59H632.4v38.63H585.51v289.09h-41V769.22H496.17Z" transform="translate(24 24)" style="fill:#000000"/><path d="M639,730.59H775.26v38.63H728.38v289.09h-41V769.22H639Z" transform="translate(24 24)" style="fill:#000000"/><path d="M806.65,730.59H917.93V768H848.5V871.74h61.58V909.1H848.5V1021h69.43v37.35H806.65Z" transform="translate(24 24)" style="fill:#000000"/><path d="M964.61,730.59h55.13q34.21,0,50.91,17.19,21.13,22.29,21.13,68.14,0,35.24-11.16,56.56t-31.9,26.43l57.15,159.4h-42.46l-57-160.46v160.46H964.61Zm41.85,145.18q24.35,0,34.41-11.88t10.06-40.12a138.46,138.46,0,0,0-2.11-26.11q-2.11-10.81-6.64-17.61a27.08,27.08,0,0,0-11.67-10,41.58,41.58,0,0,0-17-3.18h-7Z" transform="translate(24 24)" style="fill:#000000"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#C5C5C5"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#C5C5C5" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#C5C5C5"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#C5C5C5"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="#424242"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="#424242"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="#424242"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="#424242"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="#424242"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="#424242" stroke-miterlimit="10" stroke-width="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="#424242"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="#424242"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="#424242"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="#424242"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="#424242"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="#424242"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,12 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#ffffff" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<path fill="#ffffff" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
|
||||
<path fill="#ffffff" 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
|
||||
<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.6s15,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"/>
|
||||
s-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.8s-14.2-97.6-20.3-149.9
|
||||
l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
<rect x="119.4" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="0.1" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="119.4" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="1184.7" fill="#C5C5C5" width="184" height="64.7"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,12 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1250 1250" style="enable-background:new 0 0 1250 1250;" xml:space="preserve">
|
||||
<rect x="25" y="25" fill="none" stroke="#424242" stroke-width="50" stroke-miterlimit="10" width="1200" height="1200"/>
|
||||
<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
|
||||
<path fill="#424242" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.6s15,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"/>
|
||||
s-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.8s-14.2-97.6-20.3-149.9
|
||||
l-34.3,641.6H529.6L602.2,151.2z"/>
|
||||
<rect x="119.4" y="0.1" fill="#424242" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="0.1" fill="#424242" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="0.1" fill="#424242" width="184" height="64.7"/>
|
||||
<rect x="119.4" y="1184.7" fill="#424242" width="184" height="64.7"/>
|
||||
<rect x="395.7" y="1184.7" fill="#424242" width="184" height="64.7"/>
|
||||
<rect x="675.3" y="1184.7" fill="#424242" width="184" height="64.7"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -8,4 +8,4 @@
|
||||
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>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -30,7 +30,7 @@
|
||||
}
|
||||
|
||||
.inherit {
|
||||
position: relative !important;
|
||||
position: inherit !important;
|
||||
}
|
||||
|
||||
.z-10 { z-index: 10 !important; }
|
||||
@@ -143,12 +143,13 @@
|
||||
}
|
||||
|
||||
.article__tags {
|
||||
position: relative;
|
||||
/* position: relative; */
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.article__tags__dropbox {
|
||||
width: 90%;
|
||||
/* Minus the twice the padding */
|
||||
width: calc(100% - 2.5rem);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
@@ -172,20 +173,21 @@
|
||||
|
||||
.article__tags__input.freeform {
|
||||
position: relative;
|
||||
outline: 1px solid var(--vscode-inputValidation-infoBorder);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.article__tags__input.freeform input {
|
||||
padding-right: 35px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.article__tags__input button {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30px;
|
||||
padding-bottom: 2px;
|
||||
padding-top: 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
39
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "7.0.0",
|
||||
"version": "7.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -55,9 +55,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@estruyf/vscode": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@estruyf/vscode/-/vscode-0.0.2.tgz",
|
||||
"integrity": "sha512-Qb2UGiARR0Pxeknjzwq925kxyxWMsASIcCOfwOJGZed7EPhDLc6Zd1v6AReYpkQFyxSUjBVg38dT7dn5bh19fQ==",
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@estruyf/vscode/-/vscode-0.0.3.tgz",
|
||||
"integrity": "sha512-Mak13ZHj/TcyL0snPHsrqELlhpvV1JszZFScCcm1G2dp6UdP4dSk32MlNH5FusGhTIEOI6APE8krldMcZjS/3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/vscode-webview": "1.57.0"
|
||||
@@ -584,6 +584,12 @@
|
||||
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mime-types": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
|
||||
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
@@ -3806,12 +3812,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.34",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
|
||||
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.51.0"
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
@@ -5554,6 +5568,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tailwindcss-nested-groups": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss-nested-groups/-/tailwindcss-nested-groups-1.2.4.tgz",
|
||||
"integrity": "sha512-SrW9VTgH5/PmW/E0q1+TC7TckhVfonDpiYiLKsRYX57FEcM3LemiH7JYcsTlubM+eAVkd9Jjj03pTiyfnDBibQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
}
|
||||
},
|
||||
"tapable": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
||||
|
||||
136
package.json
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "7.0.0",
|
||||
"version": "7.1.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -379,6 +379,12 @@
|
||||
},
|
||||
"scope": "Custom scripts"
|
||||
},
|
||||
"frontMatter.dashboard.content.cardTags": {
|
||||
"type": "string",
|
||||
"default": "tags",
|
||||
"markdownDescription": "Specify the name of the metadata field that will be used to show the tags on the content card. When empty or null, it will hide the tags from the card. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.dashboard.content.cardTags)",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.dashboard.mediaSnippet": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
@@ -561,6 +567,54 @@
|
||||
"default": null,
|
||||
"markdownDescription": "Specify the command you want to use to start your static site generator or framework. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.startcommand)"
|
||||
},
|
||||
"frontMatter.global.activeMode": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": "",
|
||||
"markdownDescription": "Specify the activated mode of Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.global.activemode)",
|
||||
"scope": "Global"
|
||||
},
|
||||
"frontMatter.global.modes": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "Specify the modes you want to use for Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.global.modes)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of your mode."
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"description": "The features you want to use for your mode.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"panel.globalSettings",
|
||||
"panel.seo",
|
||||
"panel.actions",
|
||||
"panel.metadata",
|
||||
"panel.recentlyModified",
|
||||
"panel.otherActions",
|
||||
"dashboard.snippets.view",
|
||||
"dashboard.snippets.manage",
|
||||
"dashboard.data.view"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"features"
|
||||
]
|
||||
},
|
||||
"scope": "Global"
|
||||
},
|
||||
"frontMatter.global.notifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -591,6 +645,19 @@
|
||||
"markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.media.defaultsorting)",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.media.supportedMimeTypes": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"audio/*"
|
||||
],
|
||||
"markdownDescription": "Specify the mime types to support for the media files. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.media.supportedMimeTypes)",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.panel.freeform": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -697,7 +764,14 @@
|
||||
"description": "Title to show in the UI"
|
||||
},
|
||||
"default": {
|
||||
"type": "string",
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"array",
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"description": "Default value"
|
||||
},
|
||||
"choices": {
|
||||
@@ -1105,6 +1179,15 @@
|
||||
"title": "Authenticate",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"title": "Blockquote",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/blockquote-light.svg",
|
||||
"dark": "assets/icons/blockquote-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.bold",
|
||||
"title": "Bold",
|
||||
@@ -1141,15 +1224,6 @@
|
||||
"dark": "assets/icons/codeblock-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"title": "Blockquote",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/blockquote-light.svg",
|
||||
"dark": "assets/icons/blockquote-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"title": "Collapse sections",
|
||||
@@ -1276,8 +1350,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.snippets",
|
||||
"title": "Open snippets dashboard",
|
||||
"command": "frontMatter.dashboard.media",
|
||||
"title": "Open media dashboard",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-dark.svg",
|
||||
@@ -1285,8 +1359,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.media",
|
||||
"title": "Open media dashboard",
|
||||
"command": "frontMatter.dashboard.snippets",
|
||||
"title": "Open snippets dashboard",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-dark.svg",
|
||||
@@ -1340,6 +1414,12 @@
|
||||
"dark": "assets/icons/strikethrough-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.mode.switch",
|
||||
"title": "Switch mode",
|
||||
"category": "Front matter",
|
||||
"icon": "$(preview)"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.tasklist",
|
||||
"title": "Task list",
|
||||
@@ -1380,7 +1460,7 @@
|
||||
{
|
||||
"command": "frontMatter.insertSnippet",
|
||||
"group": "navigation@-129",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
@@ -1463,6 +1543,14 @@
|
||||
"command": "frontMatter.preview",
|
||||
"when": "frontMatterCanOpenPreview"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.data",
|
||||
"when": "frontMatter:dashboard:data:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.snippets",
|
||||
"when": "frontMatter:dashboard:snippets:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"when": "false"
|
||||
@@ -1529,7 +1617,7 @@
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertSnippet",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
@@ -1575,9 +1663,14 @@
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"command": "frontMatter.mode.switch",
|
||||
"group": "navigation@1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"group": "navigation@2",
|
||||
"when": "view == frontMatter.explorer"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1652,7 +1745,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "0.0.2",
|
||||
"@estruyf/vscode": "0.0.3",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "1.0.4",
|
||||
"@iarna/toml": "2.2.3",
|
||||
@@ -1666,6 +1759,7 @@
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/lodash.uniqby": "4.7.6",
|
||||
"@types/lodash.xor": "^4.5.6",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/mustache": "^4.1.2",
|
||||
"@types/node": "10.17.48",
|
||||
@@ -1696,6 +1790,7 @@
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"lodash.xor": "^4.5.0",
|
||||
"mdast-util-from-markdown": "1.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"mustache": "^4.2.0",
|
||||
"node-json-db": "^1.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -1713,6 +1808,7 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"style-loader": "2.0.0",
|
||||
"tailwindcss": "^2.2.7",
|
||||
"tailwindcss-nested-groups": "^1.2.4",
|
||||
"ts-loader": "8.0.3",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "^4.5.4",
|
||||
@@ -1732,4 +1828,4 @@
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { DashboardData } from '../models/DashboardData';
|
||||
import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener } from '../listeners/dashboard';
|
||||
import { MediaListener as PanelMediaListener } from '../listeners/panel'
|
||||
import { ModeListener } from '../listeners/general';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -143,6 +144,7 @@ export class Dashboard {
|
||||
DataListener.process(msg);
|
||||
TelemetryListener.process(msg);
|
||||
SnippetListener.process(msg);
|
||||
ModeListener.process(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ export class Preview {
|
||||
);
|
||||
|
||||
webView.iconPath = {
|
||||
dark: Uri.file(join(extensionPath, 'assets/frontmatter-dark.svg')),
|
||||
light: Uri.file(join(extensionPath, 'assets/frontmatter.svg'))
|
||||
dark: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-dark.svg')),
|
||||
light: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-light.svg'))
|
||||
}
|
||||
|
||||
const localhostUrl = await env.asExternalUri(
|
||||
|
||||
26
src/components/features/FeatureFlag.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IFeatureFlagProps {
|
||||
flag: string;
|
||||
features: string[] | null;
|
||||
alternative?: JSX.Element;
|
||||
}
|
||||
|
||||
export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({ flag, features, alternative, children }: React.PropsWithChildren<IFeatureFlagProps>) => {
|
||||
|
||||
if (!features ||( features.length > 0 && !features.includes(flag))) {
|
||||
if (alternative) {
|
||||
return alternative;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -35,6 +35,7 @@ export const COMMAND_NAME = {
|
||||
promote: getCommandName("promoteSettings"),
|
||||
createFolder: getCommandName("createFolder"),
|
||||
diagnostics: getCommandName("diagnostics"),
|
||||
modeSwitch: getCommandName("mode.switch"),
|
||||
|
||||
// Insert dashboards
|
||||
insertImage: getCommandName("insertImage"),
|
||||
|
||||
@@ -12,6 +12,10 @@ export const ExtensionState = {
|
||||
},
|
||||
Media: {
|
||||
Sorting: `frontMatter:Dashboard:Media:Sorting`,
|
||||
},
|
||||
Pages: {
|
||||
Cache: `frontMatter:Dashboard:Pages:Cache`,
|
||||
Index: `frontMatter:Dashboard:Pages:Index`,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
21
src/constants/Features.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
export const FEATURE_FLAG = {
|
||||
panel: {
|
||||
globalSettings: "panel.globalSettings",
|
||||
seo: "panel.seo",
|
||||
actions: "panel.actions",
|
||||
metadata: "panel.metadata",
|
||||
recentlyModified: "panel.recentlyModified",
|
||||
otherActions: "panel.otherActions",
|
||||
},
|
||||
dashboard: {
|
||||
snippets: {
|
||||
view: "dashboard.snippets.view",
|
||||
manage: "dashboard.snippets.manage",
|
||||
},
|
||||
data: {
|
||||
view: "dashboard.data.view",
|
||||
}
|
||||
}
|
||||
};
|
||||
5
src/constants/GeneralCommands.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
export enum GeneralCommands{
|
||||
setMode = "setMode"
|
||||
};
|
||||
@@ -8,4 +8,7 @@ export const CONTEXT = {
|
||||
wysiwyg: "frontMatter:markdown:wysiwyg",
|
||||
backer: "frontMatter:backers:supporter",
|
||||
isValidFile: "frontMatter:file:isValid",
|
||||
|
||||
isSnippetsDashboardEnabled: "frontMatter:dashboard:snippets:enabled",
|
||||
isDataDashboardEnabled: "frontMatter:dashboard:data:enabled",
|
||||
};
|
||||
@@ -3,7 +3,9 @@ export * from './DefaultFields';
|
||||
export * from './DefaultFileTypes';
|
||||
export * from './Extension';
|
||||
export * from './ExtensionState';
|
||||
export * from './Features';
|
||||
export * from './FrameworkDetectors';
|
||||
export * from './GeneralCommands';
|
||||
export * from './Links';
|
||||
export * from './LocalStore';
|
||||
export * from './Navigation';
|
||||
|
||||
@@ -3,6 +3,8 @@ export const EXTENSION_NAME = "Front Matter";
|
||||
export const CONFIG_KEY = "frontMatter";
|
||||
|
||||
export const SETTING_GLOBAL_NOTIFICATIONS = "global.notifications";
|
||||
export const SETTING_GLOBAL_MODES = "global.modes";
|
||||
export const SETTING_GLOBAL_ACTIVE_MODE = "global.activeMode";
|
||||
|
||||
export const SETTING_TAXONOMY_TAGS = "taxonomy.tags";
|
||||
export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories";
|
||||
@@ -56,8 +58,11 @@ export const SETTING_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
|
||||
export const SETTING_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
|
||||
export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
|
||||
|
||||
export const SETTING_MEDIA_SUPPORTED_MIMETYPES = "media.supportedMimeTypes";
|
||||
|
||||
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
|
||||
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
|
||||
export const SETTING_DASHBOARD_CONTENT_TAGS = "dashboard.content.cardTags";
|
||||
|
||||
export const SETTING_DATA_FILES = "data.files";
|
||||
export const SETTING_DATA_FOLDERS = "data.folders";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export enum DashboardCommand {
|
||||
loading = "loading",
|
||||
pages = "pages",
|
||||
searchPages = "searchPages",
|
||||
settings = "settings",
|
||||
media = "media",
|
||||
viewData = "viewData",
|
||||
mediaUpdate = "mediaUpdate",
|
||||
dataFileEntries = "dataFileEntries"
|
||||
dataFileEntries = "dataFileEntries",
|
||||
searchReady = "searchReady",
|
||||
}
|
||||
@@ -1,32 +1,46 @@
|
||||
export enum DashboardMessage {
|
||||
getViewType = 'getViewType',
|
||||
reload = 'reload',
|
||||
setPageViewType = 'setPageViewType',
|
||||
getMode = 'getMode',
|
||||
showWarning = 'showWarning',
|
||||
|
||||
// Content dashboard
|
||||
getData = 'getData',
|
||||
openFile = 'openFile',
|
||||
getTheme = 'getTheme',
|
||||
createContent = 'createContent',
|
||||
createByContentType = 'createByContentType',
|
||||
createByTemplate = 'createByTemplate',
|
||||
updateSetting = 'updateSetting',
|
||||
initializeProject = 'initializeProject',
|
||||
reload = 'reload',
|
||||
setPageViewType = 'setPageViewType',
|
||||
refreshPages = 'refreshPages',
|
||||
searchPages = 'searchPages',
|
||||
openFile = 'openFile',
|
||||
deleteFile = 'deleteFile',
|
||||
|
||||
// Media Dashboard
|
||||
getMedia = 'getMedia',
|
||||
copyToClipboard = 'copyToClipboard',
|
||||
refreshMedia = 'refreshMedia',
|
||||
refreshPages = 'refreshPages',
|
||||
uploadMedia = 'uploadMedia',
|
||||
deleteMedia = 'deleteMedia',
|
||||
revealMedia = 'revealMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
setFramework = 'setFramework',
|
||||
setState = 'setState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
|
||||
// Data dashboard
|
||||
getDataEntries = 'getDataEntries',
|
||||
putDataEntries = 'putDataEntries',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
|
||||
// Snippets dashboard
|
||||
insertSnippet = 'insertSnippet',
|
||||
addSnippet = 'addSnippet',
|
||||
updateSnippet = 'updateSnippet',
|
||||
|
||||
// Other
|
||||
getTheme = 'getTheme',
|
||||
updateSetting = 'updateSetting',
|
||||
initializeProject = 'initializeProject',
|
||||
setFramework = 'setFramework',
|
||||
setState = 'setState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
}
|
||||
102
src/dashboardWebView/components/Contents/ContentActions.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { EyeIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { MenuItem, MenuItems, ActionMenuButton, QuickAction } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
|
||||
export interface IContentActionsProps {
|
||||
title: string;
|
||||
path: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
onOpen: () => void;
|
||||
}
|
||||
|
||||
export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({ title, path, scripts, onOpen }: React.PropsWithChildren<IContentActionsProps>) => {
|
||||
const [ showDeletionAlert, setShowDeletionAlert ] = React.useState(false);
|
||||
|
||||
const onView = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
onOpen();
|
||||
};
|
||||
|
||||
const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
setShowDeletionAlert(true);
|
||||
};
|
||||
|
||||
const onDeleteConfirm = () => {
|
||||
if (path) {
|
||||
Messenger.send(DashboardMessage.deleteFile, path);
|
||||
}
|
||||
setShowDeletionAlert(false);
|
||||
}
|
||||
|
||||
const runCustomScript = React.useCallback((e: React.MouseEvent<HTMLButtonElement>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
Messenger.send(DashboardMessage.runCustomScript, {script, path});
|
||||
}, [path]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
return (scripts || []).filter(script => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk).map(script => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={script.title}
|
||||
onClick={(value, e) => runCustomScript(e, script)} />
|
||||
))
|
||||
}, [scripts]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`group-scope absolute top-6 right-0 flex flex-col space-y-4`}>
|
||||
<div className="flex items-center border border-transparent group-scope-hover:bg-gray-200 dark:group-scope-hover:bg-vulcan-200 group-scope-hover:border-gray-100 dark:group-scope-hover:border-vulcan-50 rounded-full p-2 -mt-4">
|
||||
|
||||
<Menu as="div" className="relative z-10 flex text-left">
|
||||
<div className='hidden group-scope-hover:flex'>
|
||||
<QuickAction
|
||||
title={`View content`}
|
||||
onClick={onView}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={`Delete content`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<ActionMenuButton title={`Menu`} />
|
||||
|
||||
<MenuItems widthClass='w-40' marginTopClass='mt-6'>
|
||||
<MenuItem
|
||||
title={`View`}
|
||||
onClick={onView} />
|
||||
|
||||
{ customScriptActions }
|
||||
|
||||
<MenuItem
|
||||
title={`Delete`}
|
||||
onClick={(value, e) => onDelete(e)} />
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{
|
||||
showDeletionAlert && (
|
||||
<Alert
|
||||
title={`Delete: ${title}`}
|
||||
description={`Are you sure you want to delete the "${title}" content?`}
|
||||
okBtnText={`Delete`}
|
||||
cancelBtnText={`Cancel`}
|
||||
dismiss={() => setShowDeletionAlert(false)}
|
||||
trigger={onDeleteConfirm} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -33,7 +33,7 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loadin
|
||||
<PageLayout
|
||||
folders={pageFolders}
|
||||
totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto pb-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { ViewSelector } from '../../state';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { DateField } from '../DateField';
|
||||
import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ContentActions } from './ContentActions';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
|
||||
@@ -15,16 +16,27 @@ const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const openFile = () => {
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
};
|
||||
|
||||
const tags: string[] | undefined = React.useMemo(() => {
|
||||
if (!settings?.dashboardState?.contents?.tags) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tagField = settings.dashboardState.contents.tags;
|
||||
return pageData[tagField] || [];
|
||||
}, [settings, pageData]);
|
||||
|
||||
if (view === DashboardViewType.Grid) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md dark:shadow-none hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50`}
|
||||
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left shadow-md dark:shadow-none hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50`}
|
||||
onClick={openFile}>
|
||||
|
||||
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
|
||||
{
|
||||
pageData[PREVIEW_IMAGE_FIELD] ? (
|
||||
@@ -37,16 +49,38 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="p-4 w-full">
|
||||
<div className="relative p-4 w-full">
|
||||
<div className={`flex justify-between items-center`}>
|
||||
<Status draft={draft} />
|
||||
|
||||
<DateField value={date} />
|
||||
<DateField className={`mr-4`} value={date} />
|
||||
|
||||
<ContentActions
|
||||
title={title}
|
||||
path={fmFilePath}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile} />
|
||||
</div>
|
||||
|
||||
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
|
||||
|
||||
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
|
||||
|
||||
{
|
||||
tags && tags.length > 0 && (
|
||||
<div className="mt-2">
|
||||
{
|
||||
tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-block mr-1 mt-1 text-[#5D561D] dark:text-[#F0ECD0] text-xs">
|
||||
#{tag}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -4,12 +4,14 @@ import useMessages from '../hooks/useMessages';
|
||||
import useDarkMode from '../../hooks/useDarkMode';
|
||||
import { WelcomeScreen } from './WelcomeScreen';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { DashboardViewSelector, ModeAtom } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
import { NavigationType } from '../models';
|
||||
import { DataView } from './DataView';
|
||||
import { Snippets } from './SnippetsView/Snippets';
|
||||
import { FeatureFlag } from '../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../constants';
|
||||
|
||||
export interface IDashboardProps {
|
||||
showWelcome: boolean;
|
||||
@@ -18,6 +20,7 @@ export interface IDashboardProps {
|
||||
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
|
||||
const { loading, pages, settings } = useMessages();
|
||||
const view = useRecoilValue(DashboardViewSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
useDarkMode();
|
||||
|
||||
if (!settings) {
|
||||
@@ -50,9 +53,11 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
|
||||
|
||||
if (view === NavigationType.Data) {
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<DataView />
|
||||
</main>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.data.view}>
|
||||
<main className={`h-full w-full`}>
|
||||
<DataView />
|
||||
</main>
|
||||
</FeatureFlag>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import * as React from 'react';
|
||||
import { DateHelper } from '../../helpers/DateHelper';
|
||||
|
||||
export interface IDateFieldProps {
|
||||
className?: string;
|
||||
value: Date | string;
|
||||
}
|
||||
|
||||
export const DateField: React.FunctionComponent<IDateFieldProps> = ({value}: React.PropsWithChildren<IDateFieldProps>) => {
|
||||
export const DateField: React.FunctionComponent<IDateFieldProps> = ({className, value}: React.PropsWithChildren<IDateFieldProps>) => {
|
||||
const [ dateValue, setDateValue ] = React.useState<string>("");
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -24,6 +25,6 @@ export const DateField: React.FunctionComponent<IDateFieldProps> = ({value}: Rea
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`text-vulcan-100 dark:text-whisper-900 text-xs`}>{dateValue}</span>
|
||||
<span className={`${className || ""} text-vulcan-100 dark:text-whisper-900 text-xs`}>{dateValue}</span>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaTotalSelector, PageAtom, SearchAtom, SelectedMediaFolderSelector } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import { LIMIT } from '../../hooks/useMedia';
|
||||
|
||||
export interface IPaginationStatusProps {}
|
||||
|
||||
export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> = (props: React.PropsWithChildren<IPaginationStatusProps>) => {
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const [ , setSearch ] = useRecoilState(SearchAtom);
|
||||
const page = useRecoilValue(PageAtom);
|
||||
|
||||
const getTotalPage = () => {
|
||||
const mediaItems = ((page + 1) * LIMIT);
|
||||
@@ -22,21 +17,8 @@ export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> =
|
||||
return mediaItems;
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
setPage(0);
|
||||
setSearch('');
|
||||
Messenger.send(DashboardMessage.refreshMedia, { folder: selectedFolder });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hidden sm:flex">
|
||||
<button className={`mr-2 text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500`}
|
||||
title="Refresh media"
|
||||
onClick={refresh}>
|
||||
<RefreshIcon className={`h-5 w-5`} />
|
||||
<span className="sr-only">Refresh media</span>
|
||||
</button>
|
||||
|
||||
<div className="hidden sm:flex">
|
||||
<p className="text-sm text-gray-500 dark:text-whisper-900">
|
||||
Showing <span className="font-medium">{(page * LIMIT) + 1}</span> to <span className="font-medium">{getTotalPage()}</span> of{' '}
|
||||
<span className="font-medium">{totalMedia}</span> results
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { NavigationType } from '../../models';
|
||||
import { CategoryAtom, DashboardViewAtom, FolderAtom, LoadingAtom, PageAtom, SearchAtom, SelectedMediaFolderSelector, SortingAtom, TagAtom } from '../../state';
|
||||
|
||||
export interface IRefreshDashboardDataProps {}
|
||||
|
||||
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = (props: React.PropsWithChildren<IRefreshDashboardDataProps>) => {
|
||||
const view = useRecoilValue(DashboardViewAtom);
|
||||
const [, setLoading] = useRecoilState(LoadingAtom);
|
||||
const resetSearch = useResetRecoilState(SearchAtom);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetFolder = useResetRecoilState(FolderAtom);
|
||||
const resetTag = useResetRecoilState(TagAtom);
|
||||
const resetCategory = useResetRecoilState(CategoryAtom);
|
||||
// Media
|
||||
const resetPage = useResetRecoilState(PageAtom);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
|
||||
const refreshPages = () => {
|
||||
setLoading(true);
|
||||
resetSearch();
|
||||
resetSorting();
|
||||
resetFolder();
|
||||
resetTag();
|
||||
resetCategory();
|
||||
Messenger.send(DashboardMessage.refreshPages);
|
||||
};
|
||||
|
||||
const refreshMedia = () => {
|
||||
setLoading(true);
|
||||
resetPage();
|
||||
resetSearch();
|
||||
Messenger.send(DashboardMessage.refreshMedia, { folder: selectedFolder });
|
||||
};
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
if (view === NavigationType.Contents) {
|
||||
refreshPages();
|
||||
} else if (view === NavigationType.Media) {
|
||||
refreshMedia();
|
||||
}
|
||||
}, [view]);
|
||||
|
||||
return (
|
||||
<button className={`mr-2 text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500`}
|
||||
title="Refresh dashboard"
|
||||
onClick={refresh}>
|
||||
<RefreshIcon className={`h-5 w-5`} />
|
||||
<span className="sr-only">Refresh dashboard</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useResetRecoilState } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { CategoryAtom, FolderAtom, LoadingAtom, SearchAtom, SortingAtom, TagAtom } from '../../state';
|
||||
|
||||
export interface IRefreshPagesProps {}
|
||||
|
||||
export const RefreshPages: React.FunctionComponent<IRefreshPagesProps> = (props: React.PropsWithChildren<IRefreshPagesProps>) => {
|
||||
const [, setLoading] = useRecoilState(LoadingAtom);
|
||||
const resetSearch = useResetRecoilState(SearchAtom);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetFolder = useResetRecoilState(FolderAtom);
|
||||
const resetTag = useResetRecoilState(TagAtom);
|
||||
const resetCategory = useResetRecoilState(CategoryAtom);
|
||||
|
||||
const refresh = () => {
|
||||
setLoading(true);
|
||||
resetSearch();
|
||||
resetSorting();
|
||||
resetFolder();
|
||||
resetTag();
|
||||
resetCategory();
|
||||
Messenger.send(DashboardMessage.refreshPages);
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={`mr-2 text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500`}
|
||||
title="Refresh dashboard"
|
||||
onClick={refresh}>
|
||||
<RefreshIcon className={`h-5 w-5`} />
|
||||
<span className="sr-only">Refresh dashboard</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import {SearchIcon} from '@heroicons/react/solid';
|
||||
import {SearchIcon, XCircleIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import { SearchAtom } from '../../state';
|
||||
import { RefreshPages } from './RefreshPages';
|
||||
import { SearchAtom, SearchReadyAtom } from '../../state';
|
||||
import { RefreshDashboardData } from './RefreshDashboardData';
|
||||
|
||||
export interface ISearchboxProps {
|
||||
placeholder?: string;
|
||||
@@ -12,12 +12,18 @@ export interface ISearchboxProps {
|
||||
export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({placeholder}: React.PropsWithChildren<ISearchboxProps>) => {
|
||||
const [ value, setValue ] = React.useState('');
|
||||
const [ debounceSearchValue, setDebounceValue ] = useRecoilState(SearchAtom);
|
||||
const searchReady = useRecoilValue(SearchReadyAtom);
|
||||
const debounceSearch = useDebounce<string>(value, 500);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.value);
|
||||
};
|
||||
|
||||
const reset = React.useCallback(() => {
|
||||
setValue('');
|
||||
setDebounceValue('');
|
||||
}, [setValue, setDebounceValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!debounceSearchValue && value) {
|
||||
setValue('');
|
||||
@@ -38,17 +44,26 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({placeholder
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="search"
|
||||
type="text"
|
||||
name="search"
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none`}
|
||||
className={`block w-full py-2 pl-10 pr-3 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none appearance-none disabled:opacity-50`}
|
||||
placeholder={placeholder || "Search"}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
disabled={!searchReady}
|
||||
/>
|
||||
|
||||
{
|
||||
value && (
|
||||
<button onClick={reset} className="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||
<XCircleIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RefreshPages />
|
||||
<RefreshDashboardData />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
import { DatabaseIcon, PhotographIcon, ScissorsIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { NavigationType } from '../../models';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { ModeAtom } from '../../state';
|
||||
import { Tab } from './Tab';
|
||||
|
||||
export interface ITabsProps {
|
||||
@@ -11,7 +13,7 @@ export interface ITabsProps {
|
||||
}
|
||||
|
||||
export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.PropsWithChildren<ITabsProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
|
||||
return (
|
||||
<ul className="flex items-center justify-start h-full" data-tabs-toggle="#myTabContent" role="tablist">
|
||||
@@ -29,20 +31,24 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.
|
||||
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
|
||||
</Tab>
|
||||
</li>
|
||||
<li className="mr-2" role="presentation">
|
||||
<Tab
|
||||
navigationType={NavigationType.Snippets}
|
||||
onNavigate={onNavigate}>
|
||||
<ScissorsIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
|
||||
</Tab>
|
||||
</li>
|
||||
<li className="mr-2" role="presentation">
|
||||
<Tab
|
||||
navigationType={NavigationType.Data}
|
||||
onNavigate={onNavigate}>
|
||||
<DatabaseIcon className={`h-6 w-auto mr-2`} /><span>Data</span>
|
||||
</Tab>
|
||||
</li>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.view}>
|
||||
<li className="mr-2" role="presentation">
|
||||
<Tab
|
||||
navigationType={NavigationType.Snippets}
|
||||
onNavigate={onNavigate}>
|
||||
<ScissorsIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
|
||||
</Tab>
|
||||
</li>
|
||||
</FeatureFlag>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.data.view}>
|
||||
<li className="mr-2" role="presentation">
|
||||
<Tab
|
||||
navigationType={NavigationType.Data}
|
||||
onNavigate={onNavigate}>
|
||||
<DatabaseIcon className={`h-6 w-auto mr-2`} /><span>Data</span>
|
||||
</Tab>
|
||||
</li>
|
||||
</FeatureFlag>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +1,22 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ClipboardIcon, CodeIcon, EyeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { ClipboardIcon, CodeIcon, DocumentIcon, EyeIcon, MusicNoteIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon, VideoCameraIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallback, useEffect, useMemo } 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 { FileIcon } from '../../../panelWebView/components/Icons/FileIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { MenuItem, MenuItems } from '../Menu';
|
||||
import { ActionMenuButton } from '../Menu/ActionMenuButton';
|
||||
import { QuickAction } from '../Menu/QuickAction';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import { MenuButton } from './MenuButton'
|
||||
import { QuickAction } from './QuickAction';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -166,9 +167,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
setShowDetails(true);
|
||||
};
|
||||
|
||||
const openLightbox = () => {
|
||||
setLightbox(media.vsPath || "");
|
||||
};
|
||||
const openLightbox = useCallback(() => {
|
||||
if (!isVideoFile() && !isAudioFile()) {
|
||||
setLightbox(media.vsPath || "");
|
||||
}
|
||||
}, [media.vsPath]);
|
||||
|
||||
const updateMetadata = () => {
|
||||
setShowForm(true);
|
||||
@@ -184,6 +187,55 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
))
|
||||
}
|
||||
|
||||
const isVideoFile = useCallback(() => {
|
||||
if (media.mimeType?.startsWith("video/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isAudioFile = useCallback(() => {
|
||||
if (media.mimeType?.startsWith("audio/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isImageFile = useCallback(() => {
|
||||
if (media.mimeType?.startsWith("image/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const renderMediaIcon = useMemo(() => {
|
||||
if (isVideoFile()) {
|
||||
return <VideoCameraIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />;
|
||||
}
|
||||
|
||||
if (isAudioFile()) {
|
||||
return <MusicNoteIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />;
|
||||
}
|
||||
|
||||
if (isImageFile()) {
|
||||
return <PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />;
|
||||
}
|
||||
|
||||
return <DocumentIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />;
|
||||
}, [media]);
|
||||
|
||||
const renderMedia = useMemo(() => {
|
||||
if (isVideoFile() || isAudioFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isImageFile()) {
|
||||
return <img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [media]);
|
||||
|
||||
useEffect(() => {
|
||||
if (media.alt !== alt) {
|
||||
setAlt(media.alt);
|
||||
@@ -208,69 +260,71 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
<li className="group relative bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50">
|
||||
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
|
||||
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
|
||||
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
|
||||
{
|
||||
renderMediaIcon
|
||||
}
|
||||
</div>
|
||||
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
|
||||
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
|
||||
{ renderMedia }
|
||||
</div>
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-12`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`group-scope absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
<div className="flex items-center border border-transparent group-scope-hover:bg-gray-200 dark:group-scope-hover:bg-vulcan-200 group-scope-hover:border-gray-100 dark:group-scope-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
|
||||
<div className='hidden group-hover:inline-block h-5'>
|
||||
<QuickAction
|
||||
title='View media details'
|
||||
onClick={viewMediaDetails}>
|
||||
<EyeIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
<Menu as="div" className="relative z-10 flex text-left">
|
||||
<div className='hidden group-scope-hover:flex'>
|
||||
<QuickAction
|
||||
title='View media details'
|
||||
onClick={viewMediaDetails}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title='Edit metadata'
|
||||
onClick={updateMetadata}>
|
||||
<PencilIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
<QuickAction
|
||||
title='Edit metadata'
|
||||
onClick={updateMetadata}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={(viewData.data.metadataInsert && viewData.data.fieldName) ? `Insert image for your "${viewData.data.fieldName}" field` : `Insert image with markdown markup`}
|
||||
onClick={insertToArticle}>
|
||||
<PlusIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={(viewData.data.metadataInsert && viewData.data.fieldName) ? `Insert image for your "${viewData.data.fieldName}" field` : `Insert image with markdown markup`}
|
||||
onClick={insertToArticle}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
<QuickAction
|
||||
title='Insert snippet'
|
||||
onClick={insertSnippet}>
|
||||
<CodeIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QuickAction
|
||||
title='Copy media path'
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
|
||||
<QuickAction
|
||||
title='Insert snippet'
|
||||
onClick={insertSnippet}>
|
||||
<CodeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QuickAction
|
||||
title='Copy media path'
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<QuickAction
|
||||
title='Delete media file'
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
<QuickAction
|
||||
title='Delete media file'
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<Menu as="div" className="relative z-10 inline-block text-left h-5">
|
||||
<MenuButton title={`Menu`} />
|
||||
<ActionMenuButton title={`Menu`} />
|
||||
|
||||
<MenuItems widthClass='w-40'>
|
||||
<MenuItem
|
||||
|
||||
@@ -73,12 +73,15 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
|
||||
const {getRootProps, isDragActive} = useDropzone({
|
||||
onDrop,
|
||||
accept: 'image/*'
|
||||
accept: settings?.dashboardState.media.mimeTypes || ['image/*', 'video/*', 'audio/*'],
|
||||
onDropRejected: () => {
|
||||
Messenger.send(DashboardMessage.showWarning, 'Unsupported file type');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="w-full h-full" {...getRootProps()}>
|
||||
<div className="w-full h-full pb-6" {...getRootProps()}>
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
18
src/dashboardWebView/components/Menu/ActionMenuButton.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import {DotsVerticalIcon} from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IActionMenuButtonProps {
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const ActionMenuButton: React.FunctionComponent<IActionMenuButtonProps> = ({ title, disabled }: React.PropsWithChildren<IActionMenuButtonProps>) => {
|
||||
return (
|
||||
<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 ${disabled ? 'opacity-50' : ''}`}>
|
||||
<span className="sr-only">{title}</span>
|
||||
<DotsVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface IMenuItemProps {
|
||||
value?: any;
|
||||
isCurrent?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick: (value: any) => void;
|
||||
onClick: (value: any, e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
export const MenuItem: React.FunctionComponent<IMenuItemProps> = ({title, value, isCurrent, disabled, onClick}: React.PropsWithChildren<IMenuItemProps>) => {
|
||||
@@ -14,7 +14,7 @@ export const MenuItem: React.FunctionComponent<IMenuItemProps> = ({title, value,
|
||||
<Menu.Item>
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={() => onClick(value)}
|
||||
onClick={(e) => onClick(value, e)}
|
||||
className={`${!isCurrent ? `font-normal` : `font-bold`} text-gray-500 dark:text-whisper-900 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 hover:text-gray-700 dark:hover:text-whisper-600 dark:hover:bg-vulcan-100 disabled:bg-gray-500`}
|
||||
>
|
||||
{title}
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Fragment } from 'react';
|
||||
|
||||
export interface IMenuItemsProps {
|
||||
widthClass?: string;
|
||||
marginTopClass?: string;
|
||||
}
|
||||
|
||||
export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({widthClass, children}: React.PropsWithChildren<IMenuItemsProps>) => {
|
||||
export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({widthClass, marginTopClass, children}: React.PropsWithChildren<IMenuItemsProps>) => {
|
||||
return (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
@@ -17,7 +18,7 @@ export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({widthClass,
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className={`${widthClass || ""} origin-top-right absolute right-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`}>
|
||||
<Menu.Items className={`${widthClass || ""} ${marginTopClass || "mt-2"} origin-top-right absolute right-0 z-10 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`}>
|
||||
<div className="py-1">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
|
||||
export interface IQuickActionProps {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({title, onClick, children}: React.PropsWithChildren<IQuickActionProps>) => {
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './ActionMenuButton';
|
||||
export * from './MenuButton';
|
||||
export * from './MenuItem';
|
||||
export * from './MenuItems';
|
||||
export * from './QuickAction';
|
||||
|
||||
@@ -3,10 +3,13 @@ import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { Snippet, SnippetField, Snippets } from '../../../models';
|
||||
import { Snippet, Snippets } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { FormDialog } from '../Modals/FormDialog';
|
||||
import { NewForm } from './NewForm';
|
||||
@@ -20,6 +23,7 @@ export interface IItemProps {
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: React.PropsWithChildren<IItemProps>) => {
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
const [ showInsertDialog, setShowInsertDialog ] = useState(false);
|
||||
const [ showEditDialog, setShowEditDialog ] = useState(false);
|
||||
const [ showAlert, setShowAlert ] = React.useState(false);
|
||||
@@ -100,36 +104,63 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
|
||||
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
|
||||
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
<div className='group-hover:hidden'>
|
||||
<DotsHorizontalIcon className="w-4 h-4" />
|
||||
</div>
|
||||
<FeatureFlag
|
||||
features={mode?.features || []}
|
||||
flag={FEATURE_FLAG.dashboard.snippets.manage}
|
||||
alternative={(
|
||||
viewData?.data?.filePath ? (
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
<div className='group-hover:hidden'>
|
||||
<DotsHorizontalIcon className="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div className='hidden group-hover:flex space-x-2'>
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<>
|
||||
<button onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Insert snippet</span>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div className='hidden group-hover:flex'>
|
||||
<QuickAction
|
||||
title={`Insert snippet`}
|
||||
onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
)}
|
||||
>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
|
||||
<div className='group-hover:hidden'>
|
||||
<DotsHorizontalIcon className="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<button onClick={onOpenEdit}>
|
||||
<PencilIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Edit snippet</span>
|
||||
</button>
|
||||
<div className='hidden group-hover:flex'>
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<>
|
||||
<QuickAction
|
||||
title={`Insert snippet`}
|
||||
onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<button onClick={() => setShowAlert(true)}>
|
||||
<TrashIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Delete snippet</span>
|
||||
</button>
|
||||
<QuickAction
|
||||
title={`Edit snippet`}
|
||||
onClick={onOpenEdit}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={`Delete snippet`}
|
||||
onClick={() => setShowAlert(true)}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FeatureFlag>
|
||||
|
||||
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{snippet.description}</p>
|
||||
</li>
|
||||
|
||||
@@ -3,10 +3,12 @@ import { CodeIcon, PlusSmIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { FormDialog } from '../Modals/FormDialog';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
@@ -18,6 +20,7 @@ export interface ISnippetsProps {}
|
||||
export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.PropsWithChildren<ISnippetsProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
|
||||
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
|
||||
const [ snippetBody, setSnippetBody ] = useState<string>('');
|
||||
@@ -60,74 +63,78 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
return (
|
||||
<PageLayout
|
||||
header={(
|
||||
<div
|
||||
className="py-3 px-4 flex items-center justify-between border-b border-gray-300 dark:border-vulcan-100"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<div className="flex flex-1 justify-end">
|
||||
<button
|
||||
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500`}
|
||||
title={`Create new snippet`}
|
||||
onClick={() => setShowCreateDialog(true)}>
|
||||
<PlusSmIcon className={`mr-2 h-6 w-6`} />
|
||||
<span className={`text-sm`}>Create new snippet</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}>
|
||||
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<div className={`text-xl text-center mb-6`}>
|
||||
<p>Select the snippet to add to your content.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
snippetKeys && snippetKeys.length > 0 ? (
|
||||
<ul role="list" className={`grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}>
|
||||
{
|
||||
snippetKeys.map((snippetKey: any, index: number) => (
|
||||
<Item
|
||||
key={index}
|
||||
title={snippetKey}
|
||||
snippet={snippets[snippetKey]} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
) : (
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
<div className='flex flex-col items-center text-gray-500 dark:text-whisper-900'>
|
||||
<CodeIcon className='w-32 h-32' />
|
||||
<p className='text-3xl'>No snippets found</p>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.manage}>
|
||||
<div
|
||||
className="py-3 px-4 flex items-center justify-between border-b border-gray-300 dark:border-vulcan-100"
|
||||
aria-label="snippets header"
|
||||
>
|
||||
<div className="flex flex-1 justify-end">
|
||||
<button
|
||||
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500`}
|
||||
title={`Create new snippet`}
|
||||
onClick={() => setShowCreateDialog(true)}>
|
||||
<PlusSmIcon className={`mr-2 h-6 w-6`} />
|
||||
<span className={`text-sm`}>Create new snippet</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</FeatureFlag>
|
||||
)}>
|
||||
|
||||
{
|
||||
showCreateDialog && (
|
||||
<FormDialog
|
||||
title={`Create a snippet`}
|
||||
description={``}
|
||||
isSaveDisabled={!snippetTitle || !snippetBody}
|
||||
trigger={onSnippetAdd}
|
||||
dismiss={reset}
|
||||
okBtnText='Save'
|
||||
cancelBtnText='Cancel'>
|
||||
<div className="flex flex-col">
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<div className={`text-xl text-center mb-6`}>
|
||||
<p>Select the snippet to add to your content.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<NewForm
|
||||
title={snippetTitle}
|
||||
description={snippetDescription}
|
||||
body={snippetBody}
|
||||
onTitleUpdate={(value: string) => setSnippetTitle(value)}
|
||||
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
|
||||
onBodyUpdate={(value: string) => setSnippetBody(value)} />
|
||||
|
||||
</FormDialog>
|
||||
)
|
||||
}
|
||||
{
|
||||
snippetKeys && snippetKeys.length > 0 ? (
|
||||
<ul role="list" className={`grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}>
|
||||
{
|
||||
snippetKeys.map((snippetKey: any, index: number) => (
|
||||
<Item
|
||||
key={index}
|
||||
title={snippetKey}
|
||||
snippet={snippets[snippetKey]} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
) : (
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
<div className='flex flex-col items-center text-gray-500 dark:text-whisper-900'>
|
||||
<CodeIcon className='w-32 h-32' />
|
||||
<p className='text-3xl'>No snippets found</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showCreateDialog && (
|
||||
<FormDialog
|
||||
title={`Create a snippet`}
|
||||
description={``}
|
||||
isSaveDisabled={!snippetTitle || !snippetBody}
|
||||
trigger={onSnippetAdd}
|
||||
dismiss={reset}
|
||||
okBtnText='Save'
|
||||
cancelBtnText='Cancel'>
|
||||
|
||||
<NewForm
|
||||
title={snippetTitle}
|
||||
description={snippetDescription}
|
||||
body={snippetBody}
|
||||
onTitleUpdate={(value: string) => setSnippetTitle(value)}
|
||||
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
|
||||
onBodyUpdate={(value: string) => setSnippetBody(value)} />
|
||||
|
||||
</FormDialog>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
|
||||
</PageLayout>
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface ISpinnerProps {}
|
||||
|
||||
export const Spinner: React.FunctionComponent<ISpinnerProps> = (props: React.PropsWithChildren<ISpinnerProps>) => {
|
||||
return (
|
||||
<div className={`fixed top-12 left-0 right-0 bottom-0 w-full h-full flex flex-wrap items-center justify-center bg-white bg-opacity-10 z-50`}>
|
||||
<div className={`fixed top-0 left-0 right-0 bottom-0 w-full h-full flex flex-wrap items-center justify-center bg-white bg-opacity-10 z-50`}>
|
||||
<div className="loader ease-linear rounded-full border-8 border-t-8 border-gray-50 h-32 w-32" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,42 +3,53 @@ import { useRecoilState } from 'recoil';
|
||||
import { DashboardCommand } from '../DashboardCommand';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import { Page } from '../models/Page';
|
||||
import { DashboardViewAtom, LoadingAtom, SettingsAtom, ViewDataAtom } from '../state';
|
||||
import { DashboardViewAtom, LoadingAtom, SettingsAtom, ViewDataAtom, SearchReadyAtom, ModeAtom } from '../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import { NavigationType } from '../models';
|
||||
import { GeneralCommands } from '../../constants';
|
||||
|
||||
export default function useMessages() {
|
||||
const [loading, setLoading] = useRecoilState(LoadingAtom);
|
||||
const [pages, setPages] = useState<Page[]>([]);
|
||||
const [settings, setSettings] = useRecoilState(SettingsAtom);
|
||||
const [viewData, setViewData] = useRecoilState(ViewDataAtom);
|
||||
const [, setMode] = useRecoilState(ModeAtom);
|
||||
const [, setView] = useRecoilState(DashboardViewAtom);
|
||||
const [, setSearchReady] = useRecoilState(SearchReadyAtom);
|
||||
|
||||
Messenger.listen((message: MessageEvent<EventData<any>>) => {
|
||||
switch (message.data.command) {
|
||||
Messenger.listen((event: MessageEvent<EventData<any>>) => {
|
||||
const message = event.data;
|
||||
|
||||
switch (message.command) {
|
||||
case DashboardCommand.loading:
|
||||
setLoading(message.data.data);
|
||||
setLoading(message.data);
|
||||
break;
|
||||
case DashboardCommand.viewData:
|
||||
setViewData(message.data.data);
|
||||
if (message.data.data?.type === NavigationType.Media) {
|
||||
setViewData(message.data);
|
||||
if (message.data?.type === NavigationType.Media) {
|
||||
setView(NavigationType.Media);
|
||||
} else if (message.data.data?.type === NavigationType.Contents) {
|
||||
} else if (message.data?.type === NavigationType.Contents) {
|
||||
setView(NavigationType.Contents);
|
||||
} else if (message.data.data?.type === NavigationType.Data) {
|
||||
} else if (message.data?.type === NavigationType.Data) {
|
||||
setView(NavigationType.Data);
|
||||
} else if (message.data.data?.type === NavigationType.Snippets) {
|
||||
} else if (message.data?.type === NavigationType.Snippets) {
|
||||
setView(NavigationType.Snippets);
|
||||
}
|
||||
break;
|
||||
case DashboardCommand.settings:
|
||||
setSettings(message.data.data);
|
||||
setSettings(message.data);
|
||||
break;
|
||||
case DashboardCommand.pages:
|
||||
setPages(message.data.data);
|
||||
setPages(message.data);
|
||||
setLoading(false);
|
||||
break;
|
||||
case DashboardCommand.searchReady:
|
||||
setSearchReady(true);
|
||||
break;
|
||||
case GeneralCommands.setMode:
|
||||
setMode(message.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -47,6 +58,7 @@ export default function useMessages() {
|
||||
Messenger.send(DashboardMessage.getViewType);
|
||||
Messenger.send(DashboardMessage.getTheme);
|
||||
Messenger.send(DashboardMessage.getData);
|
||||
Messenger.send(DashboardMessage.getMode);
|
||||
}, ['']);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { SortOption } from '../constants/SortOption';
|
||||
import { Tab } from '../constants/Tab';
|
||||
import { Page } from '../models/Page';
|
||||
@@ -7,16 +7,9 @@ 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: [
|
||||
{ name: 'title', weight: 0.8 },
|
||||
{ name: 'slug', weight: 0.8 },
|
||||
{ name: 'description', weight: 0.5 }
|
||||
],
|
||||
includeScore: true,
|
||||
threshold: 0.1
|
||||
};
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
|
||||
export default function usePages(pages: Page[]) {
|
||||
const [ pageItems, setPageItems ] = useState<Page[]>([]);
|
||||
@@ -28,25 +21,8 @@ export default function usePages(pages: Page[]) {
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
|
||||
useEffect(() => {
|
||||
const processPages = useCallback((searchedPages: Page[]) => {
|
||||
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) {
|
||||
const fuse = new Fuse(pages, fuseOptions);
|
||||
const results = fuse.search(search);
|
||||
searchedPages = results.map(page => page.item);
|
||||
}
|
||||
|
||||
// Filter the pages
|
||||
let pagesToShow: Page[] = Object.assign([], searchedPages);
|
||||
@@ -115,7 +91,48 @@ export default function usePages(pages: Page[]) {
|
||||
}
|
||||
|
||||
setPageItems(pagesSorted);
|
||||
}, [ settings?.draftField, pages, tab, sorting, folder, search, tag, category ]);
|
||||
}, [ settings, tab, folder, search, tag, category, sorting ]);
|
||||
|
||||
|
||||
const searchListener = (message: MessageEvent<EventData<any>>) => {
|
||||
switch (message.data.command) {
|
||||
case DashboardMessage.searchPages:
|
||||
processPages(message.data.data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
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) {
|
||||
// const fuse = new Fuse(pages, fuseOptions);
|
||||
// const results = fuse.search(search);
|
||||
// searchedPages = results.map(page => page.item);
|
||||
|
||||
Messenger.send(DashboardMessage.searchPages, { query: search });
|
||||
} else {
|
||||
processPages(searchedPages);
|
||||
}
|
||||
}, [ settings?.draftField, pages, sorting, search ]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(searchListener);
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(searchListener);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
pageItems
|
||||
|
||||
@@ -34,15 +34,17 @@ export interface Settings {
|
||||
}
|
||||
|
||||
export interface DashboardState {
|
||||
contents: ViewState;
|
||||
contents: ContentsViewState;
|
||||
media: MediaViewState;
|
||||
}
|
||||
|
||||
export interface ViewState {
|
||||
export interface ContentsViewState {
|
||||
sorting: SortingOption | null | undefined;
|
||||
defaultSorting: string | null | undefined;
|
||||
tags: string | null | undefined;
|
||||
}
|
||||
|
||||
export interface MediaViewState extends ViewState {
|
||||
export interface MediaViewState extends ContentsViewState {
|
||||
selectedFolder: string | null | undefined;
|
||||
mimeTypes: string[] | null | undefined;
|
||||
}
|
||||
7
src/dashboardWebView/state/atom/ModeAtom.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
import { Mode } from '../../../models';
|
||||
|
||||
export const ModeAtom = atom<Mode | undefined>({
|
||||
key: 'ModeAtom',
|
||||
default: undefined
|
||||
});
|
||||
6
src/dashboardWebView/state/atom/SearchReadyAtom.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const SearchReadyAtom = atom<boolean>({
|
||||
key: 'SearchReadyAtom',
|
||||
default: false
|
||||
});
|
||||
@@ -6,8 +6,10 @@ export * from './LightboxAtom';
|
||||
export * from './LoadingAtom';
|
||||
export * from './MediaFoldersAtom';
|
||||
export * from './MediaTotalAtom';
|
||||
export * from './ModeAtom';
|
||||
export * from './PageAtom';
|
||||
export * from './SearchAtom';
|
||||
export * from './SearchReadyAtom';
|
||||
export * from './SelectedMediaFolderAtom';
|
||||
export * from './SettingsAtom';
|
||||
export * from './SortingAtom';
|
||||
|
||||
@@ -7,6 +7,7 @@ import { TagType } from '../panelWebView/TagType';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { ModeListener } from '../listeners/general';
|
||||
|
||||
export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
public static readonly viewType = "frontMatter.explorer";
|
||||
@@ -80,6 +81,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
ScriptListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
TaxonomyListener.process(msg);
|
||||
ModeListener.process(msg);
|
||||
});
|
||||
|
||||
webviewView.onDidChangeVisibility(() => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { PagesListener } from './listeners/dashboard';
|
||||
import { Backers } from './commands/Backers';
|
||||
import { DataListener, SettingsListener } from './listeners/panel';
|
||||
import { NavigationType } from './dashboardWebView/models';
|
||||
import { ModeSwitch } from './services/ModeSwitch';
|
||||
|
||||
let frontMatterStatusBar: vscode.StatusBarItem;
|
||||
let statusDebouncer: { (fnc: any, time: number): void; };
|
||||
@@ -179,6 +180,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
SettingsListener.getSettings();
|
||||
DataListener.getFoldersAndFiles();
|
||||
MarkdownFoldingProvider.triggerHighlighting();
|
||||
ModeSwitch.register();
|
||||
});
|
||||
|
||||
// Create the status bar
|
||||
@@ -220,6 +222,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// What you see, is what you get
|
||||
Wysiwyg.registerCommands(subscriptions);
|
||||
|
||||
// Mode switching
|
||||
ModeSwitch.register();
|
||||
|
||||
// Diagnostics
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show));
|
||||
|
||||
|
||||
@@ -289,6 +289,8 @@ export class ArticleHelper {
|
||||
* @returns The new file path
|
||||
*/
|
||||
public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): string | undefined {
|
||||
FrontMatterParser.currentContent = null;
|
||||
|
||||
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType";
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { processKnownPlaceholders } from './PlaceholderHelper';
|
||||
|
||||
|
||||
export class ContentType {
|
||||
|
||||
/**
|
||||
@@ -100,7 +99,6 @@ export class ContentType {
|
||||
* @returns
|
||||
*/
|
||||
private static async create(contentType: IContentType, folderPath: string) {
|
||||
|
||||
const titleValue = await Questions.ContentTitle();
|
||||
if (!titleValue) {
|
||||
return;
|
||||
@@ -154,8 +152,14 @@ export class ContentType {
|
||||
if (field.type === "fields") {
|
||||
data[field.name] = this.processFields(field, titleValue, {});
|
||||
} else {
|
||||
data[field.name] = field.default ? processKnownPlaceholders(field.default, titleValue, dateFormat) : "";
|
||||
data[field.name] = field.default ? ArticleHelper.processCustomPlaceholders(data[field.name], titleValue) : "";
|
||||
const defaultValue = field.default;
|
||||
|
||||
if (typeof defaultValue === "string") {
|
||||
data[field.name] = processKnownPlaceholders(defaultValue, titleValue, dateFormat);
|
||||
data[field.name] = ArticleHelper.processCustomPlaceholders(data[field.name], titleValue);
|
||||
} else {
|
||||
data[field.name] = typeof defaultValue !== "undefined" ? defaultValue : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ export class CustomScript {
|
||||
if (script.bulk) {
|
||||
// Run script on all files
|
||||
CustomScript.bulkRun(wsPath, script);
|
||||
} else if (path) {
|
||||
// Run script for provided path
|
||||
CustomScript.singleRun(wsPath, script, path);
|
||||
} else {
|
||||
// Run script on current file.
|
||||
CustomScript.singleRun(wsPath, script);
|
||||
@@ -38,18 +41,31 @@ export class CustomScript {
|
||||
}
|
||||
}
|
||||
|
||||
private static async singleRun(wsPath: string, script: ICustomScript): Promise<void> {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) return;
|
||||
private static async singleRun(wsPath: string, script: ICustomScript, path: string | null = null): Promise<void> {
|
||||
let articlePath: string | null = path;
|
||||
let article: ParsedFrontMatter | null = null;
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!path) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) return;
|
||||
|
||||
if (article) {
|
||||
const output = await CustomScript.runScript(wsPath, article, editor.document.uri.fsPath, script);
|
||||
|
||||
CustomScript.showOutput(output, script);
|
||||
articlePath = editor.document.uri.fsPath;
|
||||
article = ArticleHelper.getFrontMatter(editor);
|
||||
} else {
|
||||
Notifications.warning(`${script.title}: Current article couldn't be retrieved.`);
|
||||
article = ArticleHelper.getFrontMatterByPath(path);
|
||||
}
|
||||
|
||||
if (articlePath && article) {
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: `Executing: ${script.title}`,
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
const output = await CustomScript.runScript(wsPath, article, articlePath as string, script);
|
||||
CustomScript.showOutput(output, script);
|
||||
});
|
||||
} else {
|
||||
Notifications.warning(`${script.title}: Article couldn't be retrieved.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { basename, join } from "path";
|
||||
import { workspace } from "vscode";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Template } from "../commands/Template";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT } from "../constants";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
|
||||
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
|
||||
import { CustomScript, DraftField, ScriptType, Snippets, SortingSetting, TaxonomyType } from "../models";
|
||||
import { DataFile } from "../models/DataFile";
|
||||
@@ -45,12 +45,14 @@ export class DashboardSettings {
|
||||
dashboardState: {
|
||||
contents: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
|
||||
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT)
|
||||
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT),
|
||||
tags: Settings.get<string>(SETTING_DASHBOARD_CONTENT_TAGS),
|
||||
},
|
||||
media: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
|
||||
defaultSorting: Settings.get<string>(SETTING_MEDIA_SORTING_DEFAULT),
|
||||
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace")
|
||||
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace"),
|
||||
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
|
||||
}
|
||||
},
|
||||
dataFiles: await this.getDataFiles(),
|
||||
|
||||
@@ -82,6 +82,13 @@ export class Extension {
|
||||
return this.ctx.extension.packageJSON.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayName of the extension
|
||||
*/
|
||||
public get displayName(): string {
|
||||
return this.ctx.extension.packageJSON.displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension's version
|
||||
*/
|
||||
|
||||
@@ -7,8 +7,8 @@ export class Logger {
|
||||
private static channel: OutputChannel | null = null;
|
||||
|
||||
private constructor() {
|
||||
const title = Extension.getInstance().title;
|
||||
Logger.channel = window.createOutputChannel(title);
|
||||
const displayName = Extension.getInstance().displayName;
|
||||
Logger.channel = window.createOutputChannel(displayName);
|
||||
}
|
||||
|
||||
public static getInstance(): Logger {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { decodeBase64Image, Extension, MediaLibrary, Notifications, parseWinPath, Settings, Sorting } from ".";
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { DEFAULT_CONTENT_TYPE, ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER } from "../constants";
|
||||
import { DEFAULT_CONTENT_TYPE, ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
|
||||
import { SortingOption } from "../dashboardWebView/models";
|
||||
import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models";
|
||||
import { basename, extname, join, parse, dirname, relative } from "path";
|
||||
@@ -12,6 +12,7 @@ import { EditorHelper } from "@estruyf/vscode";
|
||||
import { SortOption } from "../dashboardWebView/constants/SortOption";
|
||||
import { DataListener, MediaListener } from "../listeners/panel";
|
||||
import { ArticleHelper } from "./ArticleHelper";
|
||||
import { lookup } from "mime-types";
|
||||
|
||||
|
||||
export class MediaHelpers {
|
||||
@@ -109,10 +110,12 @@ export class MediaHelpers {
|
||||
files = files.map((file) => {
|
||||
try {
|
||||
const metadata = MediaLibrary.getInstance().get(file.fsPath);
|
||||
const mimeType = lookup(file.fsPath);
|
||||
|
||||
return {
|
||||
...file,
|
||||
dimensions: imageSize(file.fsPath),
|
||||
dimensions: mimeType && mimeType.startsWith('image/') ? imageSize(file.fsPath) : undefined,
|
||||
mimeType: lookup(file.fsPath) || '',
|
||||
...metadata
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -356,9 +359,16 @@ export class MediaHelpers {
|
||||
* Filter the media files
|
||||
*/
|
||||
private static filterMedia(files: Uri[]) {
|
||||
let mimeTypes = Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES) || ["image/*", "video/*", "audio/*"];
|
||||
mimeTypes = mimeTypes.map(type => type.toLowerCase());
|
||||
|
||||
return files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext.toLowerCase());
|
||||
const type = lookup(file.fsPath);
|
||||
if (type) {
|
||||
const isValid = this.acceptMimeType(type, mimeTypes);
|
||||
return isValid;
|
||||
}
|
||||
return false;
|
||||
}).map((file) => ({
|
||||
filename: basename(file.fsPath),
|
||||
fsPath: file.fsPath,
|
||||
@@ -379,4 +389,27 @@ export class MediaHelpers {
|
||||
|
||||
return Object.assign([], files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate mimetype of the selected file
|
||||
* @param crntType
|
||||
* @param supportedTypes
|
||||
* @returns
|
||||
*/
|
||||
private static acceptMimeType(crntType: string, supportedTypes: string[]) {
|
||||
if (crntType && supportedTypes) {
|
||||
const mimeType = crntType.toLowerCase();
|
||||
const baseMimeType = mimeType.replace(/\/.*$/, '')
|
||||
|
||||
return supportedTypes.some(type => {
|
||||
const validType = type.trim().toLowerCase()
|
||||
if (validType.endsWith('/*')) {
|
||||
// This is something like a image/* mime type
|
||||
return baseMimeType === validType.replace(/\/.*$/, '')
|
||||
}
|
||||
return mimeType === validType
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Uri, workspace, window } from "vscode";
|
||||
import { Logger } from "./Logger";
|
||||
import { Notifications } from "./Notifications";
|
||||
|
||||
export const openFileInEditor = async (filePath: string) => {
|
||||
@@ -8,6 +9,7 @@ export const openFileInEditor = async (filePath: string) => {
|
||||
await window.showTextDocument(doc, 1, false);
|
||||
} catch (e) {
|
||||
Notifications.error(`Couldn't open the file.`);
|
||||
Logger.error(`${filePath}: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { Dashboard } from "../../commands/Dashboard";
|
||||
import { ExtensionState } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Extension } from "../../helpers";
|
||||
import { Extension, Notifications } from "../../helpers";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ export class DashboardListener extends BaseListener {
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
|
||||
break;
|
||||
case DashboardMessage.showWarning:
|
||||
Notifications.warning(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,63 @@
|
||||
import { isValidFile } from '../../helpers/isValidFile';
|
||||
import { existsSync } from "fs";
|
||||
import { existsSync, unlinkSync } from "fs";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from "vscode";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { Folders } from "../../commands/Folders";
|
||||
import { COMMAND_NAME, DefaultFields, SETTING_CONTENT_STATIC_FOLDER, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants";
|
||||
import { COMMAND_NAME, DefaultFields, ExtensionState, SETTING_CONTENT_STATIC_FOLDER, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_FIELD_GROUPS } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Page } from "../../dashboardWebView/models";
|
||||
import { ArticleHelper, Logger, Settings } from "../../helpers";
|
||||
import { ArticleHelper, Extension, Logger, Settings } from "../../helpers";
|
||||
import { ContentType } from "../../helpers/ContentType";
|
||||
import { DateHelper } from "../../helpers/DateHelper";
|
||||
import { Notifications } from "../../helpers/Notifications";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { Field, FieldType } from '../../models';
|
||||
import { Field, FieldGroup, FieldType } from '../../models';
|
||||
import { DataListener } from '../panel';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
|
||||
export class PagesListener extends BaseListener {
|
||||
private static watchers: { [path: string]: FileSystemWatcher } = {};
|
||||
private static lastPages: Page[] = [];
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static async process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getData:
|
||||
this.getPagesData();
|
||||
break;
|
||||
case DashboardMessage.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case DashboardMessage.createByContentType:
|
||||
await commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
break;
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
case DashboardMessage.refreshPages:
|
||||
this.getPagesData(true);
|
||||
break;
|
||||
case DashboardMessage.searchPages:
|
||||
this.searchPages(msg.data);
|
||||
break;
|
||||
case DashboardMessage.deleteFile:
|
||||
this.deletePage(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved file watcher
|
||||
* @returns
|
||||
*/
|
||||
public static saveFileWatcher() {
|
||||
return workspace.onDidSaveTextDocument((doc: TextDocument) => {
|
||||
if (ArticleHelper.isSupportedFile(doc)) {
|
||||
@@ -63,33 +100,32 @@ export class PagesListener extends BaseListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
* Delete a page
|
||||
* @param path
|
||||
*/
|
||||
public static async process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getData:
|
||||
this.getPagesData();
|
||||
break;
|
||||
case DashboardMessage.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case DashboardMessage.createByContentType:
|
||||
await commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
break;
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
case DashboardMessage.refreshPages:
|
||||
this.getPagesData();
|
||||
break;
|
||||
private static async deletePage(path: string) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(`Deleting file: ${path}`)
|
||||
|
||||
unlinkSync(path);
|
||||
|
||||
this.lastPages = this.lastPages.filter(p => p.fmFilePath !== path);
|
||||
this.sendPageData(this.lastPages);
|
||||
|
||||
const ext = Extension.getInstance();
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, this.lastPages, "workspace");
|
||||
}
|
||||
|
||||
/**
|
||||
* Watcher for processing page updates
|
||||
* @param file
|
||||
*/
|
||||
private static async watcherExec(file: Uri) {
|
||||
if (Dashboard.isOpen) {
|
||||
const ext = Extension.getInstance();
|
||||
Logger.info(`File watcher execution for: ${file.fsPath}`)
|
||||
|
||||
const pageIdx = this.lastPages.findIndex(p => p.fmFilePath === file.fsPath);
|
||||
@@ -99,10 +135,11 @@ export class PagesListener extends BaseListener {
|
||||
const updatedPage = this.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder);
|
||||
if (updatedPage) {
|
||||
this.lastPages[pageIdx] = updatedPage;
|
||||
this.sendMsg(DashboardCommand.pages, this.lastPages);
|
||||
this.sendPageData(this.lastPages);
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, this.lastPages, "workspace");
|
||||
}
|
||||
} else {
|
||||
this.getPagesData();
|
||||
this.getPagesData(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +147,18 @@ export class PagesListener extends BaseListener {
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
private static async getPagesData() {
|
||||
private static async getPagesData(clear: boolean = false) {
|
||||
const ext = Extension.getInstance();
|
||||
|
||||
// Get data from the cache
|
||||
if (!clear) {
|
||||
const cachedPages = await ext.getState<Page[]>(ExtensionState.Dashboard.Pages.Cache, "workspace");
|
||||
if (cachedPages) {
|
||||
this.sendPageData(cachedPages);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the dashboard with the fresh data
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
|
||||
@@ -135,11 +183,63 @@ export class PagesListener extends BaseListener {
|
||||
}
|
||||
|
||||
this.lastPages = pages;
|
||||
this.sendMsg(DashboardCommand.pages, pages);
|
||||
this.sendPageData(pages);
|
||||
|
||||
this.sendMsg(DashboardCommand.searchReady, true);
|
||||
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace");
|
||||
await this.createSearchIndex(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the page data without the body
|
||||
*/
|
||||
private static sendPageData(pages: Page[]) {
|
||||
// Omit the body content
|
||||
this.sendMsg(DashboardCommand.pages, pages.map(p => {
|
||||
const { fmBody, ...rest } = p;
|
||||
return rest;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the search index for the pages
|
||||
* @param pages
|
||||
*/
|
||||
private static async createSearchIndex(pages: Page[]) {
|
||||
const pagesIndex = Fuse.createIndex([ 'title', 'slug', 'description', 'fmBody' ], pages);
|
||||
await Extension.getInstance().setState(ExtensionState.Dashboard.Pages.Index, pagesIndex, "workspace");
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the pages
|
||||
*/
|
||||
private static async searchPages(data: { query: string }) {
|
||||
const fuseOptions: Fuse.IFuseOptions<Page> = {
|
||||
keys: [
|
||||
{ name: 'title', weight: 1 },
|
||||
{ name: 'fmBody', weight: 1, },
|
||||
{ name: 'slug', weight: 0.5 },
|
||||
{ name: 'description', weight: 0.5 },
|
||||
],
|
||||
includeScore: true,
|
||||
ignoreLocation: true,
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
const pagesIndex = await Extension.getInstance().getState<Fuse.FuseIndex<Page>>(ExtensionState.Dashboard.Pages.Index, "workspace");
|
||||
const fuse = new Fuse(this.lastPages, fuseOptions, pagesIndex);
|
||||
const results = fuse.search(data.query || "");
|
||||
const pageResults = results.map(page => page.item);
|
||||
|
||||
this.sendMsg(DashboardCommand.searchPages, pageResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fresh page data
|
||||
*/
|
||||
public static refresh() {
|
||||
this.getPagesData();
|
||||
this.getPagesData(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +278,7 @@ export class PagesListener extends BaseListener {
|
||||
fmPreviewImage: "",
|
||||
fmTags: [],
|
||||
fmCategories: [],
|
||||
fmBody: article?.content || "",
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
@@ -222,6 +323,17 @@ export class PagesListener extends BaseListener {
|
||||
}
|
||||
|
||||
crntPageData = crntPageData[previewField];
|
||||
|
||||
// Check for preview image in block data
|
||||
if (crntPageData instanceof Array && crntPageData.length > 0) {
|
||||
// Get the first field block that contains the next field data
|
||||
const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]);
|
||||
if (fieldData) {
|
||||
crntPageData = fieldData;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,9 +438,52 @@ export class PagesListener extends BaseListener {
|
||||
if (subFields.length > 0) {
|
||||
return [...parents, field.name, ...subFields];
|
||||
}
|
||||
} else if (field.type === "block") {
|
||||
const subFields = this.findPreviewInBlockField(field);
|
||||
if (subFields.length > 0) {
|
||||
return [...parents, field.name, ...subFields];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for the preview image in the block field
|
||||
* @param field
|
||||
* @param parents
|
||||
* @returns
|
||||
*/
|
||||
private static findPreviewInBlockField(field: Field) {
|
||||
const groups = field.fieldGroup && Array.isArray(field.fieldGroup) ? field.fieldGroup : [field.fieldGroup];
|
||||
if (!groups) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const blocks = Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS);
|
||||
if (!blocks) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let found = false;
|
||||
for (const group of groups) {
|
||||
const block = blocks.find(block => block.id === group);
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newParents: string[] = [];
|
||||
if (!found) {
|
||||
newParents = this.findPreviewField(block?.fields, []);
|
||||
}
|
||||
|
||||
if (newParents.length > 0) {
|
||||
found = true;
|
||||
return newParents;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { SETTING_CONTENT_SNIPPETS } from "../../constants";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Notifications, Settings } from "../../helpers";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { SettingsListener } from "./SettingsListener";
|
||||
|
||||
|
||||
export class SnippetListener extends BaseListener {
|
||||
@@ -47,7 +48,8 @@ export class SnippetListener extends BaseListener {
|
||||
fields: fields || []
|
||||
};
|
||||
|
||||
Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
SettingsListener.getSettings();
|
||||
}
|
||||
|
||||
private static async updateSnippet(data: any) {
|
||||
@@ -58,7 +60,8 @@ export class SnippetListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
SettingsListener.getSettings();
|
||||
}
|
||||
|
||||
private static async insertSnippet(data: any) {
|
||||
|
||||
28
src/listeners/general/BaseListener.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { GeneralCommands } from './../../constants';
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { ExplorerView } from "../../explorerView/ExplorerView";
|
||||
import { Extension } from "../../helpers";
|
||||
import { Logger } from "../../helpers/Logger";
|
||||
|
||||
|
||||
export abstract class BaseListener {
|
||||
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: GeneralCommands, data: any) {
|
||||
Logger.info(`Sending message to webview (panel&dashboard): ${command}`);
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extPath);
|
||||
|
||||
panel.sendMessage({ command: command as any, data });
|
||||
|
||||
Dashboard.postWebviewMessage({ command: command as any, data });
|
||||
}
|
||||
}
|
||||
58
src/listeners/general/ModeListener.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ModeSwitch } from './../../services/ModeSwitch';
|
||||
import { CONTEXT, FEATURE_FLAG, GeneralCommands, SETTING_GLOBAL_MODES } from '../../constants';
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Mode } from '../../models';
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { Settings } from "../../helpers";
|
||||
import { commands } from 'vscode';
|
||||
|
||||
|
||||
export class ModeListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage | CommandToCode, data: any }) {
|
||||
super.process(msg as any);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getMode:
|
||||
case CommandToCode.getMode:
|
||||
this.getMode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static async getMode() {
|
||||
const modes = Settings.get<Mode[]>(SETTING_GLOBAL_MODES);
|
||||
if (!modes || modes.length === 0) {
|
||||
await this.resetEnablement();
|
||||
return;
|
||||
}
|
||||
|
||||
const activeMode = ModeSwitch.getMode();
|
||||
if (activeMode) {
|
||||
const mode = modes.find(m => m.id === activeMode);
|
||||
this.sendMsg(GeneralCommands.setMode as any, mode);
|
||||
|
||||
// Check the commands that need to be enabled/disabled
|
||||
const snippetsView = mode?.features.find(f => f === FEATURE_FLAG.dashboard.snippets.view);
|
||||
const dataView = mode?.features.find(f => f === FEATURE_FLAG.dashboard.data.view);
|
||||
|
||||
await commands.executeCommand('setContext', CONTEXT.isSnippetsDashboardEnabled, !!snippetsView);
|
||||
await commands.executeCommand('setContext', CONTEXT.isDataDashboardEnabled, !!dataView);
|
||||
} else {
|
||||
this.sendMsg(GeneralCommands.setMode as any, undefined);
|
||||
|
||||
// Enable dashboards
|
||||
await this.resetEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
public static async resetEnablement() {
|
||||
await commands.executeCommand('setContext', CONTEXT.isSnippetsDashboardEnabled, true);
|
||||
await commands.executeCommand('setContext', CONTEXT.isDataDashboardEnabled, true);
|
||||
}
|
||||
}
|
||||
1
src/listeners/general/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ModeListener';
|
||||
@@ -108,9 +108,9 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(DataListener.lastMetadataUpdate) !== JSON.stringify(updatedMetadata)) {
|
||||
// if (JSON.stringify(DataListener.lastMetadataUpdate) !== JSON.stringify(updatedMetadata)) {
|
||||
this.sendMsg(Command.metadata, updatedMetadata);
|
||||
}
|
||||
// }
|
||||
|
||||
DataListener.lastMetadataUpdate = updatedMetadata;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface MediaInfo {
|
||||
dimensions?: ISizeCalculationResult | undefined;
|
||||
caption?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
mimeType?: string | undefined;
|
||||
mtime?: Date;
|
||||
ctime?: Date;
|
||||
size?: number;
|
||||
|
||||
6
src/models/Mode.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
export interface Mode {
|
||||
id: string;
|
||||
features: string[];
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export * from './DataType';
|
||||
export * from './DraftField';
|
||||
export * from './Framework';
|
||||
export * from './MediaPaths';
|
||||
export * from './Mode';
|
||||
export * from './PanelSettings';
|
||||
export * from './Snippets';
|
||||
export * from './SortOrder';
|
||||
|
||||
@@ -32,4 +32,5 @@ export enum CommandToCode {
|
||||
updateStartCommand = "update-start-command",
|
||||
getImageUrl = "get-image-url",
|
||||
updatePlaceholder = "update-placeholder",
|
||||
getMode = "get-mode",
|
||||
}
|
||||
@@ -9,13 +9,15 @@ import { FolderAndFiles } from './components/FolderAndFiles';
|
||||
import { Metadata } from './components/Metadata';
|
||||
import { SponsorMsg } from './components/SponsorMsg';
|
||||
import useMessages from './hooks/useMessages';
|
||||
import { FeatureFlag } from '../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../constants/Features';
|
||||
|
||||
export interface IViewPanelProps {
|
||||
}
|
||||
|
||||
export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React.PropsWithChildren<IViewPanelProps>) => {
|
||||
const { loading, mediaSelecting, metadata, settings, folderAndFiles, focusElm, unsetFocus } = useMessages();
|
||||
|
||||
const { loading, mediaSelecting, metadata, settings, folderAndFiles, focusElm, unsetFocus, mode } = useMessages();
|
||||
|
||||
if (mediaSelecting) {
|
||||
return (
|
||||
<div className="frontmatter media_selection">
|
||||
@@ -32,31 +34,52 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
|
||||
|
||||
if (!metadata || Object.keys(metadata || {}).length === 0) {
|
||||
return (
|
||||
<BaseView settings={settings} folderAndFiles={folderAndFiles} />
|
||||
<BaseView mode={mode} settings={settings} folderAndFiles={folderAndFiles} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="frontmatter">
|
||||
<div className={`ext_actions`}>
|
||||
<GlobalSettings settings={settings} />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.globalSettings}>
|
||||
<GlobalSettings settings={settings} />
|
||||
</FeatureFlag>
|
||||
|
||||
{
|
||||
settings && settings.seo && <SeoStatus seo={settings.seo} data={metadata} />
|
||||
settings && settings.seo && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.seo}>
|
||||
<SeoStatus
|
||||
seo={settings.seo}
|
||||
data={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus} />
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
{
|
||||
settings && metadata && <Actions metadata={metadata} settings={settings} />
|
||||
settings && metadata && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.actions}>
|
||||
<Actions metadata={metadata} settings={settings} />
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus} />
|
||||
|
||||
<FolderAndFiles data={folderAndFiles} />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.metadata}>
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus} />
|
||||
</FeatureFlag>
|
||||
|
||||
<OtherActions settings={settings} isFile={true} />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
|
||||
<FolderAndFiles data={folderAndFiles} />
|
||||
</FeatureFlag>
|
||||
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.otherActions}>
|
||||
<OtherActions settings={settings} isFile={true} />
|
||||
</FeatureFlag>
|
||||
</div>
|
||||
|
||||
<SponsorMsg isBacker={settings?.isBacker} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { CustomScript, FolderInfo, PanelSettings } from '../../models';
|
||||
import { CustomScript, FolderInfo, Mode, PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Collapsible } from './Collapsible';
|
||||
@@ -8,13 +8,16 @@ import { OtherActions } from './OtherActions';
|
||||
import { FolderAndFiles } from './FolderAndFiles';
|
||||
import { SponsorMsg } from './SponsorMsg';
|
||||
import { StartServerButton } from './StartServerButton';
|
||||
import { FeatureFlag } from '../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../constants/Features';
|
||||
|
||||
export interface IBaseViewProps {
|
||||
settings: PanelSettings | undefined;
|
||||
folderAndFiles: FolderInfo[] | undefined;
|
||||
mode: Mode | undefined;
|
||||
}
|
||||
|
||||
const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndFiles}: React.PropsWithChildren<IBaseViewProps>) => {
|
||||
const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndFiles, mode}: React.PropsWithChildren<IBaseViewProps>) => {
|
||||
|
||||
const openDashboard = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.openDashboard);
|
||||
@@ -41,26 +44,34 @@ const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndF
|
||||
return (
|
||||
<div className="frontmatter">
|
||||
<div className={`ext_actions`}>
|
||||
<GlobalSettings settings={settings} isBase />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.globalSettings}>
|
||||
<GlobalSettings settings={settings} isBase />
|
||||
</FeatureFlag>
|
||||
|
||||
<Collapsible id={`base_actions`} title="Actions">
|
||||
<div className={`base__actions`}>
|
||||
<button onClick={openDashboard}>Open dashboard</button>
|
||||
<StartServerButton settings={settings} />
|
||||
<button onClick={initProject} disabled={settings?.isInitialized}>Initialize project</button>
|
||||
<button onClick={createContent} disabled={!settings?.isInitialized}>Create new content</button>
|
||||
<button onClick={openPreview} disabled={!settings?.preview?.host}>Open site preview</button>
|
||||
{
|
||||
customActions.map((script) => (
|
||||
<button key={script.title} onClick={() => runBulkScript(script)}>{ script.title }</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Collapsible>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.actions}>
|
||||
<Collapsible id={`base_actions`} title="Actions">
|
||||
<div className={`base__actions`}>
|
||||
<button onClick={openDashboard}>Open dashboard</button>
|
||||
<StartServerButton settings={settings} />
|
||||
<button onClick={initProject} disabled={settings?.isInitialized}>Initialize project</button>
|
||||
<button onClick={createContent} disabled={!settings?.isInitialized}>Create new content</button>
|
||||
<button onClick={openPreview} disabled={!settings?.preview?.host}>Open site preview</button>
|
||||
{
|
||||
customActions.map((script) => (
|
||||
<button key={script.title} onClick={() => runBulkScript(script)}>{ script.title }</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Collapsible>
|
||||
</FeatureFlag>
|
||||
|
||||
<FolderAndFiles data={folderAndFiles} isBase />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
|
||||
<FolderAndFiles data={folderAndFiles} isBase />
|
||||
</FeatureFlag>
|
||||
|
||||
<OtherActions settings={settings} isFile={false} isBase />
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.otherActions}>
|
||||
<OtherActions settings={settings} isFile={false} isBase />
|
||||
</FeatureFlag>
|
||||
</div>
|
||||
|
||||
<SponsorMsg isBacker={settings?.isBacker} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { RocketIcon } from '../Icons/RocketIcon';
|
||||
import { ToggleIcon } from '../Icons/ToggleIcon';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IToggleProps {
|
||||
@@ -26,7 +27,7 @@ export const Toggle: React.FunctionComponent<IToggleProps> = ({label, checked, o
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<RocketIcon /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
<ToggleIcon /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
|
||||
@@ -6,6 +6,45 @@ export interface IFrontMatterIconProps {
|
||||
|
||||
export const FrontMatterIcon: React.FunctionComponent<IFrontMatterIconProps> = ({className}: React.PropsWithChildren<IFrontMatterIconProps>) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={className || ""} viewBox="0 0 30 30"><path d="M4,11.4H2.2V2.9H5.4v2H4V6.1H5.3V8H4Z" transform="translate(1 1)" fill="currentcolor"/><path d="M10.9,11.4H9l-.9-3V8.2C8,8.1,8,8,7.9,7.8v3.6H6.1V2.9H8a2.88,2.88,0,0,1,1.9.6,3.11,3.11,0,0,1,.8,2.2A2.25,2.25,0,0,1,9.6,7.8ZM8,6.8h.1a.55.55,0,0,0,.5-.3,1.88,1.88,0,0,0,.2-.8c0-.6-.3-1-.8-1H8Z" transform="translate(1 1)" fill="currentcolor"/><path d="M16.5,7.2a6.08,6.08,0,0,1-.7,3.2A2.14,2.14,0,0,1,14,11.6a2.09,2.09,0,0,1-1.7-.9,5.84,5.84,0,0,1-.9-3.5,5.84,5.84,0,0,1,.9-3.5A2.09,2.09,0,0,1,14,2.8,2.16,2.16,0,0,1,15.9,4,8.24,8.24,0,0,1,16.5,7.2Zm-1.9,0c0-1.5-.2-2.3-.7-2.3-.2,0-.4.2-.5.6a6.53,6.53,0,0,0-.2,1.7,7.18,7.18,0,0,0,.2,1.7c.1.4.3.6.5.6s.4-.2.5-.6A7.93,7.93,0,0,0,14.6,7.2Z" transform="translate(1 1)" fill="currentcolor"/><path d="M17.2,11.4V2.9H19l.9,3c.1.1.1.3.2.6s.1.5.2.8l.2.7c-.1-.7-.1-1.4-.2-1.9a6.64,6.64,0,0,1-.1-1.3V2.9H22v8.5H20.3l-.9-3.1-.3-.9c-.1-.3-.1-.6-.2-.8,0,.6.1,1.1.1,1.6v3.4H17.2Z" transform="translate(1 1)" fill="currentcolor"/><path d="M25.3,11.4H23.5V4.9h-1v-2h3.9v2H25.3Z" transform="translate(1 1)" fill="currentcolor"/><rect x="1" y="1" width="28" height="28" fill="none" stroke="currentcolor" strokeMiterlimit="10" strokeWidth="2"/><path d="M2.9,17h.9l.6,3a5,5,0,0,1,.2,1.2c.1.4.1.8.2,1.2v-.2l.2-.9.1-.8.1-.5.6-3h.9l.7,7.5h-1l-.2-2.6V19.5h0v.1a.9.9,0,0,1-.1.5c-.1.2,0,.2-.1.3l-.1.7v.3l-.6,3.3H4.5l-.6-2.8a5.16,5.16,0,0,1-.2-1.1c-.1-.4-.1-.8-.2-1.2l-.3,5.2h-1Z" transform="translate(1 1)" fill="currentcolor"/><path d="M9.3,17h.8l1.6,7.5h-1L10.4,23H8.9l-.3,1.5h-1Zm1,5.2L10,21c-.1-.8-.3-1.7-.4-2.6a6.75,6.75,0,0,1-.2,1.4l-.3,1.5-.2,1h1.4Z" transform="translate(1 1)" fill="currentcolor"/><path d="M11.5,17h3.3v.9H13.7v6.7h-1V17.9H11.5Z" transform="translate(1 1)" fill="currentcolor"/><path d="M14.8,17h3.3v.9H17v6.7H16V17.9H14.8Z" transform="translate(1 1)" fill="currentcolor"/><path d="M18.7,17h2.7v.9H19.7v2.4h1.5v.9H19.7v2.6h1.7v.9H18.7Z" transform="translate(1 1)" fill="currentcolor"/><path d="M22.3,17h1.3c.6,0,1,.1,1.2.4a2.35,2.35,0,0,1,.5,1.6,2.5,2.5,0,0,1-.3,1.3,1.24,1.24,0,0,1-.8.6l1.4,3.7h-1l-1.4-3.7v3.7h-1V17Zm1,3.3c.4,0,.7-.1.8-.3s.2-.5.2-.9a1.27,1.27,0,0,0-.1-.6c-.1-.2-.1-.3-.2-.4s-.2-.2-.3-.2-.3-.1-.4-.1h-.2v2.5Z" transform="translate(1 1)" fill="currentcolor"/></svg>
|
||||
<svg className={className || ""} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1201 1201" enableBackground="new 0 0 1201 1201">
|
||||
<rect x="1" y="1" display="none" fill="none" stroke="#01AEB7" strokeWidth="50" strokeMiterlimit="10" width="1200" height="1200"/>
|
||||
<g enableBackground="new ">
|
||||
<path fill="currentcolor" d="M173.4,576.8H96.9V214.5h135.2v85.2h-58.7v52.8H228v81.9h-54.7V576.8z"/>
|
||||
<path fill="currentcolor" d="M463.3,576.8h-82.7l-37.8-129.1c-0.4-2.2-1-5.1-1.9-8.7c-0.9-3.6-2-7.9-3.4-12.9l0.6,23.9v126.7h-76.1
|
||||
V214.5h78.3c34.5,0,60.3,8.7,77.4,26.1c21.8,22.2,32.7,53.8,32.7,94.8c0,43.8-14.6,74.1-43.9,90.8L463.3,576.8z M338.3,382.3h5.1
|
||||
c8.8,0,16.1-4.3,22-12.9c5.9-8.6,8.8-19.6,8.8-32.9c0-27.1-11.1-40.6-33.2-40.6h-2.8V382.3z"/>
|
||||
<path fill="currentcolor" d="M693.2,396.8c0,55.2-9.8,100.1-29.5,134.7c-19.6,34.4-45.1,51.6-76.5,51.6c-27.9,0-51.8-13.2-71.7-39.7
|
||||
c-25.1-33.5-37.6-83.7-37.6-150.7c0-64.1,13.1-113.2,39.3-147.1c19.4-25.2,42.5-37.8,69.2-37.8c32.7,0,58.6,17.1,78,51.2
|
||||
C683.5,293.2,693.2,339.1,693.2,396.8z M613.7,397.5c0-65.6-9.1-98.3-27.2-98.3c-9.1,0-16.3,8.6-21.7,25.8
|
||||
c-5.3,16-7.9,39.4-7.9,70.4c0,30.5,2.5,54.2,7.4,71c5,16.8,11.8,25.2,20.6,25.2c9.1,0,16-8.3,20.9-24.9
|
||||
C611.1,450.2,613.7,427.1,613.7,397.5z"/>
|
||||
<path fill="currentcolor" d="M725.8,576.8V214.5h73l38.3,127.2c2.1,7.5,4.4,16.1,7.1,25.8c2.6,9.7,5.5,20.9,8.7,33.6l7.9,31.9
|
||||
c-2.8-31.4-5-58.4-6.4-80.7c-1.5-22.4-2.2-41.1-2.2-56.3v-81.4h73v362.4h-73l-38.5-133.3c-4-14.2-7.7-27.6-10.9-40
|
||||
c-3.2-12.4-6.1-24.3-8.5-35.6c1.6,25.5,2.8,47.7,3.5,66.7c0.7,18.9,1.1,35.5,1.1,49.8v92.5H725.8z"/>
|
||||
<path fill="currentcolor" d="M1063.8,576.8h-76.5V301.3h-42.2v-86.8h162.6v86.8h-43.9V576.8z"/>
|
||||
</g>
|
||||
<g enableBackground="new ">
|
||||
<path fill="currentcolor" d="M123,657.8h35.8l27.4,133.7c3.4,16.7,6.4,33.4,9.2,50.2c2.7,16.8,5.3,34.4,7.5,52.7
|
||||
c0.3-2.4,0.5-4.3,0.6-5.6c0.1-1.3,0.3-2.3,0.4-2.9l5.8-37.6l5.2-35.7l4.6-23.6L244,657.8h36.2l28.6,327.7h-40l-7-111.2
|
||||
c-0.3-5.7-0.5-10.5-0.7-14.6c-0.2-4.1-0.3-7.5-0.3-10.2l-1.8-43.9l-1-40.3c0-0.3,0-0.9-0.1-1.8c-0.1-0.9-0.2-2.2-0.3-3.7l-1,6.6
|
||||
c-1.1,7.8-2,14.6-2.9,20.4c-0.9,5.8-1.6,10.7-2.3,14.6l-5.2,29.9l-2,11.2l-26.2,143.1h-28.2L165.1,862c-3.4-16.3-6.3-32.8-9-49.6
|
||||
c-2.6-16.8-5-34.3-7.1-52.7l-12.1,225.8H97.5L123,657.8z"/>
|
||||
<path fill="currentcolor" d="M395.5,657.8h32.6l66.6,327.7h-41.5l-11.7-63.9H380l-11.9,63.9h-40.2L395.5,657.8z M435.5,887.5l-9.3-52.2
|
||||
c-6.2-35.5-11.2-73.4-15.1-113.8c-1.9,19.7-4.3,39.6-7.2,59.8c-3,20.2-6.4,41.6-10.5,64.2l-7.6,42H435.5z"/>
|
||||
<path fill="currentcolor" d="M495.7,657.8h136.2v38.6H585v289.1H544V696.4h-48.3V657.8z"/>
|
||||
<path fill="currentcolor" d="M638.1,657.8h136.2v38.6h-46.9v289.1h-41.1V696.4h-48.3V657.8z"/>
|
||||
<path fill="currentcolor" d="M805.4,657.8h111.3v37.4h-69.4V799h61.6v37.4h-61.6v111.9h69.4v37.4H805.4V657.8z"/>
|
||||
<path fill="currentcolor" d="M962.9,657.8h55.1c22.8,0,39.8,5.7,50.9,17.2c14.1,14.9,21.1,37.6,21.1,68.1c0,23.5-3.7,42.3-11.2,56.6
|
||||
c-7.4,14.2-18.1,23-31.9,26.4l57.2,159.4h-42.5l-57-160.5v160.5h-41.9V657.8z M1004.8,803c16.2,0,27.7-4,34.4-11.9
|
||||
c6.7-7.9,10.1-21.3,10.1-40.1c0-10.2-0.7-18.9-2.1-26.1c-1.4-7.2-3.6-13.1-6.6-17.6c-3-4.5-6.9-7.9-11.7-10
|
||||
c-4.8-2.1-10.4-3.2-17-3.2h-7V803z"/>
|
||||
</g>
|
||||
<rect x="97" y="93.9" fill="currentcolor" width="169.7" height="43"/>
|
||||
<rect x="351.9" y="93.9" fill="currentcolor" width="169.7" height="43"/>
|
||||
<rect x="609.8" y="93.9" fill="currentcolor" width="169.7" height="43"/>
|
||||
<rect x="97" y="1060.7" fill="currentcolor" width="169.7" height="43"/>
|
||||
<rect x="351.9" y="1060.7" fill="currentcolor" width="169.7" height="43"/>
|
||||
<rect x="609.8" y="1060.7" fill="currentcolor" width="169.7" height="43"/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
13
src/panelWebView/components/Icons/ToggleIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IToggleIconProps {}
|
||||
|
||||
export const ToggleIcon: React.FunctionComponent<IToggleIconProps> = (props: React.PropsWithChildren<IToggleIconProps>) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentcolor" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="16" cy="12" r="2" />
|
||||
<rect x="2" y="6" width="20" height="12" rx="6" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -80,24 +80,9 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
|
||||
return (
|
||||
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
|
||||
|
||||
{
|
||||
renderFields(contentType?.fields || [], metadata)
|
||||
}
|
||||
|
||||
{
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={metadata.keywords as string[] || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable />
|
||||
</FieldBoundary>
|
||||
}
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ISeoKeywordsProps {
|
||||
}
|
||||
|
||||
const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...data}: React.PropsWithChildren<ISeoKeywordsProps>) => {
|
||||
const [ isReady, setIsReady ] = React.useState(false);
|
||||
|
||||
const validateKeywords = () => {
|
||||
if (!keywords) {
|
||||
@@ -32,7 +33,15 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...d
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!keywords || keywords.length === 0) {
|
||||
// Workaround for lit components not updating render
|
||||
React.useEffect(() => {
|
||||
setIsReady(false);
|
||||
setTimeout(() => {
|
||||
setIsReady(true);
|
||||
}, 0);
|
||||
}, [keywords]);
|
||||
|
||||
if (!isReady || !keywords || keywords.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,7 +58,7 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords, ...d
|
||||
{
|
||||
validateKeywords().map((keyword, index) => {
|
||||
return (
|
||||
<ErrorBoundary fallback={<div />}>
|
||||
<ErrorBoundary key={keyword} fallback={<div />}>
|
||||
<SeoKeywordInfo key={index} keyword={keyword} {...data} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { SEO } from '../../models/PanelSettings';
|
||||
import { TagType } from '../TagType';
|
||||
import { ArticleDetails } from './ArticleDetails';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import FieldBoundary from './ErrorBoundary/FieldBoundary';
|
||||
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { SeoFieldInfo } from './SeoFieldInfo';
|
||||
import { SeoKeywords } from './SeoKeywords';
|
||||
import { TagPicker } from './TagPicker';
|
||||
import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents';
|
||||
|
||||
export interface ISeoStatusProps {
|
||||
seo: SEO;
|
||||
data: any;
|
||||
focusElm: TagType | null;
|
||||
unsetFocus: () => void;
|
||||
}
|
||||
|
||||
const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
const { data, seo } = props;
|
||||
const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({ data, seo, focusElm, unsetFocus }: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
const { title, slug } = data;
|
||||
const [ isOpen, setIsOpen ] = React.useState(true);
|
||||
const tableRef = React.useRef<HTMLElement>();
|
||||
@@ -93,6 +98,18 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React.PropsW
|
||||
headings={data?.articleDetails?.headingsText}
|
||||
wordCount={data?.articleDetails?.wordCount}
|
||||
content={data?.articleDetails?.content} />
|
||||
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={data.keywords as string[] || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable />
|
||||
</FieldBoundary>
|
||||
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
</div>
|
||||
|
||||
@@ -149,6 +149,43 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
|
||||
return !selected.includes(option) && option.toLowerCase().includes((inputValue || "").toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the new item to the data
|
||||
* @param e
|
||||
*/
|
||||
const onEnterData = useCallback((e: React.KeyboardEvent<HTMLInputElement>, closeMenu: Function, highlightedIndex: any) => {
|
||||
if (e.key === "Enter" && e.type === "keydown" && (highlightedIndex === null || highlightedIndex === undefined)) {
|
||||
const value = inputRef.current?.value.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the value by comma
|
||||
const newValues: string[] = [];
|
||||
const values = value.split(",");
|
||||
for (let crntValue of values) {
|
||||
crntValue = crntValue.trim();
|
||||
if (crntValue) {
|
||||
const item = options.find(o => o?.toLowerCase() === crntValue?.toLowerCase());
|
||||
if (item) {
|
||||
newValues.push(item);
|
||||
} else if (freeform) {
|
||||
newValues.push(crntValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uniqValues = Array.from(new Set([...selected, ...newValues]));
|
||||
setSelected(uniqValues);
|
||||
sendUpdate(uniqValues);
|
||||
setInputValue("");
|
||||
closeMenu();
|
||||
}
|
||||
}, [options, inputRef, selected, freeform]);
|
||||
|
||||
/**
|
||||
* Check if the input is disabled
|
||||
*/
|
||||
const checkIsDisabled = useCallback(() => {
|
||||
if (!limit) {
|
||||
return false;
|
||||
@@ -191,7 +228,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={(value) => setInputValue(value)}>
|
||||
{
|
||||
({ getInputProps, getItemProps, getMenuProps, isOpen, inputValue, getRootProps, openMenu, closeMenu, clearSelection }) => (
|
||||
({ getInputProps, getItemProps, getMenuProps, isOpen, inputValue, getRootProps, openMenu, closeMenu, clearSelection, highlightedIndex }) => (
|
||||
<>
|
||||
<div {...getRootProps(undefined, {suppressRefError: true})} className={`article__tags__input ${freeform ? 'freeform' : ''}`}>
|
||||
<input {
|
||||
@@ -199,6 +236,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
|
||||
ref: inputRef,
|
||||
onFocus: openMenu as any,
|
||||
onClick: openMenu as any,
|
||||
onKeyDown: (e) => onEnterData(e, closeMenu, highlightedIndex),
|
||||
onBlur: () => {
|
||||
closeMenu();
|
||||
unsetFocus();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { GeneralCommands } from '../../constants';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { Mode } from '../../models/Mode';
|
||||
import { DashboardData } from '../../models/DashboardData';
|
||||
import { FolderInfo, PanelSettings } from '../../models/PanelSettings';
|
||||
import { Command } from '../Command';
|
||||
@@ -15,6 +17,7 @@ export default function useMessages() {
|
||||
const [focusElm, setFocus] = useState<TagType | null>(null);
|
||||
const [folderAndFiles, setFolderAndFiles] = useState<FolderInfo[] | undefined>(undefined);
|
||||
const [mediaSelecting, setMediaSelecting] = useState<DashboardData | undefined>(undefined);
|
||||
const [mode, setMode] = useState<Mode | undefined>(undefined);
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
@@ -43,9 +46,20 @@ export default function useMessages() {
|
||||
case Command.mediaSelectionData:
|
||||
setMediaSelecting(message.data);
|
||||
break;
|
||||
case GeneralCommands.setMode:
|
||||
setMode(message.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
window.setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 5000);
|
||||
}
|
||||
}, [loading])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -55,6 +69,7 @@ export default function useMessages() {
|
||||
}, 5000);
|
||||
|
||||
vscode.postMessage({ command: CommandToCode.getData });
|
||||
vscode.postMessage({ command: CommandToCode.getMode });
|
||||
}, []);
|
||||
|
||||
return {
|
||||
@@ -64,6 +79,7 @@ export default function useMessages() {
|
||||
focusElm,
|
||||
loading,
|
||||
mediaSelecting,
|
||||
mode,
|
||||
unsetFocus: () => { setFocus(null) }
|
||||
};
|
||||
}
|
||||
78
src/services/ModeSwitch.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { ModeListener } from './../listeners/general/ModeListener';
|
||||
import { SETTING_GLOBAL_ACTIVE_MODE, SETTING_GLOBAL_MODES } from './../constants/settings';
|
||||
import { commands, StatusBarAlignment, StatusBarItem, ThemeColor, window } from "vscode";
|
||||
import { Settings } from "../helpers/SettingsHelper";
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
import { Mode } from '../models';
|
||||
|
||||
|
||||
export class ModeSwitch {
|
||||
private static isInit: boolean = false;
|
||||
private static statusBarElm: StatusBarItem;
|
||||
private static currentMode: string;
|
||||
|
||||
public static register() {
|
||||
if (!ModeSwitch.statusBarElm) {
|
||||
ModeSwitch.statusBarElm = window.createStatusBarItem(StatusBarAlignment.Left, 100);
|
||||
ModeSwitch.statusBarElm.tooltip = "Switch between the different modes of Front Matter";
|
||||
ModeSwitch.statusBarElm.command = COMMAND_NAME.modeSwitch;
|
||||
}
|
||||
|
||||
if (!ModeSwitch.isInit) {
|
||||
commands.registerCommand(COMMAND_NAME.modeSwitch, ModeSwitch.switchMode);
|
||||
|
||||
const mode = Settings.get<string | null>(SETTING_GLOBAL_ACTIVE_MODE);
|
||||
if (mode) {
|
||||
ModeSwitch.currentMode = mode;
|
||||
} else {
|
||||
ModeSwitch.currentMode = "";
|
||||
}
|
||||
|
||||
ModeSwitch.isInit = true;
|
||||
}
|
||||
|
||||
ModeListener.getMode();
|
||||
|
||||
const modes = Settings.get<string | null>(SETTING_GLOBAL_MODES);
|
||||
if (!modes || modes.length === 0) {
|
||||
ModeSwitch.statusBarElm.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
ModeSwitch.setText();
|
||||
}
|
||||
|
||||
public static getMode(): string {
|
||||
return ModeSwitch.currentMode;
|
||||
}
|
||||
|
||||
private static async switchMode() {
|
||||
const modes = Settings.get<Mode[]>(SETTING_GLOBAL_MODES);
|
||||
if (!modes || modes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modePicks = [
|
||||
"Default",
|
||||
...modes.map(m => m.id)
|
||||
]
|
||||
|
||||
const mode = await window.showQuickPick(modePicks, {
|
||||
placeHolder: `Select the mode you want to use`,
|
||||
ignoreFocusOut: true,
|
||||
title: `Front Matter: Mode selection`
|
||||
});
|
||||
|
||||
if (mode) {
|
||||
ModeSwitch.currentMode = mode === "Default" ? "" : mode;
|
||||
ModeSwitch.setText();
|
||||
|
||||
ModeListener.getMode();
|
||||
}
|
||||
}
|
||||
|
||||
private static setText() {
|
||||
ModeSwitch.statusBarElm.text = `$(preview) Mode: ${ModeSwitch.currentMode ? ModeSwitch.currentMode : "Default"}`;
|
||||
ModeSwitch.statusBarElm.show();
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,7 @@ module.exports = {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms")
|
||||
require("@tailwindcss/forms"),
|
||||
require("tailwindcss-nested-groups"),
|
||||
],
|
||||
}
|
||||
|
||||