Compare commits

..

99 Commits

Author SHA1 Message Date
Elio Struyf
301814bcdd #816 - Sample implementation 2024-06-06 17:13:03 +02:00
Elio Struyf
a22219c1b4 #802 - Remove async update by retrieving folders from cache 2024-06-06 14:35:41 +02:00
Elio Struyf
ec326a74ca Updated issue template 2024-06-06 11:43:59 +02:00
Elio Struyf
2246fbb933 Added extra diagnostic info 2024-06-06 11:41:52 +02:00
Elio Struyf
fa6f7dcfe6 Fix on article update 2024-06-06 11:24:20 +02:00
Elio Struyf
f83ed9b970 #812 - Fix date formatting with date placeholder 2024-06-06 10:51:24 +02:00
Elio Struyf
9ce70fe722 #812 - Locale check for index.md files 2024-06-06 10:13:45 +02:00
Elio Struyf
4a1b37ba88 Fix extend i18n config 2024-06-06 10:07:55 +02:00
Elio Struyf
0b8155a75f Added extra logging for placeholder and metadata updates 2024-05-25 17:25:05 +02:00
Elio Struyf
19a0f4b53f #812 - Added {{locale}} placeholder 2024-05-23 22:02:15 +02:00
Elio Struyf
0bde5610c5 #806 - Added trailingSlash option 2024-05-23 21:32:04 +02:00
Elio Struyf
b90f2adb18 #788 - Added localization 2024-05-23 20:13:54 +02:00
Elio Struyf
a70b4316f8 #788 - Safe setting update 2024-05-23 18:30:14 +02:00
Elio Struyf
16453cbb21 #442 - Hide sidebar 2024-05-23 16:44:15 +02:00
Elio Struyf
46e90df501 Fix feature flag 2024-05-23 16:12:55 +02:00
Elio Struyf
d8d72980ea #811 - Added panel.gitActions view mode option 2024-05-23 12:35:10 +02:00
Elio Struyf
7a5e452602 #441 - Show description on fields 2024-05-23 11:49:05 +02:00
Elio Struyf
beee186d72 Add support for field groups + block fields #808 2024-05-23 11:00:00 +02:00
Elio Struyf
64fc1e4b76 Refactoring of command registration 2024-05-23 10:40:38 +02:00
Elio Struyf
5c4a716367 #810 - Update tab title 2024-05-22 20:51:12 +02:00
Elio Struyf
31873bc2d2 #809 - Fix retrieving the filePrefix when updating the file name on slug change 2024-05-22 20:24:22 +02:00
Elio Struyf
0e92834517 #806 - Prefix and suffix update 2024-05-21 10:59:08 +02:00
Elio Struyf
d262518023 #806 - Fix preview URL 2024-05-17 16:13:23 +02:00
Elio Struyf
da2cf68f5c Small css fix 2024-05-17 15:25:29 +02:00
Elio Struyf
2e7ece44e2 #802 - Fix Windows paths 2024-05-07 09:26:26 +02:00
Elio Struyf
c039d260dc Updated localization key 2024-05-06 12:28:46 +02:00
Elio Struyf
2fc543f0dd Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2024-05-06 12:24:52 +02:00
Elio Struyf
48314b3f3f #804 - Fix blinking 2024-05-06 12:24:42 +02:00
Elio Struyf
a43b581e1b Update changelog 2024-05-02 21:48:34 +02:00
Elio Struyf
1ad55cdbbb #798 - snippet slide-over 2024-05-02 21:48:27 +02:00
Elio Struyf
ffa70050eb Merge pull request #803 from estruyf/issue/802
#802 - Update glob
2024-05-02 09:31:06 +02:00
Elio Struyf
e8f70c78fd Adding logging + version info 2024-04-29 16:17:43 +02:00
Elio Struyf
504774a4c8 Optimized diagnostics 2024-04-29 15:49:42 +02:00
Elio Struyf
a764c2fea7 Removed glob types 2024-04-29 15:04:43 +02:00
Elio Struyf
5f623689cc #802 - Update glob 2024-04-29 14:53:09 +02:00
Elio Struyf
54bf408c76 #801 - Single file update on save for recently modified 2024-04-29 12:09:00 +02:00
Elio Struyf
03f2284dd2 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2024-04-29 11:40:46 +02:00
Elio Struyf
f637def278 #801 - Faster folder processing 2024-04-29 11:39:41 +02:00
Elio Struyf
da46374fb4 #799 - Added logging setting 2024-04-25 21:31:55 +02:00
Elio Struyf
dee732f3ee #800 - Add colors for the Front Matter CMS output 2024-04-25 18:20:00 +02:00
Elio Struyf
d3b93424d1 #796 - Extra logging 2024-04-25 17:46:17 +02:00
Elio Struyf
a467791eaf #796 - More logging 2024-04-25 17:22:31 +02:00
Elio Struyf
70a5de960f #796 - Settings logging 2024-04-25 16:24:52 +02:00
Elio Struyf
31e27f63c1 #796 - Webview logging 2024-04-25 15:50:02 +02:00
Elio Struyf
a50f567fbb #796 - extra logging 2024-04-25 14:53:11 +02:00
Elio Struyf
bdafd25cfe Update logger 2024-04-25 13:44:01 +02:00
Elio Struyf
18b7708367 #797 - Enhancing the card menu and type 2024-04-25 11:06:00 +02:00
Elio Struyf
3fedaf7d5f Update snippets list 2024-04-24 15:38:25 +02:00
Elio Struyf
75a3fc21a3 10.2.0 2024-04-24 15:24:05 +02:00
Elio Struyf
82b894c35b #797 - Adding common actions at the bottom of the snippet cards 2024-04-24 15:23:55 +02:00
Elio Struyf
60952a05ac #796 - Return error in output 2024-04-23 19:50:27 +02:00
Elio Struyf
f46e4999a1 Update changelog 2024-04-11 17:17:09 +02:00
Elio Struyf
f9138cb3c3 Release time 2024-04-11 17:13:36 +02:00
Elio Struyf
893c46362e revert change 2024-04-10 17:53:27 +02:00
Elio Struyf
9136841b30 #716 - Missing class 2024-04-08 14:01:04 +02:00
Elio Struyf
0e21093f92 Move taxonomy picker 2024-04-08 13:45:16 +02:00
Elio Struyf
3abd9589f1 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2024-04-04 16:57:47 +02:00
Elio Struyf
81265e3c49 #671 - Fix on metadata update 2024-04-04 16:57:40 +02:00
Elio Struyf
f6fd57e126 Merge pull request #790 from mayumih387/dev
Added and updated Japanese translations
2024-04-04 10:24:57 +02:00
mayumih387
20d613452f Added and updated Japanese translations 2024-04-04 15:13:18 +09:00
Elio Struyf
35a6c8bada Tab colors 2024-04-01 17:06:48 +02:00
Elio Struyf
0b7f58d0ab #785 - Added custom script actions to the bottom of the cards 2024-04-01 16:39:05 +02:00
Elio Struyf
c859874470 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2024-04-01 15:11:17 +02:00
Elio Struyf
d70d2284b4 #787 - Support for glob patterns in the page folder paths 2024-04-01 15:11:07 +02:00
Elio Struyf
03236da793 Small design tweaks 2024-03-30 12:41:07 +01:00
Elio Struyf
07935aec73 Update changelog 2024-03-30 10:33:29 +01:00
Elio Struyf
f64c8c5958 #786 - Remove on startup as VSCode now triggers on known commands 2024-03-30 10:33:10 +01:00
Elio Struyf
c4267a69fa #785 - Media actions 2024-03-29 17:34:28 +01:00
Elio Struyf
34b331b0ee #785 - Added content actions at the bottom of the card 2024-03-29 14:10:46 +01:00
Elio Struyf
5d0fc4f605 Update localization 2024-03-28 15:19:26 +01:00
Elio Struyf
169f4ef14a Fix localization 2024-03-28 15:19:05 +01:00
Elio Struyf
7ea386328c Added keybinding to refresh dashboard 2024-03-27 12:12:57 +01:00
Elio Struyf
c17400ce6d #783 - Always show custom panel views 2024-03-27 10:57:30 +01:00
Elio Struyf
7b20d9f23d #782 - Setting the correct view 2024-03-26 09:54:34 +01:00
Elio Struyf
449bb110c2 Small refactoring 2024-03-22 09:13:00 +01:00
Elio Struyf
0d3a99abe6 #777 - Fix for untitled files 2024-03-19 13:01:29 +01:00
Elio Struyf
d2b9307a65 #777 - Extra states for invalid files 2024-03-19 12:34:00 +01:00
Elio Struyf
3842777f71 #778 - Open file or webpage 2024-03-19 12:24:08 +01:00
Elio Struyf
3a74c14ba6 Update localization 2024-03-19 10:06:07 +01:00
Elio Struyf
a5ac7379bc #777 - Show error when front matter parsing failed 2024-03-18 14:31:29 +01:00
Elio Struyf
c245e1474c Icon updates 2024-03-15 14:48:15 +01:00
Elio Struyf
c82c081fce #773 - Rename files 2024-03-14 16:38:17 +01:00
Elio Struyf
31e344f358 Retry to check if external script is binded 2024-03-13 17:43:38 +01:00
Elio Struyf
366ae82318 Added count 2024-03-13 16:22:19 +01:00
Elio Struyf
c1a0609216 Update changelog 2024-03-13 15:02:23 +01:00
Elio Struyf
87bdabf515 Merge pull request #772 from estruyf/issue/671
#671 - Implement checkbox on media card
2024-03-13 15:00:46 +01:00
Elio Struyf
15870bcc99 #771 - Fix lowercased data label 2024-03-13 14:54:35 +01:00
Elio Struyf
e0cdc5cf65 Update sponsor 2024-03-11 17:17:56 +01:00
Elio Struyf
f39b707e30 Updated readme 2024-03-11 17:06:46 +01:00
Elio Struyf
dd13d8779c Merge branch 'main' into dev 2024-03-01 10:47:38 +01:00
Elio Struyf
6f6015cf83 Merge branch 'main' of github.com:estruyf/vscode-front-matter 2024-03-01 10:33:18 +01:00
Elio Struyf
afd2878428 #769 - Fix settings on install 2024-03-01 10:33:13 +01:00
Elio Struyf
c66deb032c Merge pull request #770 from estruyf/patch-10.0.2
Patch 10.0.2
2024-03-01 10:05:04 +01:00
Elio Struyf
4c079b3e9d 10.0.2 2024-03-01 09:01:46 +01:00
Elio Struyf
03c2cd31d7 Update changelog 2024-03-01 09:01:38 +01:00
Elio Struyf
d1dba01923 #769 - Fix folder update 2024-03-01 09:00:42 +01:00
Elio Struyf
286ac4adfe Merge pull request #767 from estruyf/dev
#766 - Fix snippet placeholder retrieval
2024-02-28 21:13:56 +01:00
Elio Struyf
6e2633572a Merge pull request #763 from estruyf/dev
Release v10.0.0
2024-02-28 16:34:35 +01:00
Elio Struyf
36ae7081d1 Merge pull request #723 from estruyf/dev
Version 9.4.0
2023-12-12 16:29:45 +01:00
160 changed files with 3804 additions and 1685 deletions

View File

@@ -12,6 +12,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -23,16 +24,11 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Device:**
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
- OS: [e.g. iOS]
- Front Matter CMS Version [e.g. 10.2.0]
- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,16 +1,65 @@
# Change Log
## [10.1.0] - 2024-xx-xx
## [10.2.0] - 2024-xx-xx
### ✨ New features
- [#797](https://github.com/estruyf/vscode-front-matter/issues/797): Adding common actions at the bottom of the snippet cards
### 🎨 Enhancements
- [#441](https://github.com/estruyf/vscode-front-matter/issues/441): Show input descriptions for snippet and data forms
- [#442](https://github.com/estruyf/vscode-front-matter/issues/442): Hide sidebar on data view when data file is selecte + show dropdown of data files
- [#788](https://github.com/estruyf/vscode-front-matter/issues/788): Show a warning on setting update when it exists in an extended configuration
- [#798](https://github.com/estruyf/vscode-front-matter/issues/798): Changed dialog to slide-over for the snippet forms
- [#799](https://github.com/estruyf/vscode-front-matter/issues/799): Added `frontMatter.logging` setting to define the logging output. Options are `info`, `warn`, `error`, and `verbose`. Default is `info`.
- [#800](https://github.com/estruyf/vscode-front-matter/issues/800): Add colors for the Front Matter CMS output
- [#808](https://github.com/estruyf/vscode-front-matter/issues/808): Add support to generate field groups and `block` fields in content type generation
- [#810](https://github.com/estruyf/vscode-front-matter/issues/810): Update the tab title based on the view
- [#811](https://github.com/estruyf/vscode-front-matter/issues/811): Added `panel.gitActions` view mode option to hide the Git actions in the panel
- [#812](https://github.com/estruyf/vscode-front-matter/issues/812): Added the `{{locale}}` placeholder which can be used in the `previewPath` property
### ⚡️ Optimizations
- [#802](https://github.com/estruyf/vscode-front-matter/issues/802): Update `glob` to the latest version and remove the sync method
### 🐞 Fixes
- [#796](https://github.com/estruyf/vscode-front-matter/issues/796): Fix issue in retrieving folders/files on dashboard load
- [#801](https://github.com/estruyf/vscode-front-matter/issues/801): Faster folder processing on updates
- [#804](https://github.com/estruyf/vscode-front-matter/issues/804): Fix blinking of the front matter content area
- [#806](https://github.com/estruyf/vscode-front-matter/issues/804): Fix preview URL for `index.md` files in root of the page folder path
- [#809](https://github.com/estruyf/vscode-front-matter/issues/809): Fix retrieving the `filePrefix` when updating the file name on slug change
## [10.1.0] - 2024-04-11 - [Release notes](https://beta.frontmatter.codes/updates/v10.1.0)
### ✨ New features
- [#671](https://github.com/estruyf/vscode-front-matter/issues/671): Command bar for contents and media dashboard
### 🎨 Enhancements
- [#773](https://github.com/estruyf/vscode-front-matter/issues/773): Added the ability to rename content files
- [#777](https://github.com/estruyf/vscode-front-matter/issues/777): Show an error in the metadata panel if something went wrong while parsing the front matter
- [#778](https://github.com/estruyf/vscode-front-matter/issues/778): Added the ability to open a file or webpage when custom scripts is completed
- [#783](https://github.com/estruyf/vscode-front-matter/issues/783): Always show the custom panel view
- [#785](https://github.com/estruyf/vscode-front-matter/issues/785): Adding common actions at the bottom of the content and media cards
- [#787](https://github.com/estruyf/vscode-front-matter/issues/787): Support for glob patterns in the page folder paths
- [#790](https://github.com/estruyf/vscode-front-matter/pull/790): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387)
### 🐞 Fixes
- [#716](https://github.com/estruyf/vscode-front-matter/issues/716): Fix `dataFile` dropdown class
- [#768](https://github.com/estruyf/vscode-front-matter/issues/768): Update broken link to the documentation
- [#771](https://github.com/estruyf/vscode-front-matter/issues/771): Fix lowercase `data` tab label
- [#782](https://github.com/estruyf/vscode-front-matter/issues/782): Fix for setting the correct view when inserting media or snippets
- [#786](https://github.com/estruyf/vscode-front-matter/issues/786): Remove on startup as VSCode now triggers on known commands
## [10.0.2] - 2024-03-01
### 🐞 Fixes
- [#769](https://github.com/estruyf/vscode-front-matter/issues/769): Fix to remove internal properties for content folders
## [10.0.1] - 2024-02-28

View File

@@ -182,15 +182,31 @@ You can open showcase issues for the following things:
## 🖤 Backers & Sponsors 👇 🤘
<p align="center">
<img src="https://frontmatter.codes/api/img-sponsors" />
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
</p>
<br />
<p align="center" title="Powered by Vercel">
<a href="https://run.events/?utm_source=frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/runevents-purple.webp" alt="run.events - Event Management Platform" height="50px" />
</a>
</p>
<br />
<p align="center" title="Powered by Vercel">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" height="44px" />
</a>
</p>
<br />
<p align="center">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" />
</a>
<a href="http://bejs.io/" title="Supported by the BEJS Community">
<img src="https://frontmatter.codes/assets/sponsors/bejs-community.png" alt="Supported by the BEJS Community" height="50px"/>
</a>
</p>
## 📊 Telemetry

View File

@@ -185,9 +185,17 @@ You can open showcase issues for the following things:
<br />
<p align="center" title="Powered by Vercel">
<a href="https://run.events/?utm_source=frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/runevents-purple.webp" alt="run.events - Event Management Platform" height="50px" />
</a>
</p>
<br />
<p align="center" title="Powered by Vercel">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" />
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" height="44px" />
</a>
</p>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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">
<path fill="#02aeb7" d="M316,1082.3H119.4V151.2h347.5v218.9H316v135.7h140.5v210.5H316V1082.3z"/>
<path fill="#02aeb7" d="M602.2,151.2H704l77.7,379.9c9.5,47.4,18.1,95,26,142.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
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="#02aeb7" width="184" height="64.7"/>
<rect x="395.7" y="0.1" fill="#02aeb7" width="184" height="64.7"/>
<rect x="675.3" y="0.1" fill="#02aeb7" width="184" height="64.7"/>
<rect x="119.4" y="1184.7" fill="#02aeb7" width="184" height="64.7"/>
<rect x="395.7" y="1184.7" fill="#02aeb7" width="184" height="64.7"/>
<rect x="675.3" y="1184.7" fill="#02aeb7" width="184" height="64.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#C5C5C5" width="16" height="16">
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#C5C5C5" width="16" height="16" class="w-6 h-6">
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#424242" width="16" height="16">
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#424242" width="16" height="16" class="w-6 h-6">
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -1,3 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#C5C5C5" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#C5C5C5" width="16" height="16" class="w-6 h-6">
<path fill-rule="evenodd" d="M8.128 9.155a3.751 3.751 0 1 1 .713-1.321l1.136.656a.75.75 0 0 1 .222 1.104l-.006.007a.75.75 0 0 1-1.032.157 1.421 1.421 0 0 0-.113-.072l-.92-.531Zm-4.827-3.53a2.25 2.25 0 0 1 3.994 2.063.756.756 0 0 0-.122.23 2.25 2.25 0 0 1-3.872-2.293ZM13.348 8.272a5.073 5.073 0 0 0-3.428 3.57 5.08 5.08 0 0 0-.165 1.202 1.415 1.415 0 0 1-.707 1.201l-.96.554a3.751 3.751 0 1 0 .734 1.309l13.729-7.926a.75.75 0 0 0-.181-1.374l-.803-.215a5.25 5.25 0 0 0-2.894.05l-5.325 1.629Zm-9.223 7.03a2.25 2.25 0 1 0 2.25 3.897 2.25 2.25 0 0 0-2.25-3.897ZM12 12.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
<path d="M16.372 12.615a.75.75 0 0 1 .75 0l5.43 3.135a.75.75 0 0 1-.182 1.374l-.802.215a5.25 5.25 0 0 1-2.894-.051l-5.147-1.574a.75.75 0 0 1-.156-1.367l3-1.732Z" />
</svg>

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 939 B

View File

@@ -1,3 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#424242" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#424242" width="16" height="16" class="w-6 h-6">
<path fill-rule="evenodd" d="M8.128 9.155a3.751 3.751 0 1 1 .713-1.321l1.136.656a.75.75 0 0 1 .222 1.104l-.006.007a.75.75 0 0 1-1.032.157 1.421 1.421 0 0 0-.113-.072l-.92-.531Zm-4.827-3.53a2.25 2.25 0 0 1 3.994 2.063.756.756 0 0 0-.122.23 2.25 2.25 0 0 1-3.872-2.293ZM13.348 8.272a5.073 5.073 0 0 0-3.428 3.57 5.08 5.08 0 0 0-.165 1.202 1.415 1.415 0 0 1-.707 1.201l-.96.554a3.751 3.751 0 1 0 .734 1.309l13.729-7.926a.75.75 0 0 0-.181-1.374l-.803-.215a5.25 5.25 0 0 0-2.894.05l-5.325 1.629Zm-9.223 7.03a2.25 2.25 0 1 0 2.25 3.897 2.25 2.25 0 0 0-2.25-3.897ZM12 12.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
<path d="M16.372 12.615a.75.75 0 0 1 .75 0l5.43 3.135a.75.75 0 0 1-.182 1.374l-.802.215a5.25 5.25 0 0 1-2.894-.051l-5.147-1.574a.75.75 0 0 1-.156-1.367l3-1.732Z" />
</svg>

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 939 B

View File

@@ -105,7 +105,7 @@
"dashboard.header.tabs.contents": "Contenus",
"dashboard.header.tabs.media": "Médias",
"dashboard.header.tabs.snippets": "Snippets",
"dashboard.header.tabs.data": "données",
"dashboard.header.tabs.data": "Données",
"dashboard.header.tabs.taxonomies": "Taxonomies",
"dashboard.header.viewSwitch.toGrid": "Afficher en grille",
"dashboard.header.viewSwitch.toList": "Afficher en liste",

View File

@@ -105,7 +105,7 @@
"dashboard.header.tabs.contents": "Contenuto",
"dashboard.header.tabs.media": "Media",
"dashboard.header.tabs.snippets": "Snippets",
"dashboard.header.tabs.data": "dati",
"dashboard.header.tabs.data": "Dati",
"dashboard.header.tabs.taxonomies": "Tassonomie",
"dashboard.header.viewSwitch.toGrid": "Passa alla griglia",
"dashboard.header.viewSwitch.toList": "Passa all'elenco",

View File

@@ -3,8 +3,8 @@
"common.edit": "編集",
"common.delete": "削除",
"common.cancel": "キャンセル",
"common.clear": "クリア",
"common.apply": "適用",
"common.clear": "クリア",
"common.clear.value": "値をクリア",
"common.search": "検索",
"common.save": "保存",
@@ -35,6 +35,16 @@
"common.no": "いいえ",
"common.openSettings": "設定を開く",
"common.back": "戻る",
"common.open": "開く",
"common.openWithValue": "開く: {0}",
"common.openCustomActions": "カスタムコマンドを開く",
"common.view": "表示",
"common.translate": "翻訳する",
"common.languages": "言語",
"common.scripts": "スクリプト",
"common.rename": "ファイル名を変更する",
"loading.initPages": "記事を読み込んでいます",
"notifications.outputChannel.link": "出力ウィンドウ",
"notifications.outputChannel.description": "詳細は{0}を確認してください。",
@@ -42,18 +52,36 @@
"settings.view.common": "一般",
"settings.view.contentFolders": "記事フォルダー",
"settings.view.astro": "Astro",
"settings.view.integration": "統合機能",
"settings.openOnStartup": "起動時にダッシュボードを開く",
"settings.contentTypes": "記事タイプ",
"settings.contentFolders": "記事フォルダー",
"settings.diagnostic": "診断",
"settings.diagnostic.description": "診断プログラムを実行して、Front Matter CMS構成全体を確認できます。",
"settings.diagnostic.link": "完全診断を実行する",
"settings.git": "Git同期",
"settings.git.enabled": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。",
"settings.git.commitMessage": "コミットメッセージ",
"settings.git.submoduleInfo": "Gitサブモジュールを使用している場合は、サブモジュールの設定についてドキュメントを参照してください。",
"settings.git.submoduleLink": "Gitサブモジュールについて確認する",
"settings.integration.title": "統合機能",
"settings.commonSettings.website.title": "ウェブサイトとSSGの設定",
"settings.commonSettings.previewUrl": "プレビュー用URL",
"settings.commonSettings.websiteUrl": "ウェブサイトのURL",
"settings.commonSettings.startCommand": "SSG/フレームワーク起動コマンド",
"settings.integrationsView.deepl.title": "DeepL",
"settings.integrationsView.deepl.intput.label": "API key",
"settings.integrationsView.deepl.intput.placeholder": "DeepL API keyを入力",
"settings.integrationsView.azure.title": "Azure AI Translator",
"settings.integrationsView.azure.intput.label": "サブスクリプションキー",
"settings.integrationsView.azure.intput.placeholder": "Azure AI Translatorのサブスクリプションキーを入力",
"settings.integrationsView.azure.region.label": "リージョン",
"settings.integrationsView.azure.region.placeholder": "Azure AI Translatorのリージョンを入力 例: westeurope",
"developer.title": "開発モード",
"developer.reload.title": "ダッシュボードを再読み込み",
"developer.reload.label": "再読み込み",
@@ -81,6 +109,8 @@
"dashboard.contents.contentActions.menuItem.view": "開く",
"dashboard.contents.contentActions.alert.title": "削除: {0}",
"dashboard.contents.contentActions.alert.description": "本当に\"{0}\"を削除しますか?",
"dashboard.contents.contentActions.translations.create": "翻訳する",
"dashboard.contents.contentActions.translations.menu": "翻訳版",
"dashboard.contents.item.invalidTitle": "<無効なタイトル>",
"dashboard.contents.item.invalidDescription": "<無効なディスクリプション>",
@@ -108,6 +138,7 @@
"dashboard.dataView.dataView.getStarted": "データタイプを選択して開始する",
"dashboard.dataView.dataView.noDataFiles": "データファイルが見つかりませんでした",
"dashboard.dataView.dataView.getStarted.link": "データファイルの利用方法について確認する",
"dashboard.dataView.dataView.update.message": "データエントリーを更新しました。",
"dashboard.dataView.emptyView.heading": "最初にデータタイプを選んでください",
@@ -118,6 +149,13 @@
"dashboard.errorView.description": "ダッシュボードを一旦閉じてからやり直してください。",
"dashboard.filters.languageFilter.label": "ロケール",
"dashboard.filters.languageFilter.all": "全て",
"dashboard.header.actionsBar.itemsSelected": "{0}件を選択中",
"dashboard.header.actionsBar.alertDelete.title": "選択ファイルを削除",
"dashboard.header.actionsBar.alertDelete.description": "選択したファイルを本当に削除しますか?",
"dashboard.header.breadcrumb.home": "ホーム",
"dashboard.header.clearFilters.title": "絞り込み・グループ・並べ替えを解除",
@@ -201,10 +239,17 @@
"dashboard.media.folderCreation.hexo.create": "Assetフォルダーを作成",
"dashboard.media.folderCreation.folder.create": "新規フォルダーを作成",
"dashboard.media.folderItem.contentDirectory": "コンテンツディレクトリー",
"dashboard.media.folderItem.publicDirectory": "Publicディレクトリー",
"dashboard.media.item.buttom.insert.image": "画像を挿入",
"dashboard.media.item.buttom.insert.snippet": "スニペットを挿入",
"dashboard.media.item.quickAction.insert.field": "この画像を\"{0}\"フィールドに追加",
"dashboard.media.item.quickAction.insert.markdown": "画像をMarkdown記法で挿入",
"dashboard.media.item.quickAction.copy.path": "ファイルパスをコピー",
"dashboard.media.item.quickAction.delete": "ファイルを削除",
"dashboard.media.item.menuItem.view": "メタデータの詳細を表示",
"dashboard.media.item.menuItem.edit.metadata": "メタデータを編集",
"dashboard.media.item.menuItem.insert.image": "画像を挿入",
"dashboard.media.item.menuItem.reveal.media": "メディアの場所を表示",
@@ -275,6 +320,8 @@
"dashboard.steps.stepsToGetStarted.contentFolders.information.description": "エクスプローラーでフォルダー名を右クリックして「フォルダーを登録」を選択する方法でも、フォルダーの登録が可能です。",
"dashboard.steps.stepsToGetStarted.tags.name": "全てのタグとカテゴリーをインポート(オプション)",
"dashboard.steps.stepsToGetStarted.tags.description": "Front Matterに記事用フォルダーが登録されました。記事から全てのタグとカテゴリーをインポートしますか",
"dashboard.steps.stepsToGetStarted.git.name": "Git同期を有効化しますか",
"dashboard.steps.stepsToGetStarted.git.description": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。",
"dashboard.steps.stepsToGetStarted.showDashboard.name": "ダッシュボードを開く",
"dashboard.steps.stepsToGetStarted.showDashboard.description": "全ての設定が終わると、ダッシュボードが表示できるようになります。",
"dashboard.steps.stepsToGetStarted.template.name": "設定用のテンプレートを使用する",
@@ -283,6 +330,7 @@
"dashboard.steps.stepsToGetStarted.astroContentTypes.name": "Astroコンテンツコレクションのコンテンツタイプを作成する",
"dashboard.taxonomyView.button.add.title": "\"{0}\"をタクソノミーに追加",
"dashboard.taxonomyView.button.tag.title": "タグを追加",
"dashboard.taxonomyView.button.edit.title": "\"{0}\"を編集",
"dashboard.taxonomyView.button.merge.title": "\"{0}\"をマージ",
"dashboard.taxonomyView.button.move.title": "他のタクソノミーへ移行",
@@ -329,6 +377,11 @@
"dashboard.configuration.astro.astroContentTypes.empty": "Astroコンテンツコレクションが見つかりません。",
"dashboard.configuration.astro.astroContentTypes.description": "以下のAstroコンテンツコレクションは、コンテンツタイプを生成するために使用できます。",
"panel.git.gitAction.title": "変更の反映",
"panel.git.gitAction.branch.select": "ブランチを選択",
"panel.git.gitAction.input.placeholder": "コミットメッセージ",
"panel.git.gitAction.button.fetch": "フェッチ",
"panel.contentType.contentTypeValidator.title": "記事タイプ",
"panel.contentType.contentTypeValidator.hint": "記事タイプのフィールドは設定と異なります。この記事の記事タイプを、作成・更新または設定しますか?",
"panel.contentType.contentTypeValidator.button.create": "新しい記事タイプを作成",
@@ -414,6 +467,7 @@
"panel.globalSettings.action.server.placeholder": "例: {0}",
"panel.metadata.title": "メタデータ",
"panel.metadata.focusProblems": "詳細を「問題」表示で確認してください。",
"panel.otherActions.title": "他のコマンド",
"panel.otherActions.writingSettings.enabled": "ライティング設定が有効",
@@ -474,6 +528,10 @@
"commands.article.setDate.error": "日付の表示形式の解析中に何らかの問題が発生しました。\"{0}\"の設定を確認してください。",
"commands.article.updateSlug.error": "ファイル名を変更できませんでした。: {0}",
"commands.article.rename.fileNotExists.error": "ファイルが存在しません。",
"commands.article.rename.fileExists.error": "\"{0}\" というファイル名は既に存在しています。",
"commands.article.rename.fileName.title": "ファイル名を変更: {0}",
"commands.article.rename.fileName.prompt": "ファイル名",
"commands.cache.cleared": "キャッシュがクリアされました。",
@@ -501,6 +559,19 @@
"commands.folders.get.notificationError.remove.action": "フォルダー設定を削除",
"commands.folders.get.notificationError.create.action": "フォルダーを作成",
"commands.i18n.create.warning.noFileSelected": "ファイルが選択されていません。",
"commands.i18n.create.warning.noFile": "ファイルが取得できませんでした。",
"commands.i18n.create.warning.noContentType": "現在のファイルの記事タイプを取得できませんでした。",
"commands.i18n.create.warning.noConfig": "i18nの設定が見つかりません。",
"commands.i18n.create.error.noLocaleDefinition": "現在のファイルのロケールを取得できませんでした。",
"commands.i18n.create.error.noLocales": "現在のファイルは利用可能なすべての言語に翻訳されています。",
"commands.i18n.create.error.noContentFolder": "現在のファイルの記事フォルダーを指定できませんでした。",
"commands.i18n.create.error.fileExists": "そのi18n翻訳は既に存在しています。",
"commands.i18n.create.success.created": "\"{0}\" i18n記事ファイルを作成しました。",
"commands.i18n.create.quickPick.title": "言語別の記事を作成",
"commands.i18n.create.quickPick.placeHolder": "どの言語で記事を作成しますか?",
"commands.i18n.translate.progress.title": "記事を翻訳しています...",
"commands.preview.panel.title": "プレビュー: {0}",
"commands.preview.askUserToPickFolder.title": "プレビュー用の記事フォルダーを選択してください。",
@@ -611,9 +682,6 @@
"helpers.extension.getVersion.changelog": "変更履歴を確認する",
"helpers.extension.getVersion.starIt": "⭐️を付ける",
"helpers.extension.getVersion.update.notification": "{0} が v{1} に更新されました!新機能をチェックしてください!",
"helpers.extension.migrateSettings.deprecated.warning": "\"{0}\"及び\"{1}\"の設定は非推奨になりました。代わりに\"isPublishDate\"と\"isModifiedDate\"の日付フィールドを使用してください。",
"helpers.extension.migrateSettings.deprecated.warning.hide": "非表示にする",
"helpers.extension.migrateSettings.deprecated.warning.seeGuide": "移行ガイドを読む",
"helpers.extension.migrateSettings.templates.quickPick.title": "{0} - テンプレート",
"helpers.extension.migrateSettings.templates.quickPick.placeholder": "テンプレート機能の使用を継続しますか?",
"helpers.extension.checkIfExtensionCanRun.warning": "Front MatterのBETA版は安定版がインストールされている場合は利用できません。BETA版のみがインストールされていることを確認してください。",
@@ -648,6 +716,7 @@
"helpers.questions.selectContentType.quickPick.title": "記事タイプ",
"helpers.questions.selectContentType.quickPick.placeholder": "新規作成する記事の記事タイプを選択してください。",
"helpers.questions.selectContentType.noSelection.warning": "記事タイプが選択されていません。",
"helpers.questions.selectContentType.quickPick.error.noContentTypes": "このフォルダーには、一致する記事タイプが設定されていません。",
"helpers.seoHelper.checkLength.diagnostic.message": "記事{0}の文字数が{1}文字を超えています(現在の文字数: {2}。SEOの観点上、{1}文字以内に収めることが推奨されます。",
@@ -690,6 +759,7 @@
"listeners.dashboard.settingsListener.triggerTemplate.progress.title": "テンプレートをダウンロードして初期化しています...",
"listeners.dashboard.settingsListener.triggerTemplate.download.error": "テンプレートのダウンロードに失敗しました。",
"listeners.dashboard.settingsListener.triggerTemplate.init.error": "テンプレートの初期化に失敗しました。",
"listeners.dashboard.settingsListener.setSecretValue.message": "設定が更新されました。",
"listeners.dashboard.snippetListener.addSnippet.missingFields.warning": "スニペットのタイトルまたはbodyが空です。",
"listeners.dashboard.snippetListener.addSnippet.exists.warning": "同じタイトルのスニペットが既に存在しています。",
@@ -700,6 +770,7 @@
"listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。",
"listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "記事データがありません。",
"listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "データファイルのエントリーが見つかりませんでした。",
"listeners.panel.dataListener.pushMetadata.frontMatter.error": "front matterの解析中にエラーが発生しまいた。ファイルの内容を確認してください。",
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。",

View File

@@ -37,10 +37,12 @@
"common.back": "Back",
"common.open": "Open",
"common.openWithValue": "Open: {0}",
"common.openCustomActions": "Open custom actions",
"common.view": "View",
"common.translate": "Translate",
"common.languages": "Languages",
"common.scripts": "Scripts",
"common.rename": "Rename",
"loading.initPages": "Loading content",
@@ -72,7 +74,7 @@
"settings.integrationsView.deepl.title": "DeepL",
"settings.integrationsView.deepl.intput.label": "API key",
"settings.integrationsView.deepl.intput.placeholder": "Enter your Azure Translator API key",
"settings.integrationsView.deepl.intput.placeholder": "Enter your Deepl API key",
"settings.integrationsView.azure.title": "Azure AI Translator Service",
"settings.integrationsView.azure.intput.label": "Subscription key",
@@ -207,7 +209,7 @@
"dashboard.header.tabs.contents": "Contents",
"dashboard.header.tabs.media": "Media",
"dashboard.header.tabs.snippets": "Snippets",
"dashboard.header.tabs.data": "data",
"dashboard.header.tabs.data": "Data",
"dashboard.header.tabs.taxonomies": "Taxonomies",
"dashboard.header.viewSwitch.toGrid": "Change to grid",
@@ -272,6 +274,8 @@
"dashboard.preview.button.refresh.title": "Refresh",
"dashboard.preview.button.open.title": "Open",
"dashboard.snippetsView.item.type.content": "Content snippet",
"dashboard.snippetsView.item.type.media": "Media snippet",
"dashboard.snippetsView.item.quickAction.editSnippet": "Edit snippet",
"dashboard.snippetsView.item.quickAction.deleteSnippet": "Delete snippet",
"dashboard.snippetsView.item.quickAction.viewSnippet": "View snippet file",
@@ -465,6 +469,7 @@
"panel.globalSettings.action.server.placeholder": "Example: {0}",
"panel.metadata.title": "Metadata",
"panel.metadata.focusProblems": "Check the problems view for more information",
"panel.otherActions.title": "Other actions",
"panel.otherActions.writingSettings.enabled": "Writing settings enabled",
@@ -525,6 +530,10 @@
"commands.article.setDate.error": "Something failed while parsing the date format. Check your \"{0}\" setting.",
"commands.article.updateSlug.error": "Failed to rename file: {0}",
"commands.article.rename.fileNotExists.error": "The file did not exist",
"commands.article.rename.fileExists.error": "A file with the name \"{0}\" already exists",
"commands.article.rename.fileName.title": "Rename: {0}",
"commands.article.rename.fileName.prompt": "File name",
"commands.cache.cleared": "Cache cleared",
@@ -718,6 +727,7 @@
"helpers.settingsHelper.readConfig.progress.title": "{0}: Reading dynamic config file...",
"helpers.settingsHelper.readConfig.error": "Error reading your configuration.",
"helpers.settingsHelper.refreshConfig.success": "Settings have been refreshed.",
"helpers.settingsHelper.safeUpdate.warning": "Cannot update setting \"{0}\" because you've extended or split the Front Matter CMS configuration. Please manually add your changes. Check the output for the setting update.",
"helpers.taxonomyHelper.rename.input.title": "Rename the {0}",
"helpers.taxonomyHelper.rename.validate.equalValue": "The new value must be different from the old one.",
@@ -763,6 +773,7 @@
"listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
"listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "No article data",
"listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "Couldn't find data file entries",
"listeners.panel.dataListener.pushMetadata.frontMatter.error": "Something went wrong while parsing your front matter. Please check the contents of your file.",
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor",

209
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "vscode-front-matter-beta",
"version": "10.1.0",
"version": "10.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-front-matter-beta",
"version": "10.1.0",
"version": "10.2.0",
"license": "MIT",
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.0.6"
@@ -21,7 +21,6 @@
"@sentry/react": "^6.19.7",
"@sentry/tracing": "^6.19.7",
"@tailwindcss/forms": "^0.5.3",
"@types/glob": "7.1.3",
"@types/invariant": "^2.2.35",
"@types/js-yaml": "^4.0.9",
"@types/lodash.omit": "^4.5.7",
@@ -34,7 +33,7 @@
"@types/react": "17.0.0",
"@types/react-datepicker": "^4.8.0",
"@types/react-dom": "17.0.0",
"@types/vscode": "^1.73.0",
"@types/vscode": "^1.90.0",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"@vscode-elements/elements": "^1.2.0",
@@ -54,7 +53,7 @@
"eslint": "^8.33.0",
"fuse.js": "6.5.3",
"github-directory-downloader": "^1.3.6",
"glob": "7.1.6",
"glob": "^10.3.12",
"gray-matter": "4.0.3",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
@@ -111,7 +110,7 @@
"yawn-yaml": "^1.5.0"
},
"engines": {
"vscode": "^1.73.0"
"vscode": "^1.90.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -1813,16 +1812,6 @@
"@types/send": "*"
}
},
"node_modules/@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"dependencies": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
@@ -1925,12 +1914,6 @@
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
"dev": true
},
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -2101,9 +2084,9 @@
"dev": true
},
"node_modules/@types/vscode": {
"version": "1.86.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz",
"integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==",
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz",
"integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==",
"dev": true
},
"node_modules/@types/vscode-webview": {
@@ -3043,13 +3026,13 @@
}
},
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -3057,7 +3040,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -3590,9 +3573,9 @@
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -4590,17 +4573,17 @@
"dev": true
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -4867,9 +4850,9 @@
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -5110,20 +5093,22 @@
}
},
"node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "*"
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -5147,6 +5132,30 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -8534,12 +8543,12 @@
"dev": true
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dev": true,
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@@ -8550,9 +8559,9 @@
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -9174,9 +9183,9 @@
}
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
@@ -10551,6 +10560,26 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -11442,52 +11471,6 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/sucrase/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/sucrase/node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -11969,9 +11952,9 @@
}
},
"node_modules/undici": {
"version": "5.28.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz",
"integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==",
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"dev": true,
"dependencies": {
"@fastify/busboy": "^2.0.0"
@@ -12595,9 +12578,9 @@
}
},
"node_modules/webpack-dev-middleware": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
"integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",

View File

@@ -3,15 +3,14 @@
"displayName": "Front Matter CMS",
"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, Docusaurus, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "10.1.0",
"version": "10.2.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
"color": "#0e131f",
"theme": "dark"
},
"badges": [
{
"badges": [{
"description": "version",
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
"href": "https://github.com/estruyf/vscode-front-matter"
@@ -27,10 +26,13 @@
},
"qna": "https://github.com/estruyf/vscode-front-matter/discussions",
"engines": {
"vscode": "^1.73.0"
"vscode": "^1.90.0"
},
"l10n": "./l10n",
"categories": [
"AI",
"Chat",
"Visualization",
"Other"
],
"keywords": [
@@ -51,8 +53,7 @@
},
"activationEvents": [
"workspaceContains:**/.frontmatter",
"workspaceContains:**/frontmatter.json",
"onStartupFinished"
"workspaceContains:**/frontmatter.json"
],
"main": "./dist/extension.js",
"contributes": {
@@ -71,11 +72,31 @@
"**/.frontmatter/config/*.json": "jsonc"
}
},
"keybindings": [
{
"chatParticipants": [{
"id": "frontMatter.chat",
"name": "fm",
"fullName": "Front Matter",
"description": "Front Matter CMS chat",
"isSticky": true,
"commands": [{
"name": "docs",
"description": "Ask a question about Front Matter CMS and we will try to help you out."
}, {
"name": "create",
"description": "Create a new Front Matter CMS project.",
"isSticky": true
}]
}],
"keybindings": [{
"command": "frontMatter.dashboard",
"key": "alt+d"
},
{
"command": "workbench.action.webview.reloadWebviewAction",
"key": "ctrl+r",
"mac": "cmd+r",
"when": "activeWebviewPanelId == frontMatterDashboard"
},
{
"command": "frontMatter.insertMedia",
"key": "ctrl+shift+i",
@@ -90,23 +111,19 @@
}
],
"viewsContainers": {
"activitybar": [
{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}
]
"activitybar": [{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}]
},
"views": {
"frontmatter-explorer": [
{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}
]
"frontmatter-explorer": [{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}]
},
"configuration": {
"title": "%settings.configuration.title%",
@@ -174,8 +191,7 @@
"frontMatter.content.defaultFileType": {
"type": "string",
"default": "md",
"oneOf": [
{
"oneOf": [{
"enum": [
"md",
"mdx"
@@ -191,8 +207,7 @@
"frontMatter.content.defaultSorting": {
"type": "string",
"default": "",
"oneOf": [
{
"oneOf": [{
"enum": [
"LastModifiedAsc",
"LastModifiedDesc",
@@ -293,6 +308,10 @@
"default": null,
"description": "%setting.frontMatter.content.pageFolders.items.properties.previewPath.description%"
},
"trailingSlash": {
"type": "boolean",
"description": "%setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description%"
},
"filePrefix": {
"type": [
"null",
@@ -540,8 +559,7 @@
"categories"
],
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
"items": [
{
"items": [{
"type": "string",
"enum": [
"contentFolders",
@@ -614,8 +632,7 @@
"command": {
"$id": "#scriptCommand",
"type": "string",
"anyOf": [
{
"anyOf": [{
"enum": [
"node",
"bash",
@@ -811,8 +828,7 @@
"title",
"file"
],
"anyOf": [
{
"anyOf": [{
"required": [
"schema"
]
@@ -866,8 +882,7 @@
"id",
"path"
],
"anyOf": [
{
"anyOf": [{
"required": [
"schema"
]
@@ -1108,29 +1123,26 @@
}
}
},
"default": [
{
"name": "default",
"fileTypes": null,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}
],
"default": [{
"name": "default",
"fileTypes": null,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}],
"scope": "Media"
},
"frontMatter.media.supportedMimeTypes": {
@@ -1181,6 +1193,12 @@
"markdownDescription": "%setting.frontMatter.preview.pathName.markdownDescription%",
"scope": "Site preview"
},
"frontMatter.preview.trailingSlash": {
"type": "string",
"default": "",
"markdownDescription": "%setting.frontMatter.preview.trailingSlash.markdownDescription%",
"scope": "Site preview"
},
"frontMatter.site.baseURL": {
"type": "string",
"default": "",
@@ -1360,8 +1378,7 @@
"default": "",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
"not": {
"anyOf": [
{
"anyOf": [{
"const": ""
},
{
@@ -1555,8 +1572,7 @@
"type",
"name"
],
"allOf": [
{
"allOf": [{
"if": {
"properties": {
"type": {
@@ -1727,6 +1743,10 @@
"default": null,
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description%"
},
"trailingSlash": {
"type": "boolean",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description%"
},
"slugTemplate": {
"type": [
"null",
@@ -1764,51 +1784,48 @@
"fields"
]
},
"default": [
{
"name": "default",
"pageBundle": false,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}
],
"default": [{
"name": "default",
"pageBundle": false,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}],
"scope": "Taxonomy"
},
"frontMatter.taxonomy.customTaxonomy": {
@@ -1821,8 +1838,7 @@
"type": "string",
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
"not": {
"anyOf": [
{
"anyOf": [{
"const": ""
},
{
@@ -2004,11 +2020,20 @@
"frontMatter.website.host": {
"type": "string",
"markdownDescription": "%setting.frontMatter.website.host.markdownDescription%"
},
"frontMatter.logging": {
"type": "string",
"default": "info",
"enum": [
"error",
"warn",
"info",
"verbose"
]
}
}
},
"commands": [
{
"commands": [{
"command": "frontMatter.project.switch",
"title": "%command.frontMatter.project.switch%",
"category": "Front Matter",
@@ -2334,21 +2359,16 @@
}
}
],
"submenus": [
{
"id": "frontmatter.submenu",
"label": "Front Matter"
}
],
"submenus": [{
"id": "frontmatter.submenu",
"label": "Front Matter"
}],
"menus": {
"webview/context": [
{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}
],
"editor/title": [
{
"webview/context": [{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}],
"editor/title": [{
"command": "frontMatter.markup.heading",
"group": "navigation@-133",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
@@ -2434,14 +2454,11 @@
"when": "resourceFilename == 'frontmatter.json'"
}
],
"explorer/context": [
{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}
],
"frontmatter.submenu": [
{
"explorer/context": [{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}],
"frontmatter.submenu": [{
"command": "frontMatter.createFromTemplate",
"when": "explorerResourceIsFolder",
"group": "frontmatter@1"
@@ -2457,8 +2474,7 @@
"group": "frontmatter@3"
}
],
"commandPalette": [
{
"commandPalette": [{
"command": "frontMatter.init",
"when": "frontMatterCanInit"
},
@@ -2635,8 +2651,7 @@
"when": "frontMatter:file:isValid == true"
}
],
"view/title": [
{
"view/title": [{
"command": "frontMatter.chatbot",
"group": "navigation@0",
"when": "view == frontMatter.explorer"
@@ -2668,57 +2683,64 @@
}
]
},
"grammars": [
{
"languages": [{
"id": "frontmatter.project.output",
"mimetypes": [
"text/x-code-output"
]
}],
"grammars": [{
"path": "./syntaxes/hugo.tmLanguage.json",
"scopeName": "frontmatter.markdown.hugo",
"injectTo": [
"text.html.markdown"
]
},
{
"language": "frontmatter.project.output",
"scopeName": "frontmatter.project.output",
"path": "./syntaxes/frontmatter-output.tmLanguage.json"
}
],
"walkthroughs": [
{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [
{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
"completionEvents": [
"onContext:frontMatterInitialized"
]
"walkthroughs": [{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
"completionEvents": [
"onContext:frontMatterInitialized"
]
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}
]
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}]
},
"scripts": {
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
@@ -2752,7 +2774,6 @@
"@sentry/react": "^6.19.7",
"@sentry/tracing": "^6.19.7",
"@tailwindcss/forms": "^0.5.3",
"@types/glob": "7.1.3",
"@types/invariant": "^2.2.35",
"@types/js-yaml": "^4.0.9",
"@types/lodash.omit": "^4.5.7",
@@ -2765,7 +2786,7 @@
"@types/react": "17.0.0",
"@types/react-datepicker": "^4.8.0",
"@types/react-dom": "17.0.0",
"@types/vscode": "^1.73.0",
"@types/vscode": "^1.90.0",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"@vscode-elements/elements": "^1.2.0",
@@ -2785,7 +2806,7 @@
"eslint": "^8.33.0",
"fuse.js": "6.5.3",
"github-directory-downloader": "^1.3.6",
"glob": "7.1.6",
"glob": "^10.3.12",
"gray-matter": "4.0.3",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
@@ -2847,4 +2868,4 @@
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.0.6"
}
}
}

View File

@@ -48,6 +48,7 @@
"command.frontMatter.markup.unorderedlist": "順序なしリスト",
"command.frontMatter.git.sync": "同期",
"command.frontMatter.cache.clear": "キャッシュをクリア",
"command.frontMatter.i18n.create": "新しい翻訳を作成",
"settings.configuration.title": "Front Matter: チームで作業する場合はfrontmatter.jsonで設定してください。",
"setting.frontMatter.projects.markdownDescription": "Front Matter CMSを利用するプロジェクトを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)",
"setting.frontMatter.projects.items.properties.name.markdownDescription": "プロジェクトの名前を指定します。",
@@ -60,7 +61,7 @@
"setting.frontMatter.content.defaultFileType.markdownDescription": "新しい記事を作成する際の既定のファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultfiletype)",
"setting.frontMatter.content.defaultSorting.markdownDescription": "ダッシュボード上に表示される記事一覧の既定の並び順を設定します。Enum列挙型や任意のIDを指定してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultsorting)",
"setting.frontMatter.content.draftField.markdownDescription": "記事の下書きステータスを管理するフィールドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.draftfield)",
"setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの種類",
"setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの",
"setting.frontMatter.content.draftField.properties.name.description": "使用するフィールドの名前",
"setting.frontMatter.content.draftField.properties.invert.description": "既定では、記事が下書きの場合、下書きフィールドは true に設定されます。これを true に設定すると、false に設定されます。",
"setting.frontMatter.content.draftField.properties.choices.description": "フィールドの選択肢のリスト",
@@ -75,6 +76,12 @@
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。",
"setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "現在の場所に使用できる記事タイプを定義します。定義しない場合は、全ての記事タイプを使用できます。",
"setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "フォルダー内の新しい記事の作成を無効にします。",
"setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "ページフォルダー用デフォルトのロケールIDを設定します。このフォルダー内の全ての記事が`frontMatter.content.i18n`で設定された言語へ翻訳可能になります。",
"setting.frontMatter.content.pageFolders.items.properties.locales.description": "ページフォルダーで利用するロケールを設定します。この設定は記事の翻訳に使用されます。",
"setting.frontMatter.content.i18n.markdownDescription": "ウェブサイトで利用するロケールを設定します。この設定はページフォルダーレベルの設定より優先されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)",
"setting.frontMatter.content.i18n.items.properties.title.description": "言語名",
"setting.frontMatter.content.i18n.items.properties.locale.description": "言語コード",
"setting.frontMatter.content.i18n.items.properties.path.description": "言語フォルダーの相対パス",
"setting.frontMatter.content.placeholders.markdownDescription": "記事タイプとテンプレートで使用するプレースホルダーを配列で定義して、記事のfront matterを自動で入力できるようにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.placeholders)",
"setting.frontMatter.content.placeholders.items.properties.id.description": "プレースホルダーのIDを記事タイプまたはテンプレートで、次のように使用します: {{placeholder}}",
"setting.frontMatter.content.placeholders.items.properties.value.description": "プレースホルダーの値",
@@ -92,6 +99,7 @@
"setting.frontMatter.content.sorting.items.properties.type.description": "フィールド値の型",
"setting.frontMatter.content.supportedFileTypes.markdownDescription": "Front Matterでサポートされるファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.supportedfiletypes)",
"setting.frontMatter.content.wysiwyg.markdownDescription": "What You See, Is What You GetWYSIWYGMarkdownコントロールを有効にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.wysiwyg)",
"setting.frontMatter.content.filters.markdownDescription": "ダッシュボードで利用するフィルターを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.filters)",
"setting.frontMatter.custom.scripts.markdownDescription": "実行するNode.jsスクリプトのパスを指定します。現在のファイルのパスが引数として渡されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.custom.scripts)",
"setting.frontMatter.custom.scripts.items.properties.id.description": "スクリプトの ID。",
"setting.frontMatter.custom.scripts.items.properties.title.description": "スクリプトに付けるタイトル。ボタンのタイトルとして表示されます。",
@@ -100,8 +108,8 @@
"setting.frontMatter.custom.scripts.items.properties.bulk.description": "全ての記事ファイルに対してスクリプトを実行する",
"setting.frontMatter.custom.scripts.items.properties.output.description": "スクリプト出力を出力する場所を定義します。デフォルトは通知表示ですが、エディターパネルに表示するように指定できます。",
"setting.frontMatter.custom.scripts.items.properties.outputType.description": "エディター・パネルの出力のタイプ。たとえば、「マークダウン」に変更するために使用できます",
"setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型",
"setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類",
"setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型",
"setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類",
"setting.frontMatter.custom.scripts.items.properties.hidden.description": "UI からアクションを非表示にする",
"setting.frontMatter.custom.scripts.items.properties.environments.items.properties.type.description": "スクリプトを使用する必要がある環境タイプ",
"setting.frontMatter.custom.scripts.items.properties.environments.items.properties.script.description": "実行するスクリプトへのパス",
@@ -152,7 +160,18 @@
"setting.frontMatter.global.notifications.markdownDescription": "表示したい通知を設定します。既定では全ての通知が表示されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.notifications)",
"setting.frontMatter.global.disabledNotifications.markdownDescription": "これは、Front Matter CMSで無効にできる通知タイプの配列です。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications)",
"setting.frontMatter.media.defaultSorting.markdownDescription": "ダッシュボードのメディア一覧での既定の並び順を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting)",
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアファイルでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアコンテンツでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
"setting.frontMatter.media.contentTypes.markdownDescription": "Front Matterで利用するメディアコンテンツのタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.contenttypes)",
"setting.frontMatter.media.contentTypes.items.description": "Front Matterで利用するメディアフコンテンツのタイプについての説明。",
"setting.frontMatter.media.contentTypes.items.properties.name.description": "メディアコンテンツのタイプ名",
"setting.frontMatter.media.contentTypes.items.properties.fileTypes.description": "利用可能とするメディアコンテンツのタイプの指定",
"setting.frontMatter.media.contentTypes.items.properties.fields.description": "メディアコンテンツのフィールドを設定します。",
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description": "UI表示用のタイトル",
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description": "フィールドの名前",
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "フィールド値の型",
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "単一行フィールド",
"setting.frontMatter.panel.freeform.markdownDescription": "未登録のタグ/カテゴリーをタグピッカーに入力可能にするかどうかを設定します有効にすると、後で保存するオプションが使えます。規定値はtrue。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
"setting.frontMatter.panel.actions.disabled.markdownDescription": "パネル内で非表示にしたいコマンドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
"setting.frontMatter.preview.host.markdownDescription": "プレビュー表示に使用するホストのURLを設定します`http://localhost:1313`)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
@@ -164,11 +183,11 @@
"setting.frontMatter.taxonomy.commaSeparatedFields.items.description": "コンマ区切りの配列として使用するフィールドの名前。",
"setting.frontMatter.taxonomy.contentTypes.markdownDescription": "記事・ページ・その他で利用したい記事タイプを設定します。front matterで正しく`type`が設定されていることを確認してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.contenttypes)",
"setting.frontMatter.taxonomy.contentTypes.items.description": "Front Matterで使用する記事タイプを定義します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義する",
"setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fileType.description": "作成する記事タイプを指定します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義する",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.description": "Front Matterで使用する記事タイプを定義します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールドの種類を定義する",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールド値の型",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.name.description": "使用するフィールドの名前",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.title.description": "UI に表示するタイトル",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.description.description": "UI に表示する説明",
@@ -176,8 +195,8 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.description": "選択肢を定義する",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "選択肢 ID",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "選択肢のタイトル",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールドである",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) である",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールド",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) ",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "複数の値を選択できますか?",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "画像フィールドをプレビューとして使用できるかどうかを指定します。記事タイプごとに使用できるプレビュー画像は1つだけです。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "メタデータ セクションからフィールドを非表示にしますか?",
@@ -210,6 +229,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "比較で大文字と小文字を区別するかどうかを指定します。デフォルト: true",
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "新しい記事を作成するときにフォルダーを作成するかどうかを指定します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "記事タイプのカスタム プレビュー パスを定義します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "記事タイプのカスタムスラッグのテンプレートを設定します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "新しい記事の作成に使用できるオプションのテンプレート。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "新しい記事の作成後に使用できるオプションのポストスクリプト。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。",
@@ -236,6 +256,7 @@
"setting.frontMatter.taxonomy.seoTitleLength.markdownDescription": "SEOに適したタイトルの文字数を設定します。`-1`に設定するとオフになります。)[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlelength)",
"setting.frontMatter.taxonomy.slugPrefix.markdownDescription": "スラッグに付与する接頭辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugprefix)",
"setting.frontMatter.taxonomy.slugSuffix.markdownDescription": "スラッグに付与する接尾辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugsuffix)",
"setting.frontMatter.taxonomy.slugTemplate.markdownDescription": "記事を作成する際のカスタムスラッグのテンプレートを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugtemplate)",
"setting.frontMatter.taxonomy.tags.markdownDescription": "Front Matterで利用するタグを管理します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags)",
"setting.frontMatter.telemetry.disable.markdownDescription": "利用状況の送信をオフにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable)",
"setting.frontMatter.templates.enabled.markdownDescription": "テンプレート機能を利用します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled)",
@@ -250,5 +271,8 @@
"command.frontMatter.settings.refresh": "Front Matterの設定の再読み込み",
"setting.frontMatter.config.dynamicFilePath.markdownDescription": "動的構成ファイルへのパスを指定します (例: [[ワークスペース]]/config.js)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)",
"setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "記事をサブコンテンツとして作成可能かどうかを設定します。",
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。"
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。",
"setting.frontMatter.git.disableOnBranches.markdownDescription": "Git Actionsを無効化したいブランチを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches)",
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "特定のブランチを更新する場合にコミットメッセージを必須にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage)"
}

View File

@@ -73,11 +73,13 @@
"setting.frontMatter.content.pageFolders.items.properties.path.description": "Path of the folder",
"setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description": "Exclude sub-directories",
"setting.frontMatter.content.pageFolders.items.properties.previewPath.description": "Defines a custom preview path for the folder.",
"setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "Defines a prefix for the file name.",
"setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "Defines which content types can be used for the current location. If not defined, all content types will be available.",
"setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "Disable the creation of new content in the folder.",
"setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "Set the default locale ID for the page folder. All content from this folder is translatable to the languages defined in the `frontMatter.content.i18n` setting.",
"setting.frontMatter.content.pageFolders.items.properties.locales.description": "Define the locales for the page folder. This will be used for the translation of the content.",
"setting.frontMatter.content.pageFolders.items.properties.slugTemplate.description": "Defines a custom slug template.",
"setting.frontMatter.content.i18n.markdownDescription": "Specify the locales you want to use for your website. This setting can be overwritten on page folder level. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)",
"setting.frontMatter.content.i18n.items.properties.title.description": "Title of the locale",
"setting.frontMatter.content.i18n.items.properties.locale.description": "Locale code",
@@ -175,6 +177,7 @@
"setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
"setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
"setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
"setting.frontMatter.preview.trailingSlash.markdownDescription": "Specify if you want to add a trailing slash to the preview URL. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.trailingslash)",
"setting.frontMatter.preview.pathName.markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.pathname)",
"setting.frontMatter.site.baseURL.markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.site.baseurl)",
"setting.frontMatter.taxonomy.alignFilename.markdownDescription": "Align the filename with the new slug when it gets generated. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.alignfilename)",
@@ -229,6 +232,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "Specify if the comparison is case sensitive. Default: true",
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "Specify if you want to create a folder when creating new content.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "Defines a custom preview path for the content type.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "Defines a custom slug template for the content type.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "An optional template that can be used for creating new content.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "An optional post script that can be used after new content creation.",

View File

@@ -1,3 +1,13 @@
import {
Position,
TextDocument,
TextDocumentWillSaveEvent,
TextEdit,
Uri,
commands,
window,
workspace
} from 'vscode';
import { Folders } from './Folders';
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
import { isValidFile } from './../helpers/isValidFile';
@@ -13,7 +23,6 @@ import {
TelemetryEvent,
SETTING_SLUG_TEMPLATE
} from './../constants';
import * as vscode from 'vscode';
import { CustomPlaceholder, Field } from '../models';
import { format } from 'date-fns';
import {
@@ -33,17 +42,35 @@ import { Telemetry } from '../helpers/Telemetry';
import { ParsedFrontMatter } from '../parsers';
import { MediaListener } from '../listeners/panel';
import { NavigationType } from '../dashboardWebView/models';
import { Position } from 'vscode';
import { SNIPPET } from '../constants/Snippet';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Article {
/**
* Registers the commands for the Article class.
*
* @param subscriptions - The array of subscriptions to register the commands with.
*/
public static async registerCommands(subscriptions: unknown[]) {
subscriptions.push(
commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate)
);
subscriptions.push(commands.registerCommand(COMMAND_NAME.generateSlug, Article.updateSlug));
// Inserting an image in Markdown
subscriptions.push(commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia));
// Inserting a snippet in Markdown
subscriptions.push(commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet));
}
/**
* Sets the article date
*/
public static async setDate() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
@@ -53,7 +80,7 @@ export class Article {
return;
}
article = this.updateDate(article);
article = await this.updateDate(article);
try {
ArticleHelper.update(editor, article);
@@ -68,8 +95,8 @@ export class Article {
* Update the date in the front matter
* @param article
*/
public static updateDate(article: ParsedFrontMatter) {
article.data = ArticleHelper.updateDates(article);
public static async updateDate(article: ParsedFrontMatter) {
article.data = await ArticleHelper.updateDates(article);
return article;
}
@@ -77,12 +104,12 @@ export class Article {
* Sets the article lastmod date
*/
public static async setLastModifiedDate() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const updatedArticle = this.setLastModifiedDateInner(editor.document);
const updatedArticle = await this.setLastModifiedDateInner(editor.document);
if (typeof updatedArticle === 'undefined') {
return;
@@ -91,10 +118,8 @@ export class Article {
ArticleHelper.update(editor, updatedArticle as ParsedFrontMatter);
}
public static async setLastModifiedDateOnSave(
document: vscode.TextDocument
): Promise<vscode.TextEdit[]> {
const updatedArticle = this.setLastModifiedDateInner(document);
public static async setLastModifiedDateOnSave(document: TextDocument): Promise<TextEdit[]> {
const updatedArticle = await this.setLastModifiedDateInner(document);
if (typeof updatedArticle === 'undefined') {
return [];
@@ -105,9 +130,9 @@ export class Article {
return [update];
}
private static setLastModifiedDateInner(
document: vscode.TextDocument
): ParsedFrontMatter | undefined {
private static async setLastModifiedDateInner(
document: TextDocument
): Promise<ParsedFrontMatter | undefined> {
const article = ArticleHelper.getFrontMatterFromDocument(document);
// Only set the date, if there is already front matter set
@@ -116,7 +141,7 @@ export class Article {
}
const cloneArticle = Object.assign({}, article);
const dateField = ArticleHelper.getModifiedDateField(article);
const dateField = await ArticleHelper.getModifiedDateField(article);
try {
const fieldName = dateField?.name || DefaultFields.LastModified;
cloneArticle.data[fieldName] = Article.formatDate(new Date(), dateField?.dateFormat);
@@ -160,7 +185,7 @@ export class Article {
Telemetry.send(TelemetryEvent.generateSlug);
const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
@@ -172,8 +197,12 @@ export class Article {
}
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
const contentType = ArticleHelper.getContentType(article);
filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType);
const contentType = await ArticleHelper.getContentType(article);
filePrefix = await ArticleHelper.getFilePrefix(
filePrefix,
editor.document.uri.fsPath,
contentType
);
const titleField = 'title';
const articleTitle: string = article.data[titleField];
@@ -219,7 +248,7 @@ export class Article {
// Check if the file name should be updated by the slug
// This is required for systems like Jekyll
if (updateFileName) {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (editor) {
const ext = extname(editor.document.fileName);
const fileName = basename(editor.document.fileName);
@@ -237,7 +266,7 @@ export class Article {
try {
await editor.document.save();
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
await workspace.fs.rename(editor.document.uri, Uri.file(newPath), {
overwrite: false
});
} catch (e: unknown) {
@@ -257,11 +286,18 @@ export class Article {
* Retrieve the slug from the front matter
*/
public static getSlug() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const file = parseWinPath(editor.document.fileName);
if (!isValidFile(file)) {
return;
}
const parsedFile = parse(file);
const slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE);
if (slugTemplate) {
if (slugTemplate === '{{title}}') {
@@ -277,16 +313,11 @@ export class Article {
}
}
const file = parseWinPath(editor.document.fileName);
if (!isValidFile(file)) {
return;
}
const parsedFile = parse(file);
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
if (parsedFile.name.toLowerCase() !== 'index') {
return parsedFile.name;
return `${prefix}${parsedFile.name}${suffix}`;
}
const folderName = basename(dirname(file));
@@ -297,7 +328,7 @@ export class Article {
* Toggle the page its draft mode
*/
public static async toggleDraft() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
@@ -315,13 +346,13 @@ export class Article {
* Article auto updater
* @param event
*/
public static async autoUpdate(event: vscode.TextDocumentWillSaveEvent) {
public static async autoUpdate(event: TextDocumentWillSaveEvent) {
const document = event.document;
if (document && ArticleHelper.isSupportedFile(document)) {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
// Is article located in one of the content folders
const folders = Folders.get();
const folders = Folders.getCached();
const documentPath = parseWinPath(document.fileName);
const folder = folders.find((f) => documentPath.startsWith(f.path));
if (!folder) {
@@ -355,19 +386,19 @@ export class Article {
* Insert an image from the media dashboard into the article
*/
public static async insertMedia() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
const contentType =
article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
article && article.data ? await ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
const position = editor.selection.active;
const selectionText = editor.document.getText(editor.selection);
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
await commands.executeCommand(COMMAND_NAME.dashboard, {
type: 'media',
data: {
pageBundle: !!contentType.pageBundle,
@@ -386,7 +417,7 @@ export class Article {
* Insert a snippet into the article
*/
public static async insertSnippet() {
const editor = vscode.window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
@@ -440,9 +471,9 @@ export class Article {
}
const article = ArticleHelper.getFrontMatter(editor);
const contentType = article ? ArticleHelper.getContentType(article) : undefined;
const contentType = article ? await ArticleHelper.getContentType(article) : undefined;
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
await commands.executeCommand(COMMAND_NAME.dashboard, {
type: NavigationType.Snippets,
data: {
fileTitle: article?.data.title || '',

View File

@@ -1,14 +1,99 @@
import { Telemetry } from './../helpers/Telemetry';
import { TelemetryEvent, PreviewCommands, GeneralCommands } from './../constants';
import { TelemetryEvent, PreviewCommands, GeneralCommands, WEBSITE_LINKS, COMMAND_NAME } from './../constants';
import { join } from 'path';
import { commands, Uri, ViewColumn, window } from 'vscode';
import {
CancellationToken,
chat,
ChatContext,
ChatRequest,
ChatResponseStream,
commands,
LanguageModelChatMessage,
lm,
Uri,
ViewColumn,
window
} from 'vscode';
import { Extension } from '../helpers';
import { WebviewHelper } from '@estruyf/vscode';
import { getLocalizationFile } from '../utils/getLocalizationFile';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { Folders } from '.';
export class Chatbot {
private static company: string;
private static chatId: number;
public static async register() {
const fmChat = chat.createChatParticipant('frontMatter.chat', this.handler);
fmChat.iconPath = Uri.joinPath(
Extension.getInstance().extensionPath,
'/assets/icons/frontmatter-short-teal.svg'
);
}
private static async handler(
request: ChatRequest,
context: ChatContext,
stream: ChatResponseStream,
token: CancellationToken
) {
const { command, prompt } = request;
if (command === 'docs' && prompt) {
if (!this.chatId || !this.company) {
stream.progress('Initializing the Front Matter AI...');
await this.initChat();
}
if (!this.company || !this.chatId) {
this.showAiError(stream);
return;
}
stream.progress('Fetching information from the docs...');
const data: {
answer: string;
answerId: number;
sources: string[];
} = await this.retrieveDocs(prompt);
if (!data.answer) {
this.showAiError(stream);
return;
}
stream.markdown(data.answer);
} else if (command === 'create') {
stream.progress(`Checking your configuration...`);
const messages = [LanguageModelChatMessage.User(`You are a kind and helpful assistant named Front Matter AI. You aim to provide support in managing Front Matter CMS and its content. If you don't know the answer to a question, you will guide the user to the documentation (https://frontmatter.codes/docs). You can use the "https://frontmatter.codes/docs" link as a reference. When returning code you will surround it in a MD code block, not HTML.`)];
messages.push(LanguageModelChatMessage.User(request.prompt));
const [model] = await lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4' });
try {
const chatResponse = await model.sendRequest(messages, {}, token);
for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
}
stream.button({
command: COMMAND_NAME.createByContentType,
title: 'Create new content',
});
} catch (err) {
this.showAiError(stream);
return;
}
} else {
stream.markdown('Sorry, I do not understand that command. Check out the [Front Matter CMS](https://frontmatter.codes/docs) documentation for more information.')
}
}
/**
* Open the Chatbot in the editor
*/
@@ -119,4 +204,50 @@ export class Chatbot {
Telemetry.send(TelemetryEvent.openChatbot);
}
private static async initChat() {
const response = await fetch(`${WEBSITE_LINKS.root}/api/ai-init`);
if (!response.ok) {
return;
}
const data = await response.json();
if (!data.company || !data.chatId) {
return;
}
this.company = data.company;
this.chatId = data.chatId;
}
private static async retrieveDocs(prompt: string) {
if (!this.company || !this.chatId) {
return;
}
const response = await fetch(`${WEBSITE_LINKS.root}/api/ai-chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'accept': '*',
},
body: JSON.stringify({
company: this.company,
chatId: this.chatId,
question: prompt,
}),
});
if (!response.ok) {
return;
}
return await response.json();
}
private static showAiError(stream: ChatResponseStream) {
stream.markdown(`Sorry, I am not able to connect to the Front Matter AI service. Please try again later or check out the [Front Matter CMS](https://frontmatter.codes/docs) documentation.`);
}
}

View File

@@ -1,10 +1,25 @@
import { commands, QuickPickItem, window } from 'vscode';
import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants';
import { Settings } from '../helpers';
import { Extension, Settings } from '../helpers';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Content {
/**
* Registers the commands for the Content class.
*/
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.createContent, Content.create));
}
/**
* Creates content based on user selection.
* If templates are enabled, shows a quick pick menu to choose between content type and template.
* If templates are disabled, executes the createByContentType command directly.
*/
public static async create() {
const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED);
if (!templatesEnabled) {

View File

@@ -24,7 +24,6 @@ import {
ExtensionListener,
SnippetListener,
TaxonomyListener,
LogListener,
LocalizationListener,
SsgListener
} from '../listeners/dashboard';
@@ -35,6 +34,7 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
import { NavigationType } from '../dashboardWebView/models';
import { ignoreMsgCommand } from '../utils';
export class Dashboard {
private static webview: WebviewPanel | null = null;
@@ -45,6 +45,13 @@ export class Dashboard {
return Dashboard._viewData;
}
public static setTitle(title: string) {
if (title && Dashboard.webview) {
Dashboard.webview.title =
title || `Front Matter ${l10n.t(LocalizationKey.commandsDashboardTitle)}`;
}
}
/**
* Init the dashboard
*/
@@ -223,7 +230,9 @@ export class Dashboard {
});
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
Logger.info(`Receiving message from webview: ${msg.command}`);
if (!ignoreMsgCommand(msg.command)) {
Logger.verbose(`Receiving message from dashboard: ${msg.command}`);
}
LocalizationListener.process(msg);
DashboardListener.process(msg);
@@ -237,7 +246,6 @@ export class Dashboard {
ModeListener.process(msg);
GitListener.process(msg);
TaxonomyListener.process(msg);
LogListener.process(msg);
SsgListener.process(msg);
});
}

View File

@@ -1,13 +1,26 @@
import { Folders } from './Folders';
import { ViewColumn, workspace } from 'vscode';
import { ViewColumn, commands, workspace } from 'vscode';
import ContentProvider from '../providers/ContentProvider';
import { join } from 'path';
import { ContentFolder } from '../models';
import { Settings } from '../helpers/SettingsHelper';
import {
COMMAND_NAME,
DEFAULT_FILE_TYPES,
SETTING_CONTENT_SUPPORTED_FILETYPES
} from '../constants';
import { Extension } from '../helpers';
export class Diagnostics {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show));
}
public static async show() {
const folders = Folders.get();
const folders = await Folders.get();
const projectName = Folders.getProjectFolderName();
const wsFolder = Folders.getWorkspaceFolder();
@@ -18,27 +31,38 @@ export class Diagnostics {
const all = await Diagnostics.allProjectFiles();
const logging = `# Project name
const fileTypes = Diagnostics.getFileTypes();
const logging = `# ${Extension.getInstance().displayName} - Diagnostics
Beta: \`${Extension.getInstance().isBetaVersion()}\`
Version: \`${Extension.getInstance().version}\`
## Project name
${projectName}
# Folders
## Workspace folder
${folders.map((f) => `- ${f.title}: "${f.path}"`).join('\n')}
\`${wsFolder ? wsFolder.fsPath : 'No workspace folder'}\`
# Workspace folder
${wsFolder ? wsFolder.fsPath : 'No workspace folder'}
# Total files
## Total files
${all}
# Folders to search files
## Folders
| Title | Path |
| ----- | ---- |
${folders.map((f) => `| ${f.title} | \`${f.path}\` |`).join('\n')}
### Files in folders
| Project start length | Search in | ${fileTypes.join(` | `)} |
|--- | --- | --- | --- | --- |
${folderData.join('\n')}
# Complete frontmatter.json config
## Complete frontmatter.json config
\`\`\`json
${JSON.stringify(Settings.globalConfig, null, 2)}
@@ -48,8 +72,12 @@ ${JSON.stringify(Settings.globalConfig, null, 2)}
ContentProvider.show(logging, `${projectName} diagnostics`, 'markdown', ViewColumn.One);
}
private static getFileTypes = (): string[] => {
return Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
};
private static async allProjectFiles() {
const allFiles = await workspace.findFiles(`**/*.*`);
const allFiles = await workspace.findFiles(`**/*.*`, '**/node_modules/**');
return `Total files found: ${allFiles.length}`;
}
@@ -59,22 +87,19 @@ ${JSON.stringify(Settings.globalConfig, null, 2)}
projectStart = projectStart?.replace(/\\/g, '/');
projectStart = projectStart?.startsWith('/') ? projectStart.substring(1) : projectStart;
const mdFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')
);
const mdxFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')
);
const markdownFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')
const fileTypes = Diagnostics.getFileTypes();
const fileTypeLengths = await Promise.all(
fileTypes.map(async (ft) => {
const path = join(projectStart || '', folder.excludeSubdir ? '/' : '**/', `*.${ft}`);
const files = await workspace.findFiles(path, '**/node_modules/**');
return (files || []).length;
})
);
return `- Project start length: ${projectStart.length} | Search in: "${join(
return `| ${projectStart.length} | \`${join(
projectStart,
folder.excludeSubdir ? '/' : '**/',
'*.*'
)}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${
markdownFiles.length
}`;
)}\` | ${fileTypeLengths.join(` | `)} |`;
}
}

View File

@@ -1,6 +1,7 @@
import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder';
import { Questions } from './../helpers/Questions';
import {
COMMAND_NAME,
SETTING_CONTENT_I18N,
SETTING_CONTENT_PAGE_FOLDERS,
SETTING_CONTENT_STATIC_FOLDER,
@@ -14,7 +15,7 @@ import { ContentFolder, FileInfo, FolderInfo, I18nConfig, StaticFolder } from '.
import uniqBy = require('lodash.uniqby');
import { Template } from './Template';
import { Notifications } from '../helpers/Notifications';
import { Logger, Settings, processTimePlaceholders } from '../helpers';
import { Extension, Logger, Settings, processTimePlaceholders } from '../helpers';
import { existsSync } from 'fs';
import { format } from 'date-fns';
import { Dashboard } from './Dashboard';
@@ -25,13 +26,27 @@ import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
import { Telemetry } from '../helpers/Telemetry';
import { glob } from 'glob';
import { mkdirAsync } from '../utils/mkdirAsync';
import { existsAsync } from '../utils';
import { existsAsync, isWindows } from '../utils';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
export class Folders {
private static _folders: ContentFolder[] = [];
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.createByTemplate, Folders.create));
}
public static clearCached() {
Logger.verbose(`Folders:clearCached`);
Folders._folders = [];
}
/**
* Add a media folder
* @returns
@@ -98,7 +113,8 @@ export class Folders {
return;
}
const folders = Folders.get().filter((f) => !f.disableCreation);
let folders = await Folders.get();
folders = folders.filter((f) => !f.disableCreation);
const location = folders.find((f) => f.path === selectedFolder.path);
if (location) {
const folderPath = Folders.getFolderPath(Uri.file(location.path));
@@ -122,7 +138,7 @@ export class Folders {
if (folder && folder.fsPath) {
const wslPath = folder.fsPath.replace(/\//g, '\\');
let folders = Folders.get();
let folders = await Folders.get();
const exists = folders.find(
(f) => f.path.includes(folder.fsPath) || f.path.includes(wslPath)
@@ -171,7 +187,7 @@ export class Folders {
*/
public static async unregister(folder: Uri) {
if (folder && folder.path) {
let folders = Folders.get();
let folders = await Folders.get();
folders = folders.filter((f) => f.path !== folder.fsPath);
await Folders.update(folders);
@@ -282,8 +298,9 @@ export class Folders {
* Get the registered folders information
*/
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
Logger.verbose('Folders:getInfo:start');
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
const folders = Folders.get();
const folders = await Folders.get();
if (folders && folders.length > 0) {
const folderInfo: FolderInfo[] = [];
@@ -295,9 +312,11 @@ export class Folders {
}
}
Logger.verbose('Folders:getInfo:end');
return folderInfo;
}
Logger.verbose('Folders:getInfo:end - no folders found');
return null;
}
@@ -305,7 +324,14 @@ export class Folders {
* Get the folder settings
* @returns
*/
public static get(): ContentFolder[] {
public static async get(): Promise<ContentFolder[]> {
Logger.verbose('Folders:get:start');
if (Folders._folders.length > 0) {
Logger.verbose('Folders:get:end - cached folders');
return Folders._folders;
}
const wsFolder = Folders.getWorkspaceFolder();
let folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
@@ -315,6 +341,26 @@ export class Folders {
const contentFolders: ContentFolder[] = [];
// Check if wildcard is used
const wildcardFolders = folders.filter((f) => f.path.includes('*'));
if (wildcardFolders && wildcardFolders.length > 0) {
for (const folder of wildcardFolders) {
folders = folders.filter((f) => f.path !== folder.path);
const folderPath = Folders.absWsFolder(folder, wsFolder);
const subFolders = await Folders.findFolders(folderPath);
for (const subFolder of subFolders) {
const subFolderPath = parseWinPath(subFolder);
folders.push({
...folder,
title: `${folder.title} (${subFolderPath.replace(wsFolder?.fsPath || '', '')})`,
path: subFolderPath
});
}
}
}
folders.forEach((folder) => {
if (!folder.title) {
folder.title = basename(folder.path);
@@ -333,11 +379,11 @@ export class Folders {
),
l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction),
l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction)
).then((answer) => {
).then(async (answer) => {
if (
answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction)
) {
const folders = Folders.get();
const folders = await Folders.get();
Folders.update(folders.filter((f) => f.path !== folder.path));
} else if (
answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction)
@@ -397,7 +443,17 @@ export class Folders {
}
});
return contentFolders.filter((folder) => folder !== null) as ContentFolder[];
Logger.verbose('Folders:get:end');
Folders._folders = contentFolders.filter((folder) => folder !== null) as ContentFolder[];
return Folders._folders;
}
/**
* Get the cached folder settings
* @returns {ContentFolder[]} - The cached folder settings
*/
public static getCached(): ContentFolder[] {
return Folders._folders;
}
/**
@@ -405,8 +461,13 @@ export class Folders {
* @param folders
*/
public static async update(folders: ContentFolder[]) {
const originalFolders = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const wsFolder = Folders.getWorkspaceFolder();
// Filter out the locale folders
folders = folders.filter((folder) => !folder.locale || folder.locale === folder.defaultLocale);
// Remove the internal FM properties
const folderDetails = folders
.map((folder) => {
const detail = {
@@ -414,17 +475,29 @@ export class Folders {
path: Folders.relWsFolder(folder, wsFolder)
};
if (detail['$schema'] || detail.extended) {
return null;
delete detail['$schema'];
delete detail.extended;
if (detail.locale && detail.locale === detail.defaultLocale) {
// Check if the folder was on the original list
const originalFolder = originalFolders.find((f) => f.path === folder.originalPath);
if (originalFolder && !originalFolder.locales && folder.locales) {
delete detail.locales;
}
delete detail.localeSourcePath;
delete detail.localeTitle;
}
delete detail.locale;
delete detail.originalPath;
return detail;
})
.filter((folder) => folder !== null);
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
// Reinitialize the folder listeners
PagesListener.startWatchers();
@@ -437,11 +510,10 @@ export class Folders {
*/
public static getAbsFilePath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
if (filePath.includes(WORKSPACE_PLACEHOLDER)) {
let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
return parseWinPath(absPath);
}
@@ -455,7 +527,6 @@ export class Folders {
*/
public static getAbsFolderPath(folderPath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
let absPath = '';
if (folderPath.includes(WORKSPACE_PLACEHOLDER)) {
@@ -464,7 +535,7 @@ export class Folders {
absPath = join(parseWinPath(wsFolder?.fsPath || ''), folderPath);
}
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
return parseWinPath(absPath);
}
@@ -475,14 +546,13 @@ export class Folders {
* @returns
*/
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
if (absPath.includes('../')) {
absPath = join(absPath);
}
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
return parseWinPath(absPath);
}
@@ -494,12 +564,11 @@ export class Folders {
* @returns
*/
public static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = parseWinPath(folder.path).replace(
parseWinPath(wsFolder?.fsPath || ''),
WORKSPACE_PLACEHOLDER
);
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
return absPath;
}
@@ -507,9 +576,11 @@ export class Folders {
* Find the content folders
*/
public static async getContentFolders() {
Logger.verbose('Folders:getContentFolders:start');
// Find folders that contain files
const wsFolder = Folders.getWorkspaceFolder();
if (!wsFolder) {
Logger.error('Folders:getContentFolders:workspaceFolderNotFound');
return [];
}
@@ -530,7 +601,7 @@ export class Folders {
folders = [...folders, ...(await this.findFolders(pattern))];
} catch (e) {
Logger.error(
`Something went wrong while searching for folders with pattern "${pattern}": ${
`Folders:getContentFolders:error: Something went wrong while searching for folders with pattern "${pattern}": ${
(e as Error).message
}`
);
@@ -543,6 +614,8 @@ export class Folders {
}
const uniqueFolders = [...new Set(folders)];
Logger.verbose('Folders:getContentFolders:end');
return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder));
}
@@ -551,8 +624,8 @@ export class Folders {
* @param folderPath
* @returns
*/
public static getFilePrefixByFolderPath(folderPath: string) {
const folders = Folders.get();
public static async getFilePrefixByFolderPath(folderPath: string) {
const folders = await Folders.get();
const pageFolder = folders.find((f) => parseWinPath(f.path) === parseWinPath(folderPath));
if (pageFolder && typeof pageFolder.filePrefix !== 'undefined') {
@@ -567,8 +640,8 @@ export class Folders {
* @param filePath
* @returns
*/
public static getFilePrefixBeFilePath(filePath: string) {
const folders = Folders.get();
public static async getFilePrefixBeFilePath(filePath: string) {
const folders = await Folders.get();
if (folders.length > 0) {
filePath = parseWinPath(filePath);
@@ -596,8 +669,10 @@ export class Folders {
* @param filePath - The file path to match against the page folders.
* @returns The page folder that matches the file path, or undefined if no match is found.
*/
public static getPageFolderByFilePath(filePath: string): ContentFolder | undefined {
const folders = Folders.get();
public static async getPageFolderByFilePath(
filePath: string
): Promise<ContentFolder | undefined> {
const folders = await Folders.get();
const parsedPath = parseWinPath(filePath);
const pageFolderMatches = folders
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
@@ -610,6 +685,27 @@ export class Folders {
return;
}
/**
* Retrieves the file stats for a given file.
* @param file - The URI of the file.
* @param folderPath - The path of the folder containing the file.
* @returns An object containing the file path, file name, folder name, folder path, and file stats.
*/
public static async getFileStats(file: Uri, folderPath: string) {
const fileName = basename(file.fsPath);
const folderName = dirname(file.fsPath).split(sep).pop();
const stats = await workspace.fs.stat(file);
return {
filePath: file.fsPath,
fileName,
folderName,
folderPath,
...stats
};
}
private static async getFilesByFolder(
folder: ContentFolder,
supportedFiles: string[] | undefined,
@@ -617,6 +713,7 @@ export class Folders {
): Promise<FolderInfo | undefined> {
try {
const folderPath = parseWinPath(folder.path);
const folderUri = Uri.file(folderPath);
if (typeof folderPath === 'string') {
let files: Uri[] = [];
@@ -635,7 +732,9 @@ export class Folders {
let foundFiles = await Folders.findFiles(filePath);
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
foundFiles = foundFiles.filter((f) =>
parseWinPath(f.fsPath).startsWith(parseWinPath(folderUri.fsPath))
);
files = [...files, ...foundFiles];
}
@@ -645,17 +744,8 @@ export class Folders {
for (const file of files) {
try {
const fileName = basename(file.fsPath);
const folderName = dirname(file.fsPath).split(sep).pop();
const stats = await workspace.fs.stat(file);
fileStats.push({
filePath: file.fsPath,
fileName,
folderName,
...stats
});
const fileInfo = await Folders.getFileStats(file, folderPath);
fileStats.push(fileInfo);
} catch (error) {
// Skip the file
}
@@ -669,6 +759,7 @@ export class Folders {
return {
title: folder.title,
path: folderPath,
files: files.length,
lastModified: fileStats,
locale: folder.locale,
@@ -688,14 +779,20 @@ export class Folders {
* @param pattern
* @returns
*/
private static findFolders(pattern: string): Promise<string[]> {
return new Promise((resolve) => {
glob(pattern, { ignore: '**/node_modules/**', dot: true }, (err, files) => {
const allFolders = files.map((file) => dirname(file));
const uniqueFolders = [...new Set(allFolders)];
resolve(uniqueFolders);
});
});
private static async findFolders(pattern: string): Promise<string[]> {
Logger.verbose(`Folders:findFolders:start - ${pattern}`);
try {
pattern = isWindows() ? parseWinPath(pattern) : pattern;
const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true });
const allFolders = (files || []).map((file) => dirname(file));
const uniqueFolders = [...new Set(allFolders)];
Logger.verbose(`Folders:findFolders:end - ${uniqueFolders.length}`);
return uniqueFolders;
} catch (e) {
Logger.error(`Folders:findFolders:error - ${(e as Error).message}`);
return [];
}
}
/**
@@ -704,11 +801,17 @@ export class Folders {
* @returns
*/
private static async findFiles(pattern: string): Promise<Uri[]> {
return new Promise((resolve) => {
glob(pattern, { ignore: '**/node_modules/**' }, (err, files) => {
const allFiles = files.map((file) => Uri.file(file));
resolve(allFiles);
});
});
Logger.verbose(`Folders:findFiles:start - ${pattern}`);
try {
pattern = isWindows() ? parseWinPath(pattern) : pattern;
const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true });
const allFiles = (files || []).map((file) => Uri.file(file));
Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`);
return allFiles;
} catch (e) {
Logger.error(`Folders:findFiles:error - ${(e as Error).message}`);
return [];
}
}
}

View File

@@ -1,6 +1,3 @@
import { processFmPlaceholders } from './../helpers/processFmPlaceholders';
import { processPathPlaceholders } from './../helpers/processPathPlaceholders';
import { Telemetry } from './../helpers/Telemetry';
import {
SETTING_PREVIEW_HOST,
SETTING_PREVIEW_PATHNAME,
@@ -9,23 +6,33 @@ import {
PreviewCommands,
SETTING_EXPERIMENTAL,
SETTING_DATE_FORMAT,
GeneralCommands
GeneralCommands,
SETTING_PREVIEW_TRAILING_SLASH
} from './../constants';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { join, parse } from 'path';
import { commands, env, Uri, ViewColumn, window, WebviewPanel, extensions } from 'vscode';
import { Extension, parseWinPath, processTimePlaceholders, Settings } from '../helpers';
import {
ArticleHelper,
Extension,
parseWinPath,
processI18nPlaceholders,
processTimePlaceholders,
processFmPlaceholders,
processPathPlaceholders,
Settings,
Telemetry,
processDateTimePlaceholders
} from '../helpers';
import { ContentFolder, ContentType, PreviewSettings } from '../models';
import { format } from 'date-fns';
import { DateHelper } from '../helpers/DateHelper';
import { Article } from '.';
import { urlJoin } from 'url-join-ts';
import { WebviewHelper } from '@estruyf/vscode';
import { Folders } from './Folders';
import { ParsedFrontMatter } from '../parsers';
import { getLocalizationFile } from '../utils/getLocalizationFile';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { joinUrl } from '../utils';
import { i18n } from './i18n';
export class Preview {
public static filePath: string | undefined = undefined;
@@ -65,7 +72,7 @@ export class Preview {
const localhostUrl = await this.getLocalServerUrl();
if (browserLiteCommand) {
const pageUrl = urlJoin(localhostUrl.toString(), slug || '');
const pageUrl = joinUrl(localhostUrl.toString(), slug || '');
commands.executeCommand(browserLiteCommand, pageUrl);
return;
}
@@ -177,7 +184,7 @@ export class Preview {
<title>Front Matter Preview</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
<div id="app" data-type="preview" data-url="${urlJoin(
<div id="app" data-type="preview" data-url="${joinUrl(
localhostUrl.toString(),
slug || ''
)}" data-isProd="${isProd}" data-environment="${
@@ -208,7 +215,7 @@ export class Preview {
webView.webview.postMessage({
command: PreviewCommands.toWebview.updateUrl,
payload: urlJoin(localhost.toString(), slug || '')
payload: joinUrl(localhost.toString(), slug || '')
});
}
}
@@ -237,11 +244,11 @@ export class Preview {
let contentType: ContentType | undefined = undefined;
if (article?.data) {
contentType = ArticleHelper.getContentType(article);
contentType = await ArticleHelper.getContentType(article);
}
// Check if there is a pathname defined on content folder level
const folders = Folders.get();
const folders = await Folders.get();
if (folders.length > 0) {
const foldersWithPath = folders.filter((folder) => folder.previewPath);
@@ -291,6 +298,11 @@ export class Preview {
slug = Article.getSlug();
}
const locale = await i18n.getLocale(filePath);
if (locale && locale.path === slug) {
slug = '';
}
if (pathname) {
// Known placeholders
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
@@ -310,10 +322,17 @@ export class Preview {
const folderPath = wsFolder ? parseWinPath(wsFolder.fsPath) : '';
const relativePath = filePath.replace(folderPath, '');
pathname = processPathPlaceholders(pathname, relativePath, filePath, selectedFolder);
pathname = processI18nPlaceholders(pathname, selectedFolder);
const file = parse(filePath);
if (file.name.toLowerCase() === 'index' && pathname.endsWith(slug)) {
slug = '';
if (file.name.toLowerCase() === 'index') {
const cleanPathName = pathname.endsWith('/')
? pathname.substring(0, pathname.length - 1)
: pathname;
if (cleanPathName.endsWith(slug) || !pathname || pathname === '/') {
slug = '';
}
}
}
@@ -321,11 +340,9 @@ export class Preview {
pathname = article?.data ? processFmPlaceholders(pathname, article?.data) : pathname;
try {
const articleDate = ArticleHelper.getDate(article);
slug = join(
format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string),
slug
);
const articleDate = await ArticleHelper.getDate(article);
pathname = processDateTimePlaceholders(pathname, dateFormat, articleDate);
slug = join(pathname, slug);
} catch (error) {
slug = join(pathname, slug);
}
@@ -339,6 +356,24 @@ export class Preview {
slug = slug.substring(0, slug.endsWith('_index') ? slug.length - 6 : slug.length - 5);
}
// Add the trailing slash
let trailingSlash = false;
if (settings.trailingSlash !== undefined) {
trailingSlash = settings.trailingSlash;
}
if (selectedFolder && selectedFolder.trailingSlash !== undefined) {
trailingSlash = selectedFolder.trailingSlash;
}
if (contentType && contentType.trailingSlash !== undefined) {
trailingSlash = contentType.trailingSlash;
}
if (trailingSlash && !slug.endsWith('/')) {
slug = `${slug}/`;
}
return slug;
}
@@ -375,10 +410,12 @@ export class Preview {
public static getSettings(): PreviewSettings {
const host = Settings.get<string>(SETTING_PREVIEW_HOST);
const pathname = Settings.get<string>(SETTING_PREVIEW_PATHNAME);
const trailingSlash = Settings.get<boolean>(SETTING_PREVIEW_TRAILING_SLASH);
return {
host,
pathname
pathname,
trailingSlash
};
}

View File

@@ -42,6 +42,25 @@ categories: []
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
// Initialize command
subscriptions.push(
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
await Project.init();
if (cb) {
cb();
}
})
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.initTemplate, () => Project.createSampleTemplate(true))
);
subscriptions.push(commands.registerCommand(COMMAND_NAME.registerFolder, Folders.register));
subscriptions.push(commands.registerCommand(COMMAND_NAME.unregisterFolder, Folders.unregister));
subscriptions.push(commands.registerCommand(COMMAND_NAME.createFolder, Folders.addMediaFolder));
subscriptions.push(commands.registerCommand(COMMAND_NAME.switchProject, Project.switchProject));
}
@@ -63,7 +82,7 @@ categories: []
await Settings.createTeamSettings();
// Add the default content type
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
if (sampleTemplate !== undefined) {
await Project.createSampleTemplate();

View File

@@ -1,14 +1,37 @@
import { TaxonomyHelper } from './../helpers/TaxonomyHelper';
import * as vscode from 'vscode';
import { TaxonomyType } from '../models';
import { EXTENSION_NAME } from '../constants';
import { ArticleHelper, FilesHelper } from '../helpers';
import { COMMAND_NAME, EXTENSION_NAME } from '../constants';
import { ArticleHelper, Extension, FilesHelper } from '../helpers';
import { FrontMatterParser } from '../parsers';
import { Notifications } from '../helpers/Notifications';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Settings {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
Settings.create(TaxonomyType.Tag);
})
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
Settings.create(TaxonomyType.Category);
})
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export)
);
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap));
}
/**
* Create a new taxonomy
*

View File

@@ -102,13 +102,13 @@ export class StatusListener {
* @param article
* @param collection
*/
private static verifyRequiredFields(
private static async verifyRequiredFields(
editor: vscode.TextEditor,
article: ParsedFrontMatter,
collection: vscode.DiagnosticCollection
) {
// Check for missing fields
const emptyFields = ContentType.findEmptyRequiredFields(article);
const emptyFields = await ContentType.findEmptyRequiredFields(article);
const fieldsToReport = [];
if (emptyFields && emptyFields.length > 0) {

View File

@@ -2,12 +2,13 @@ import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import {
COMMAND_NAME,
SETTING_CONTENT_DEFAULT_FILETYPE,
SETTING_TEMPLATES_FOLDER,
TelemetryEvent
} from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { ArticleHelper, Extension, Settings } from '../helpers';
import { Article, Folders } from '.';
import { Notifications } from '../helpers/Notifications';
import { Project } from './Project';
import { ContentType } from '../helpers/ContentType';
@@ -20,6 +21,24 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
export class Template {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate)
);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
const folderPath = Folders.getFolderPath(folder);
if (folderPath) {
Template.create(folderPath);
}
})
);
}
/**
* Generate a template
*/
@@ -176,7 +195,7 @@ export class Template {
newFilePath
);
const article = Article.updateDate(frontMatter);
const article = await Article.updateDate(frontMatter);
if (!article) {
return;

View File

@@ -48,10 +48,10 @@ export class i18n {
*
* @returns An array of I18nConfig settings.
*/
public static getAll() {
public static async getAll() {
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N) || [];
const folders = Folders.get();
const folders = await Folders.get();
if (folders) {
for (const folder of folders) {
if (folder.locales) {
@@ -77,7 +77,7 @@ export class i18n {
}
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder) {
pageFolder = await i18n.getPageFolder(filePath);
}
@@ -100,7 +100,7 @@ export class i18n {
return false;
}
const pageFolder = Folders.getPageFolderByFilePath(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder || !pageFolder.locale) {
return false;
}
@@ -119,7 +119,7 @@ export class i18n {
return false;
}
const pageFolder = Folders.getPageFolderByFilePath(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
if (!pageFolder || !pageFolder.defaultLocale) {
return false;
}
@@ -155,7 +155,7 @@ export class i18n {
return;
}
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
const fileInfo = await i18n.getFileInfo(filePath);
@@ -217,7 +217,7 @@ export class i18n {
};
} = {};
let pageFolder = Folders.getPageFolderByFilePath(filePath);
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
const fileInfo = await i18n.getFileInfo(filePath);
if (pageFolder && pageFolder.defaultLocale && pageFolder.localeSourcePath) {
@@ -272,7 +272,7 @@ export class i18n {
fileUri = Uri.file(fileUri);
}
const pageFolder = Folders.getPageFolderByFilePath(fileUri.fsPath);
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
if (!pageFolder || !pageFolder.localeSourcePath) {
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
return;
@@ -331,7 +331,7 @@ export class i18n {
return;
}
const contentType = ArticleHelper.getContentType(article);
const contentType = await ArticleHelper.getContentType(article);
if (!contentType) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
return;
@@ -482,7 +482,7 @@ export class i18n {
* @returns A promise that resolves to the ContentFolder object representing the page folder, or undefined if not found.
*/
private static async getPageFolder(filePath: string): Promise<ContentFolder | undefined> {
const folders = Folders.get();
const folders = await Folders.get();
const localeFolders = folders?.filter((folder) => folder.defaultLocale);
if (!localeFolders) {

View File

@@ -12,7 +12,7 @@ export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({
alternative,
children
}: React.PropsWithChildren<IFeatureFlagProps>) => {
if (!features || (features.length > 0 && !features.includes(flag))) {
if (!features || features.length === 0 || (features.length > 0 && !features.includes(flag))) {
if (alternative) {
return alternative;
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
export interface IArrowClockwiseIconProps {
className?: string;
}
export const ArrowClockwiseIcon: React.FunctionComponent<IArrowClockwiseIconProps> = ({ className }: React.PropsWithChildren<IArrowClockwiseIconProps>) => {
return (
<svg className={className || 'h-4 w-4'} fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M3.07 9.05a7 7 0 0 1 12.55-3.22l.13.17H12.5a.5.5 0 1 0 0 1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 0-1 0v2.2a8 8 0 1 0 1.99 4.77.5.5 0 0 0-1 .08 7 7 0 1 1-13.92-.5Z" fill="currentColor"></path>
</svg>
);
};

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
export interface IRenameIconProps {
className: string;
}
export const RenameIcon: React.FunctionComponent<IRenameIconProps> = ({
className
}: React.PropsWithChildren<IRenameIconProps>) => {
return (
<svg className={className} fill="currentColor" aria-hidden="true" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 2a.5.5 0 0 0 0 1h1v14h-1a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-1V3h1a.5.5 0 0 0 0-1h-3Zm-4 2h4v1h-4C3.67 5 3 5.67 3 6.5v7c0 .83.67 1.5 1.5 1.5h4v1h-4A2.5 2.5 0 0 1 2 13.5v-7A2.5 2.5 0 0 1 4.5 4Zm11 11h-4v1h4a2.5 2.5 0 0 0 2.5-2.5v-7A2.5 2.5 0 0 0 15.5 4h-4v1h4c.83 0 1.5.67 1.5 1.5v7c0 .83-.67 1.5-1.5 1.5Z" fill="currentColor"></path></svg>
);
};

View File

@@ -7,7 +7,7 @@ import { LabelField } from './LabelField';
export type BoolFieldProps = HTMLFieldProps<
boolean,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Bool({
@@ -37,6 +37,12 @@ function Bool({
/>
<span className="field__toggle__slider"></span>
</label>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -9,7 +9,7 @@ const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8);
export type DateFieldProps = HTMLFieldProps<
Date,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date }
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date, description?: string }
>;
function Date({
@@ -50,6 +50,12 @@ function Date({
type="datetime-local"
value={dateFormat(value) ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type LongTextFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLTextAreaElement> }
{ inputRef?: Ref<HTMLTextAreaElement>, description?: string }
>;
function LongText({
@@ -36,6 +36,12 @@ function LongText({
ref={inputRef}
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type NumFieldProps = HTMLFieldProps<
number,
HTMLDivElement,
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement> }
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Num({
@@ -47,6 +47,12 @@ function Num({
type="number"
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
export type TextFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function Text({
@@ -40,6 +40,12 @@ function Text({
type={type}
value={value ?? ''}
/>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -8,7 +8,7 @@ import { LocalizationKey } from '../../localization';
export type UnknownFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
{ inputRef?: Ref<HTMLInputElement>, description?: string }
>;
function UnknownField({
@@ -30,6 +30,12 @@ function UnknownField({
<LabelField label={label} id={id} required={props.required} />
<div className={`text-[var(--vscode-errorForeground)]`}>{l10n.t(LocalizationKey.fieldUnknown)}</div>
{
props.description && (
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
)
}
</div>
);
}

View File

@@ -6,7 +6,8 @@ export const FEATURE_FLAG = {
metadata: 'panel.metadata',
recentlyModified: 'panel.recentlyModified',
otherActions: 'panel.otherActions',
contentType: 'panel.contentType'
contentType: 'panel.contentType',
gitActions: 'panel.gitActions'
},
dashboard: {
snippets: {

View File

@@ -24,6 +24,12 @@ export const GeneralCommands = {
content: {
locales: 'getContentLocales'
},
logging: {
info: 'logInfo',
warn: 'logWarn',
error: 'logError',
verbose: 'logVerbose'
},
runCommand: 'runCommand',
getLocalization: 'getLocalization',
openOnWebsite: 'openOnWebsite'

View File

@@ -47,6 +47,8 @@ export const TelemetryEvent = {
webviewContentsView: 'webviewContentsView',
webviewSnippetsView: 'webviewSnippetsView',
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
webviewSettings: 'webviewSettings',
webviewUnknown: 'webviewUnknown',
// Git
gitSync: 'gitSync',

View File

@@ -52,6 +52,7 @@ export const SETTING_PANEL_ACTIONS_DISABLED = 'panel.actions.disabled';
export const SETTING_PREVIEW_HOST = 'preview.host';
export const SETTING_PREVIEW_PATHNAME = 'preview.pathName';
export const SETTING_PREVIEW_TRAILING_SLASH = 'preview.trailingSlash';
export const SETTING_CUSTOM_SCRIPTS = 'custom.scripts';
@@ -114,6 +115,8 @@ export const SETTING_SNIPPETS_WRAPPER = 'snippets.wrapper.enabled';
export const SETTING_WEBSITE_URL = 'website.host';
export const SETTING_LOGGING = 'logging';
/**
* Sponsors only settings
*/

View File

@@ -30,6 +30,7 @@ export enum DashboardMessage {
getPinnedItems = 'getPinnedItems',
pinItem = 'pinItem',
unpinItem = 'unpinItem',
rename = 'rename',
// Media Dashboard
getMedia = 'getMedia',
@@ -74,8 +75,8 @@ export enum DashboardMessage {
getState = 'getState',
runCustomScript = 'runCustomScript',
sendTelemetry = 'sendTelemetry',
logError = 'logError',
showNotification = 'showNotification',
setTitle = 'setTitle',
// Settings
getSettings = 'getSettings',

View File

@@ -9,8 +9,8 @@ import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { DataView } from './DataView';
import { Snippets } from './SnippetsView/Snippets';
import { FEATURE_FLAG } from '../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import { FEATURE_FLAG, GeneralCommands } from '../../constants';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { TaxonomyView } from './TaxonomyView';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { routePaths } from '..';
@@ -18,7 +18,6 @@ import { useEffect, useMemo, useState } from 'react';
import { UnknownView } from './UnknownView';
import { ErrorBoundary } from '@sentry/react';
import { ErrorView } from './ErrorView';
import { DashboardMessage } from '../DashboardMessage';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import { SettingsView } from './SettingsView/SettingsView';
@@ -55,7 +54,27 @@ export const App: React.FunctionComponent<IAppProps> = ({
return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view);
}, [mode?.features]);
const checkDevMode = (retry: number = 0) => {
if (!window.fmExternal) {
if (retry < 5) {
setTimeout(() => checkDevMode(retry + 1), 150);
} else {
setIsDevMode(false);
return;
}
}
if (window.fmExternal && window.fmExternal.isDevelopment) {
setIsDevMode(true);
}
}
useEffect(() => {
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Loaded with view ${view}`,
location: 'DASHBOARD'
});
if (view && routePaths[view]) {
navigate(routePaths[view]);
return;
@@ -65,9 +84,23 @@ export const App: React.FunctionComponent<IAppProps> = ({
}, [view]);
useEffect(() => {
if (window.fmExternal && window.fmExternal.isDevelopment) {
setIsDevMode(true);
if (settings && Object.keys(settings).length > 0) {
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Settings loaded`,
location: 'DASHBOARD'
});
}
if (pages) {
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Pages loaded - ${pages.length} pages`,
location: 'DASHBOARD'
});
}
}, [JSON.stringify(settings), JSON.stringify(pages)]);
useEffect(() => {
checkDevMode();
}, []);
if (!settings) {
@@ -87,11 +120,14 @@ export const App: React.FunctionComponent<IAppProps> = ({
fallback={<ErrorView />}
onError={(error: Error, componentStack: string, eventId: string) => {
Messenger.send(
DashboardMessage.logError,
`Event ID: ${eventId}
GeneralCommands.toVSCode.logging.error,
{
message: `Event ID: ${eventId}
Message: ${error.message}
Stack: ${componentStack}`
Stack: ${componentStack}`,
location: 'DASHBOARD'
}
);
}}
>

View File

@@ -36,7 +36,7 @@ export const DateField: React.FunctionComponent<IDateFieldProps> = ({
}
return (
<span className={`date__field ${className || ''} text-xs text-[var(--frontmatter-text)]`}>
<span className={`date__field ${className || ''} text-xs text-[var(--frontmatter-secondary-text)]`}>
{dateValue}
</span>
);

View File

@@ -5,27 +5,27 @@ import { useMemo } from 'react';
export interface IItemSelectionProps {
filePath: string;
isRowItem?: boolean;
show?: boolean;
}
export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
filePath,
isRowItem
show
}: React.PropsWithChildren<IItemSelectionProps>) => {
const { onMultiSelect, selectedFiles } = useSelectedItems();
const cssNames = useMemo(() => {
if (isRowItem) {
if (show) {
return 'block';
}
return `${selectedFiles.includes(filePath) ? 'block' : 'hidden'} absolute top-2 left-2`;
}, [isRowItem, selectedFiles]);
}, [show, selectedFiles]);
return (
<div className={`${cssNames} group-hover:block`}>
<VSCodeCheckbox
style={{
boxShadow: isRowItem ? "" : "0 0 3px var(--frontmatter-border-preserve)"
boxShadow: show ? "" : "0 0 3px var(--frontmatter-border-preserve)"
}}
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e.stopPropagation();

View File

@@ -20,7 +20,7 @@ export const Spinner: React.FunctionComponent<ISpinnerProps> = (
{
type === 'initPages' && (
<div className='spinner-msg h-full text-2xl flex justify-center items-center text-[var(--vscode-foreground)]'>
<div className='spinner-msg h-full text-2xl flex justify-center items-center text-[var(--frontmatter-text)]'>
<span>{l10n.t(LocalizationKey.loadingInitPages)}</span>
<span className='dots'></span>
</div>

View File

@@ -5,6 +5,7 @@ export interface ITextFieldProps {
name: string;
value?: string;
placeholder?: string;
description?: string;
icon?: JSX.Element;
disabled?: boolean;
autoFocus?: boolean;
@@ -18,6 +19,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
name,
value,
placeholder,
description,
icon,
autoFocus,
multiline,
@@ -27,52 +29,63 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
onReset
}: React.PropsWithChildren<ITextFieldProps>) => {
return (
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
<>
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
{
multiline ? (
<textarea
rows={rows || 3}
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
) : (
<input
type="text"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
)
}
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
{
multiline ? (
<textarea
rows={rows || 3}
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
) : (
<input
type="text"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{description}
</p>
)
}
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
</>
);
};

View File

@@ -1,23 +1,25 @@
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, EllipsisVerticalIcon, LanguageIcon } from '@heroicons/react/24/outline';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { CustomScript, I18nConfig, ScriptType } from '../../../models';
import { CustomScript, I18nConfig } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { QuickAction } from '../Menu';
import { Alert } from '../Modals/Alert';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { SelectedItemActionAtom, SettingsSelector } from '../../state';
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
import { PinIcon } from '../Icons/PinIcon';
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { RenameIcon } from '../../../components/icons/RenameIcon';
import { openOnWebsite } from '../../utils';
import { CustomActions } from './CustomActions';
import { TranslationMenu } from './TranslationMenu';
export interface IContentActionsProps {
title: string;
path: string;
relPath: string;
contentType: string;
scripts: CustomScript[] | undefined;
listView?: boolean;
locale?: I18nConfig;
@@ -32,9 +34,9 @@ export interface IContentActionsProps {
}
export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
title,
path,
relPath,
contentType,
scripts,
onOpen,
listView,
@@ -42,8 +44,8 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
translations,
locale
}: React.PropsWithChildren<IContentActionsProps>) => {
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom);
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
const settings = useRecoilValue(SettingsSelector);
const onView = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
@@ -51,30 +53,19 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
onOpen();
};
const onDelete = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
const onDelete = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
e.stopPropagation();
setShowDeletionAlert(true);
};
setSelectedItemAction({ path, action: 'delete' });
}, [path]);
const onDeleteConfirm = () => {
if (path) {
Messenger.send(DashboardMessage.deleteFile, path);
}
setShowDeletionAlert(false);
};
const onOpenFile = (filePath: string) => {
messageHandler.send(DashboardMessage.openFile, filePath);
}
const openOnWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
const onRename = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
e.stopPropagation();
if (settings?.websiteUrl && path) {
Messenger.send(GeneralCommands.toVSCode.openOnWebsite, {
websiteUrl: settings.websiteUrl,
filePath: path
});
}
messageHandler.send(DashboardMessage.rename, path);
}, [path])
const onOpenWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
e.stopPropagation();
openOnWebsite(settings?.websiteUrl, path);
}, [settings?.websiteUrl, path]);
const pinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
@@ -91,14 +82,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
})
}, [path]);
const runCustomScript = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
e.stopPropagation();
Messenger.send(DashboardMessage.runCustomScript, { script, path });
},
[path]
);
const runCommand = React.useCallback((commandId: string) => {
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
command: commandId,
@@ -110,103 +93,22 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
return pinnedItems.includes(relPath);
}, [pinnedItems, relPath]);
const customScriptActions = React.useMemo(() => {
return (scripts || [])
.filter(
(script) =>
(script.type === undefined || script.type === ScriptType.Content) &&
!script.bulk &&
!script.hidden
)
.map((script) => (
<DropdownMenuItem key={script.id || script.title} onClick={(e) => runCustomScript(e, script)}>
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{script.title}</span>
</DropdownMenuItem>
));
}, [scripts]);
const translationsMenu = React.useMemo(() => {
if (!locale || !translations || Object.keys(translations).length === 0) {
return null;
}
const crntLocale = translations[locale.locale];
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
if (otherLocales.length === 0) {
return null;
}
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem onClick={() => onOpenFile(crntLocale.path)}>
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{
otherLocales.map(([key, value]) => (
<DropdownMenuItem
key={key}
onClick={() => onOpenFile(value.path)}
>
<span>{value.locale.title || value.locale.locale}</span>
</DropdownMenuItem>
))
}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
);
}, [translations, locale, isDefaultLocale]);
return (
<>
<div
className={`${listView ? '' : 'group/card absolute top-6 right-0'
className={`${listView ? '' : 'group/card absolute top-6 right-2'
} flex flex-col space-y-4`}
>
<div
className={`flex items-center border border-transparent rounded-full ${listView ? '' : 'p-2 -mt-4'
className={`flex items-center border border-transparent rounded-full ${listView ? '' : 'p-1 -mt-3'
} group-hover/card:bg-[var(--vscode-sideBar-background)] group-hover/card:border-[var(--frontmatter-border)]`}
>
<div className={`relative flex text-left`}>
{!listView && (
<div className="hidden group-hover/card:flex">
<QuickAction title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} onClick={onView}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
{
settings?.websiteUrl && (
<QuickAction title={l10n.t(LocalizationKey.commonOpenOnWebsite)} onClick={openOnWebsite}>
<GlobeEuropeAfricaIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)
}
<QuickAction
title={l10n.t(LocalizationKey.commonDelete)}
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
)}
<DropdownMenu>
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] data-[state=open]:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
<DropdownMenuTrigger
className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] data-[state=open]:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
<span className="sr-only">{l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)}</span>
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@@ -220,9 +122,14 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={onRename}>
<RenameIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonRename)}</span>
</DropdownMenuItem>
{
settings?.websiteUrl && (
<DropdownMenuItem onClick={openOnWebsite}>
<DropdownMenuItem onClick={onOpenWebsite}>
<GlobeEuropeAfricaIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
</DropdownMenuItem>
@@ -238,9 +145,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
)
}
{translationsMenu}
<TranslationMenu
isDefaultLocale={isDefaultLocale}
locale={locale}
translations={translations} />
{customScriptActions}
<CustomActions
filePath={path}
contentType={contentType}
scripts={scripts} />
<DropdownMenuItem onClick={onDelete} className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
<TrashIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
@@ -252,17 +165,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
</div>
</div>
</div>
{showDeletionAlert && (
<Alert
title={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertTitle, title)}
description={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertDescription, title)}
okBtnText={l10n.t(LocalizationKey.commonDelete)}
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
dismiss={() => setShowDeletionAlert(false)}
trigger={onDeleteConfirm}
/>
)}
</>
);
};

View File

@@ -1,17 +1,21 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import * as l10n from '@vscode/l10n';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Page } from '../../models';
import { LoadingAtom, SettingsSelector } from '../../state';
import { LoadingAtom, SelectedItemActionAtom, SettingsSelector } from '../../state';
import { Overview } from './Overview';
import { Spinner } from '../Common/Spinner';
import { SponsorMsg } from '../Layout/SponsorMsg';
import usePages from '../../hooks/usePages';
import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { useCallback, useEffect, useState } from 'react';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
import { GeneralCommands, TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { FilesProvider } from '../../providers/FilesProvider';
import { Alert } from '../Modals/Alert';
import { LocalizationKey } from '../../../localization';
import { deletePage } from '../../utils';
export interface IContentsProps {
pages: Page[];
@@ -23,13 +27,51 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
const loading = useRecoilValue(LoadingAtom);
const settings = useRecoilValue(SettingsSelector);
const { pageItems } = usePages(pages);
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
const [page, setPage] = useState<Page | undefined>(undefined);
const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
const pageFolders = [...new Set(pageItems.map((page) => page.fmFolder))];
const onDismiss = useCallback(() => {
setShowDeletionAlert(false);
setSelectedItemAction(undefined);
}, []);
const onDeleteConfirm = useCallback(() => {
if (page) {
deletePage(page.fmFilePath);
}
setShowDeletionAlert(false);
setSelectedItemAction(undefined);
}, [page]);
useEffect(() => {
if (selectedItemAction && selectedItemAction.path && selectedItemAction.action === 'delete') {
const page = pageItems.find((p) => p.fmFilePath === selectedItemAction.path);
if (page) {
setPage(page);
setShowDeletionAlert(true);
}
setSelectedItemAction(undefined);
}
}, [pageItems, selectedItemAction]);
useEffect(() => {
messageHandler.send(GeneralCommands.toVSCode.logging.info, {
message: `Contents view loaded with ${pageItems.length} pages`,
location: 'DASHBOARD'
});
}, [JSON.stringify(pageItems)]);
useEffect(() => {
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewContentsView
});
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsContents));
}, []);
return (
@@ -46,6 +88,17 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
/>
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
{showDeletionAlert && page && (
<Alert
title={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertTitle, page.title)}
description={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertDescription, page.title)}
okBtnText={l10n.t(LocalizationKey.commonDelete)}
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
dismiss={onDismiss}
trigger={onDeleteConfirm}
/>
)}
</PageLayout>
</FilesProvider>
);

View File

@@ -0,0 +1,89 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { CommandLineIcon } from '@heroicons/react/24/outline';
import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid';
import { runCustomScript } from '../../utils';
import { CustomScript, ScriptType } from '../../../models';
import { LocalizationKey } from '../../../localization';
export interface ICustomActionsProps {
filePath: string;
contentType: string;
scripts: CustomScript[] | undefined;
showTrigger?: boolean;
}
export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
filePath,
contentType,
scripts,
showTrigger = false,
}: React.PropsWithChildren<ICustomActionsProps>) => {
const onRunCustomScript = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
e.stopPropagation();
runCustomScript(script, filePath);
},
[filePath]
);
const customScripts = React.useMemo(() => {
return (scripts || []).filter((script: CustomScript) => {
if (script.contentTypes && script.contentTypes.length > 0) {
return script.contentTypes.includes(contentType);
}
return true;
});
}, [scripts, contentType]);
const customActions = React.useMemo(() => {
if (!customScripts || customScripts.length === 0) {
return null;
}
return (
(customScripts || [])
.filter(
(script) =>
(script.type === undefined || script.type === ScriptType.Content) &&
!script.bulk &&
!script.hidden
)
.map((script) => (
<DropdownMenuItem
key={script.id || script.title}
title={script.title}
onClick={(e) => onRunCustomScript(e, script)}>
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{script.title}</span>
</DropdownMenuItem>
))
);
}, [customScripts, onRunCustomScript]);
if (!customActions || customActions.length === 0) {
return null;
}
if (showTrigger) {
return (
<DropdownMenu>
<DropdownMenuTrigger
title={l10n.t(LocalizationKey.commonOpenCustomActions)}
className='px-2 text-[var(--frontmatter-secondary-text)] hover:text-[var(--frontmatter-button-hoverBackground)] focus-visible:outline-none'>
<span className="sr-only">{l10n.t(LocalizationKey.commonOpenCustomActions)}</span>
<CommandLineIconSolid className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{customActions}
</DropdownMenuContent>
</DropdownMenu>
);
}
return <>{customActions}</>;
};

View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { QuickAction } from '../Menu';
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon } from '@heroicons/react/24/solid';
import { LocalizationKey } from '../../../localization';
import { openFile, openOnWebsite } from '../../utils';
import { useRecoilState } from 'recoil';
import { SelectedItemActionAtom } from '../../state';
import { CustomScript } from '../../../models';
import { CustomActions } from './CustomActions';
export interface IFooterActionsProps {
filePath: string;
contentType: string;
websiteUrl?: string;
scripts?: CustomScript[];
}
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
filePath,
contentType,
websiteUrl,
scripts
}: React.PropsWithChildren<IFooterActionsProps>) => {
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
return (
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] rounded-b`}>
{/* <ItemSelection filePath={filePath} show /> */}
<QuickAction
title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => openFile(filePath)}>
<span className={`sr-only`}>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
{
websiteUrl && (
<QuickAction
title={l10n.t(LocalizationKey.commonOpenOnWebsite)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => openOnWebsite(websiteUrl, filePath)}>
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
<GlobeEuropeAfricaIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)
}
<CustomActions
filePath={filePath}
contentType={contentType}
scripts={scripts}
showTrigger />
<QuickAction
title={l10n.t(LocalizationKey.commonDelete)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={() => setSelectedItemAction({ path: filePath, action: 'delete' })}>
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonDelete)}</span>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
);
};

View File

@@ -1,10 +1,8 @@
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { DashboardMessage } from '../../DashboardMessage';
import { Page } from '../../models/Page';
import { SettingsSelector, ViewSelector } from '../../state';
import { DateField } from '../Common/DateField';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardViewType } from '../../models';
import { ContentActions } from './ContentActions';
import { useMemo } from 'react';
@@ -13,11 +11,14 @@ import * as React from 'react';
import useExtensibility from '../../hooks/useExtensibility';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useNavigate } from 'react-router-dom';
import { routePaths } from '../..';
import useCard from '../../hooks/useCard';
import { I18nLabel } from './I18nLabel';
import { ItemSelection } from '../Common/ItemSelection';
import { openFile } from '../../utils';
import { FooterActions } from './FooterActions';
import useSelectedItems from '../../hooks/useSelectedItems';
import { cn } from '../../../utils/cn';
import { Tags } from './Tags';
export interface IItemProps extends Page { }
@@ -26,12 +27,12 @@ const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
export const Item: React.FunctionComponent<IItemProps> = ({
...pageData
}: React.PropsWithChildren<IItemProps>) => {
const { selectedFiles } = useSelectedItems();
const view = useRecoilValue(ViewSelector);
const settings = useRecoilValue(SettingsSelector);
const draftField = useMemo(() => settings?.draftField, [settings]);
const cardFields = useMemo(() => settings?.dashboardState?.contents?.cardFields, [settings?.dashboardState?.contents?.cardFields]);
const { escapedTitle, escapedDescription } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const navigate = useNavigate();
const { titleHtml, descriptionHtml, dateHtml, statusHtml, tagsHtml, imageHtml, footerHtml } = useExtensibility({
fmFilePath: pageData.fmFilePath,
date: pageData.date,
@@ -41,9 +42,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
pageData
});
const openFile = () => {
Messenger.send(DashboardMessage.openFile, pageData.fmFilePath);
};
const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
const onOpenFile = React.useCallback(() => {
openFile(pageData.fmFilePath);
}, [pageData.fmFilePath]);
const tags: string[] | undefined = useMemo(() => {
if (!settings?.dashboardState?.contents?.tags) {
@@ -92,9 +95,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return (
dateHtml ? (
<div className='mr-4' dangerouslySetInnerHTML={{ __html: dateHtml }} />
<div className='mr-6' dangerouslySetInnerHTML={{ __html: dateHtml }} />
) : (
cardFields?.date && pageData.date ? <DateField className={`mr-4`} value={pageData.date} format={pageData.fmDateFormat} /> : null
cardFields?.date && pageData.date ? <DateField className={`mr-6`} value={pageData.date} format={pageData.fmDateFormat} /> : null
)
)
}, [dateHtml, cardFields?.date, pageData]);
@@ -107,12 +110,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return (
<li className="relative">
<div
className={`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}
className={cn(`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`, isSelected && `border-[var(--frontmatter-border-active)]`)}
>
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={openFile}
className={`relative h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
onClick={onOpenFile}
className={`relative rounded-t h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
>
{
imageHtml ?
@@ -139,29 +142,29 @@ export const Item: React.FunctionComponent<IItemProps> = ({
<div className="relative p-4 w-full grow">
{
(statusPlaceholder || datePlaceholder) && (
<div className={`flex justify-between items-center ${hasDraftOrDate ? `mb-2` : ``}`}>
{statusPlaceholder}
{datePlaceholder}
<div className={`space-y-2 ${hasDraftOrDate ? `mb-2` : ``}`}>
<div>{statusPlaceholder}</div>
<div>{datePlaceholder}</div>
</div>
)
}
<ContentActions
title={pageData.title}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
contentType={pageData.fmContentType}
locale={pageData.fmLocale}
isDefaultLocale={pageData.fmDefaultLocale}
translations={pageData.fmTranslations}
scripts={settings?.scripts}
onOpen={openFile}
onOpen={onOpenFile}
/>
<I18nLabel page={pageData} />
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={openFile}
onClick={onOpenFile}
className={`text-left block`}>
{
titleHtml ? (
@@ -178,13 +181,13 @@ export const Item: React.FunctionComponent<IItemProps> = ({
(escapedDescription || descriptionHtml) && (
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={openFile}
onClick={onOpenFile}
className={`mt-2 text-left block`}>
{
descriptionHtml ? (
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
) : (
<p className={`text-xs text-[vara(--vscode-titleBar-activeForeground)]`}>{escapedDescription}</p>
<p className={`text-xs text-[var(--frontmatter-secondary-text)]`}>{escapedDescription}</p>
)
}
</button>
@@ -195,27 +198,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
tagsHtml ? (
<div className="mt-2" dangerouslySetInnerHTML={{ __html: tagsHtml }} />
) : (
tags && tags.length > 0 && (
<div className="mt-2">
{tags.map(
(tag, index) => tag && (
<button
key={index}
className={`inline-block mr-1 mt-1 text-xs text-[var(--vscode-textPreformat-foreground)] hover:brightness-75 hover:underline hover:underline-offset-1`}
title={l10n.t(LocalizationKey.commonFilterValue, tag)}
onClick={() => {
const tagField = settings?.dashboardState.contents.tags;
if (tagField) {
navigate(`${routePaths.contents}?taxonomy=${tagField}&value=${tag}`);
}
}}
>
#{tag}
</button>
)
)}
</div>
)
<Tags values={tags} tagField={settings?.dashboardState?.contents?.tags} />
)
}
</div>
@@ -225,6 +208,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
<div className="placeholder__card__footer p-4 w-full" dangerouslySetInnerHTML={{ __html: footerHtml }} />
)
}
<FooterActions
filePath={pageData.fmFilePath}
contentType={pageData.fmContentType}
websiteUrl={settings?.websiteUrl}
scripts={settings?.scripts} />
</div>
</li>
);
@@ -235,20 +224,20 @@ export const Item: React.FunctionComponent<IItemProps> = ({
className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`}
>
<div className="col-span-8 font-bold truncate flex items-center space-x-4">
<ItemSelection filePath={pageData.fmFilePath} isRowItem />
<ItemSelection filePath={pageData.fmFilePath} show />
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={openFile}>
onClick={onOpenFile}>
{escapedTitle}
</button>
<ContentActions
title={escapedTitle || ""}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
contentType={pageData.fmContentType}
scripts={settings?.scripts}
onOpen={openFile}
onOpen={onOpenFile}
listView
/>
</div>

View File

@@ -2,28 +2,32 @@ import * as React from 'react';
import { Page } from '../../models';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { ContentActions } from './ContentActions';
import { DashboardMessage } from '../../DashboardMessage';
import { messageHandler } from '@estruyf/vscode/dist/client';
import useCard from '../../hooks/useCard';
import { SettingsSelector } from '../../state';
import { useRecoilValue } from 'recoil';
import { ItemSelection } from '../Common/ItemSelection';
import { openFile } from '../../utils';
import useSelectedItems from '../../hooks/useSelectedItems';
import { cn } from '../../../utils/cn';
export interface IPinnedItemProps extends Page { }
export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
...pageData
}: React.PropsWithChildren<IPinnedItemProps>) => {
const { selectedFiles } = useSelectedItems();
const settings = useRecoilValue(SettingsSelector);
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const openFile = React.useCallback(() => {
messageHandler.send(DashboardMessage.openFile, pageData.fmFilePath);
const isSelected = React.useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
const onOpenFile = React.useCallback(() => {
openFile(pageData.fmFilePath);
}, [pageData.fmFilePath]);
return (
<li className='group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] relative'>
<button onClick={openFile} className='relative h-full w-1/3'>
<li className={cn(`group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] relative`, isSelected && `border-[var(--frontmatter-border-active)]`)}>
<button onClick={onOpenFile} className='relative h-full w-1/3'>
{
pageData["fmPreviewImage"] ? (
<img
@@ -44,13 +48,13 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
<ItemSelection filePath={pageData.fmFilePath} />
<button onClick={openFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
<button onClick={onOpenFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
<p className='font-bold'>{escapedTitle}</p>
<ContentActions
title={pageData.title}
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
contentType={pageData.fmContentType}
scripts={settings?.scripts}
onOpen={openFile}
/>

View File

@@ -34,7 +34,7 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
if (draftValue) {
return (
<span
className={`inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem] text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
className={`inline-block px-[3px] py-[2px] rounded font-semibold uppercase tracking-wide text-[0.7rem] text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
>
{draftValue}
</span>
@@ -51,7 +51,7 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
return (
<span
className={`draft__status
inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem]
inline-block px-[3px] py-[2px] rounded font-semibold uppercase tracking-wide text-[0.7rem]
${draftValue ?
'bg-[var(--vscode-statusBarItem-errorBackground)] text-[var(--vscode-statusBarItem-errorForeground)]' :
isFuture ?

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { routePaths } from '../..';
import { LocalizationKey } from '../../../localization';
import { useNavigate } from 'react-router-dom';
export interface ITagProps {
value?: string;
tagField?: string | null | undefined;
}
export const Tag: React.FunctionComponent<ITagProps> = ({
value,
tagField
}: React.PropsWithChildren<ITagProps>) => {
const navigate = useNavigate();
if (!value) {
return null;
}
return (
<button
className={`inline-block mr-1 mt-1 text-xs text-[var(--vscode-button-secondaryForeground)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] border border-[var(--frontmatter-border)] rounded px-1 py-0.5`}
title={l10n.t(LocalizationKey.commonFilterValue, value)}
onClick={() => {
if (tagField) {
navigate(`${routePaths.contents}?taxonomy=${tagField}&value=${value}`);
}
}}
>
#{value}
</button>
);
};

View File

@@ -0,0 +1,29 @@
import * as React from 'react';
import { Tag } from './Tag';
export interface ITagsProps {
values?: string[];
tagField?: string | null | undefined;
}
export const Tags: React.FunctionComponent<ITagsProps> = ({
values,
tagField
}: React.PropsWithChildren<ITagsProps>) => {
if (!values || values.length === 0) {
return null;
}
return (
<div className="mt-2">
{values.map(
(tag, index) => tag && (
<Tag
key={index}
value={tag}
tagField={tagField} />
)
)}
</div>
);
};

View File

@@ -0,0 +1,80 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '../../../components/shadcn/Dropdown';
import { LanguageIcon } from '@heroicons/react/24/outline';
import { openFile } from '../../utils/MessageHandlers';
import { I18nConfig } from '../../../models';
import { LocalizationKey } from '../../../localization';
export interface ITranslationMenuProps {
isDefaultLocale?: boolean;
locale?: I18nConfig;
translations?: {
[locale: string]: {
locale: I18nConfig;
path: string;
};
};
}
export const TranslationMenu: React.FunctionComponent<ITranslationMenuProps> = ({
isDefaultLocale,
locale,
translations,
}: React.PropsWithChildren<ITranslationMenuProps>) => {
const otherLocales = React.useMemo(() => {
if (!translations) {
return [];
}
return Object.entries(translations).filter(([key]) => key !== locale?.locale);
}, [translations]);
const crntLocale = React.useMemo(() => {
if (!locale?.locale || !translations || !translations[locale.locale]) {
return null;
}
return translations[locale.locale];
}, [translations, locale]);
if (!locale || !translations || Object.keys(translations).length === 0) {
return null;
}
if (otherLocales.length === 0 || !crntLocale) {
return null;
}
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{
otherLocales.map(([key, value]) => (
<DropdownMenuItem
key={key}
onClick={() => openFile(value.path)}
>
<span>{value.locale.title || value.locale.locale}</span>
</DropdownMenuItem>
))
}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
);
};

View File

@@ -17,10 +17,13 @@ import { Container } from './SortableContainer';
import { SortableItem } from './SortableItem';
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
import { DataType } from '../../../models/DataType';
import { TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
import { GeneralCommands, TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
import { NavigationItem } from '../Layout';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
import { MenuButton, MenuItem } from '../Menu';
import { Transition } from '@headlessui/react';
export interface IDataViewProps { }
@@ -125,10 +128,17 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
useEffect(() => {
Messenger.listen(messageListener);
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsData));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewDataView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Data view loaded',
location: 'DASHBOARD'
});
return () => {
Messenger.unlisten(messageListener);
};
@@ -161,38 +171,71 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
{settings?.dataFiles && settings.dataFiles.length > 0 ? (
<div className="relative w-full flex-grow mx-auto overflow-hidden">
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside
className={`flex flex-col flex-grow overflow-y-auto border-r py-6 px-4 overflow-auto border-[var(--frontmatter-border)]`}
>
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
</h2>
<nav className={`flex-1 py-4 -mx-4`}>
<div
className={`divide-y border-t border-b divide-[var(--frontmatter-border)] border-[var(--frontmatter-border)]`}
{dataFiles && dataFiles.length > 0 ? (
<div className={`relative w-full flex-grow mx-auto overflow-hidden`}>
{
!selectedData && (
<div className={`flex w-64 flex-col absolute inset-y-0`}>
<aside
className={`flex flex-col flex-grow overflow-y-auto border-r py-6 px-4 overflow-auto border-[var(--frontmatter-border)]`}
>
{dataFiles &&
dataFiles.length > 0 &&
dataFiles.map((dataFile, idx) => (
<NavigationItem
key={`${dataFile.id}-${idx}`}
isSelected={selectedData?.id === dataFile.id}
onClick={() => setSchema(dataFile)}
>
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
<span>{dataFile.title}</span>
</NavigationItem>
))}
</div>
</nav>
</aside>
</div>
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
</h2>
<section className={`pl-64 flex min-w-0 h-full`}>
<nav className={`flex-1 py-4 -mx-4`}>
<div
className={`divide-y border-t border-b divide-[var(--frontmatter-border)] border-[var(--frontmatter-border)]`}
>
{dataFiles &&
dataFiles.length > 0 &&
dataFiles.map((dataFile, idx) => (
<NavigationItem
key={`${dataFile.id}-${idx}`}
onClick={() => setSchema(dataFile)}
>
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
<span>{dataFile.title}</span>
</NavigationItem>
))}
</div>
</nav>
</aside>
</div>
)
}
<Transition
as={`div`}
show={!!selectedData}
enter="transition ease transform"
enterFrom="opacity-0"
enterTo="opacity-100">
<div className={`w-full px-4 py-2 border-b border-[var(--frontmatter-border)]`}>
{selectedData && (
<DropdownMenu>
<MenuButton
label={l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
title={selectedData.title}
/>
<DropdownMenuContent>
{dataFiles.map((dataFile) => (
<MenuItem
key={dataFile.id}
title={dataFile.title}
value={dataFile}
isCurrent={selectedData.id === dataFile.id}
onClick={() => setSchema(dataFile)}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</Transition>
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : "pl-64"}`}>
{selectedData ? (
<>
{!selectedData.singleEntry && (

View File

@@ -13,6 +13,8 @@ import { CustomScript, ScriptType } from '../../../models';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { useFilesContext } from '../../providers/FilesProvider';
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
import { RenameIcon } from '../../../components/icons/RenameIcon';
import { openFile } from '../../utils';
export interface IActionsBarProps {
view: NavigationType;
@@ -31,7 +33,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
const viewFile = React.useCallback(() => {
if (selectedFiles.length === 1) {
if (view === NavigationType.Contents) {
messageHandler.send(DashboardMessage.openFile, selectedFiles[0]);
openFile(selectedFiles[0]);
} else if (view === NavigationType.Media) {
setSelectedItemAction({ path: selectedFiles[0], action: 'view' })
}
@@ -106,7 +108,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
<DropdownMenuContent align='start'>
<DropdownMenuItem onClick={() => messageHandler.send(DashboardMessage.openFile, crntLocale.path)}>
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
</DropdownMenuItem>
@@ -116,7 +118,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
otherLocales.map(([key, value]) => (
<DropdownMenuItem
key={key}
onClick={() => messageHandler.send(DashboardMessage.openFile, value.path)}
onClick={() => openFile(value.path)}
>
<span>{value.locale.title || value.locale.locale}</span>
</DropdownMenuItem>
@@ -195,6 +197,21 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
<span>{l10n.t(LocalizationKey.commonView)}</span>
</ActionsBarItem>
{
view === NavigationType.Contents && (
<ActionsBarItem
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
onClick={() => {
messageHandler.send(DashboardMessage.rename, selectedFiles[0]);
setSelectedFiles([]);
}}
>
<RenameIcon className="w-4 h-4 mr-2" aria-hidden="true" />
<span>{l10n.t(LocalizationKey.commonRename)}</span>
</ActionsBarItem>
)
}
{
view === NavigationType.Media && (
<>
@@ -234,7 +251,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
onClick={() => setSelectedFiles([])}
>
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected)}</span>
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
</button>
)
}

View File

@@ -32,6 +32,7 @@ import { Link } from '../Common/Link';
import { SPONSOR_LINK } from '../../../constants';
import { Filters } from './Filters';
import { ActionsBar } from './ActionsBar';
import { RefreshDashboardData } from './RefreshDashboardData';
export interface IHeaderProps {
header?: React.ReactNode;
@@ -124,10 +125,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
return (
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
<div className={`overflow-x-auto mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
<div className={`px-4 overflow-x-auto mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
<Tabs onNavigate={updateView} />
<div className='flex items-center space-x-2 pr-4'>
<div className='flex items-center space-x-2'>
<ProjectSwitcher />
{
@@ -163,13 +164,15 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
{location.pathname === routePaths.contents && (
<>
<div className={`px-4 mt-2 mb-2 flex items-center justify-between`}>
<div className={`flex items-center justify-start space-x-4 flex-1`}>
<div className={`flex items-center justify-start space-x-2 flex-1`}>
<ChoiceButton
title={l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
choices={choiceOptions}
onClick={createContent}
disabled={!settings?.initialized}
/>
<RefreshDashboardData />
</div>
<Searchbox />

View File

@@ -63,7 +63,7 @@ export const Navigation: React.FunctionComponent<INavigationProps> = ({
}, [settings?.draftField?.type, tabInfo]);
return (
<nav className="flex-1 -mb-px flex space-x-6 xl:space-x-8" aria-label="Tabs">
<nav className="flex-1 -mb-px flex space-x-2 xl:space-x-4" aria-label="Tabs">
{settings?.draftField?.type === 'boolean' ? (
tabs.map((tab) => (
<NavigationItem

View File

@@ -1,5 +1,4 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { useCallback } from 'react';
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
@@ -18,6 +17,7 @@ import {
} from '../../state';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { ArrowClockwiseIcon } from '../../../components/icons/ArrowClockwiseIcon';
export interface IRefreshDashboardDataProps { }
@@ -62,11 +62,11 @@ export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardData
return (
<button
className={`mr-2 text-[var(--vscode-foreground)] hover:text-[var(--vscode-textLink-foreground)]`}
className={`mr-2 text-[var(--frontmatter-text)] hover:text-[var(--vscode-textLink-foreground)]`}
title={l10n.t(LocalizationKey.dashboardHeaderRefreshDashboardLabel)}
onClick={refresh}
>
<ArrowPathIcon className={`h-5 w-5`} />
<ArrowClockwiseIcon className={`h-5 w-5`} />
<span className="sr-only">{l10n.t(LocalizationKey.dashboardHeaderRefreshDashboardLabel)}</span>
</button>
);

View File

@@ -1,9 +1,8 @@
import { MagnifyingGlassIcon, XCircleIcon } from '@heroicons/react/24/solid';
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useDebounce } from '../../../hooks/useDebounce';
import { SearchAtom, SearchReadyAtom } from '../../state';
import { RefreshDashboardData } from './RefreshDashboardData';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { TextField } from '../Common/TextField';
@@ -58,8 +57,6 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({
onReset={reset}
/>
</div>
<RefreshDashboardData />
</div>
);
};

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { useLocation } from 'react-router-dom';
import { NavigationType } from '../../models';
import { cn } from '../../../utils/cn';
export interface ITabProps {
navigationType: NavigationType;
@@ -16,11 +17,11 @@ export const Tab: React.FunctionComponent<ITabProps> = ({
return (
<button
className={`h-full flex items-center py-2 px-4 text-sm font-medium text-center border-b-2 border-transparent hover:border-[var(--vscode-tab-activeForeground)] hover:text-[var(--vscode-tab-activeForeground)] ${location.pathname === `/${navigationType}`
className={cn(`h-full flex items-center py-2 px-1 text-sm font-medium text-center border-b-2 border-transparent hover:text-[var(--vscode-tab-activeForeground)] ${location.pathname === `/${navigationType}`
?
`text-[var(--vscode-tab-activeForeground)] border-[var(--vscode-tab-activeForeground)]` :
`text-[var(--vscode-tab-inactiveForeground)]`
}`}
`text-[var(--frontmatter-nav-active)] border-[var(--frontmatter-nav-active)]` :
`text-[var(--frontmatter-nav-inactive)]`
}`)}
type="button"
role="tab"
aria-controls="profile"

View File

@@ -1,14 +1,14 @@
import { CircleStackIcon, PhotoIcon, ScissorsIcon, TagIcon } from '@heroicons/react/24/outline';
import { PhotoIcon, ScissorsIcon, TagIcon, CircleStackIcon } from '@heroicons/react/24/solid';
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 { ModeAtom } from '../../state';
import { Tab } from './Tab';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { PageIcon } from '../../../panelWebView/components/Icons';
export interface ITabsProps {
onNavigate: (navigationType: NavigationType) => void;
@@ -19,44 +19,50 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({
}: React.PropsWithChildren<ITabsProps>) => {
const mode = useRecoilValue(ModeAtom);
const allDashboardVIews = [
FEATURE_FLAG.dashboard.snippets.view,
FEATURE_FLAG.dashboard.data.view,
FEATURE_FLAG.dashboard.taxonomy.view
];
return (
<ul
className="flex items-center justify-start h-full"
className="flex items-center justify-start h-full space-x-4"
data-tabs-toggle="#myTabContent"
role="tablist"
>
<li className="mr-2" role="presentation">
<li role="presentation">
<Tab navigationType={NavigationType.Contents} onNavigate={onNavigate}>
<MarkdownIcon className={`h-6 w-auto mr-2`} />
<PageIcon className={`h-4 w-auto mr-2`} />
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsContents)}</span>
</Tab>
</li>
<li className="mr-2" role="presentation">
<li role="presentation">
<Tab navigationType={NavigationType.Media} onNavigate={onNavigate}>
<PhotoIcon className={`h-6 w-auto mr-2`} />
<PhotoIcon className={`h-4 w-auto mr-2`} />
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsMedia)}</span>
</Tab>
</li>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.view}>
<li className="mr-2" role="presentation">
<FeatureFlag features={mode?.features || [...allDashboardVIews]} flag={FEATURE_FLAG.dashboard.snippets.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Snippets} onNavigate={onNavigate}>
<ScissorsIcon className={`h-6 w-auto mr-2`} />
<ScissorsIcon className={`h-4 w-auto mr-2`} />
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsSnippets)}</span>
</Tab>
</li>
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.data.view}>
<li className="mr-2" role="presentation">
<FeatureFlag features={mode?.features || [...allDashboardVIews]} flag={FEATURE_FLAG.dashboard.data.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Data} onNavigate={onNavigate}>
<CircleStackIcon className={`h-6 w-auto mr-2`} />
<CircleStackIcon className={`h-4 w-auto mr-2`} />
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsData)}</span>
</Tab>
</li>
</FeatureFlag>
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
<li className="mr-2" role="presentation">
<FeatureFlag features={mode?.features || [...allDashboardVIews]} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
<li role="presentation">
<Tab navigationType={NavigationType.Taxonomy} onNavigate={onNavigate}>
<TagIcon className={`h-6 w-auto mr-2`} />
<TagIcon className={`h-4 w-auto mr-2`} />
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsTaxonomies)}</span>
</Tab>
</li>

View File

@@ -36,7 +36,7 @@ export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = ({
return (
<footer
className={`w-full px-4 py-2 text-center space-x-8 flex items-center border-t ${isBacker ? 'justify-center' : 'justify-between'
} bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}
} bg-[var(--vscode-editor-background)] text-[var(--frontmatter-secondary-text)] border-[var(--frontmatter-border)]`}
>
{isBacker ? (
<span>

View File

@@ -0,0 +1,55 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { CustomScript, ScriptType } from '../../../models';
import { runCustomScript } from '../../utils';
import { CommandLineIcon } from '@heroicons/react/24/outline';
import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid';
import { LocalizationKey } from '../../../localization';
export interface ICustomActionsProps {
filePath: string;
scripts?: CustomScript[];
showTrigger?: boolean;
}
export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
filePath,
scripts,
showTrigger = false,
}: React.PropsWithChildren<ICustomActionsProps>) => {
const customActions = React.useMemo(() => {
return (scripts || [])
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
.map((script) => (
<DropdownMenuItem
key={script.title}
onClick={() => runCustomScript(script, filePath)}
>
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{script.title}</span>
</DropdownMenuItem>
));
}, [scripts]);
if (showTrigger) {
return (
<DropdownMenu>
<DropdownMenuTrigger
title={l10n.t(LocalizationKey.commonOpenCustomActions)}
className='px-2 text-[var(--frontmatter-secondary-text)] hover:text-[var(--frontmatter-button-hoverBackground)] focus-visible:outline-none'>
<span className="sr-only">{l10n.t(LocalizationKey.commonOpenCustomActions)}</span>
<CommandLineIconSolid className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{customActions}
</DropdownMenuContent>
</DropdownMenu>
);
}
return <>{customActions}</>;
};

View File

@@ -10,7 +10,7 @@ export const DetailsItem: React.FunctionComponent<IDetailsItemProps> = ({ title,
<>
<div className="py-3 flex justify-between text-sm font-medium">
<dt className={`text-[var(--vscode-editor-foreground)]`}>{title}</dt>
<dd className={`text-right text-[var(--vscode-foreground)]`}>
<dd className={`text-right text-[var(--frontmatter-text)]`}>
{details}
</dd>
</div>

View File

@@ -67,23 +67,23 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
fields?.forEach((field) => {
if (field.name === "title") {
items.push(
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
<DetailsItem key="title" title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
);
} else if (field.name === "caption") {
if (isImageFile) {
items.push(
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
<DetailsItem key="caption" title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
);
}
} else if (field.name === "alt") {
if (isImageFile) {
items.push(
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
<DetailsItem key="alt" title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
);
}
} else {
items.push(
<DetailsItem title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
<DetailsItem key={field.name} title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
);
}
});
@@ -147,7 +147,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
)}
<div className="mt-4 flex items-start justify-between">
<div>
<h2 className={`text-lg font-medium text-[var(--vscode-foreground)]`}>
<h2 className={`text-lg font-medium text-[var(--frontmatter-text)]`}>
{media.filename}
</h2>
<p className={`text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
@@ -169,7 +169,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{!showForm && (
<>
<h3 className={`text-base flex items-center text-[var(--vscode-foreground)]`}>
<h3 className={`text-base flex items-center text-[var(--frontmatter-text)]`}>
<span>{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormMetadataTitle)}</span>
<button onClick={onEdit}>
<PencilSquareIcon className="w-4 h-4 ml-2" aria-hidden="true" />
@@ -185,7 +185,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{!showForm && (
<div>
<h3 className={`text-base text-[var(--vscode-foreground)]`}>
<h3 className={`text-base text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormInformationTitle)}
</h3>
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>

View File

@@ -18,6 +18,7 @@ import { parseWinPath } from '../../../helpers/parseWinPath';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useMediaFolder from '../../hooks/useMediaFolder';
import { RefreshDashboardData } from '../Header/RefreshDashboardData';
export interface IFolderCreationProps { }
@@ -107,7 +108,7 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
}
return (
<div className="flex flex-1 justify-start">
<div className="flex flex-1 justify-start space-x-2">
{renderPostAssetsButton}
<button
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium focus:outline-none rounded text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50`}
@@ -117,6 +118,8 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
<FolderPlusIcon className={`mr-2 h-6 w-6`} />
<span className={``}>{l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}</span>
</button>
<RefreshDashboardData />
</div>
);
};

View File

@@ -37,7 +37,7 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
<div className="relative mr-4">
<FolderIcon className={`h-12 w-12`} />
{isContentFolder && (
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--vscode-foreground)]`}>
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--frontmatter-text)]`}>
C
</span>
)}

View File

@@ -0,0 +1,111 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { QuickAction } from '../Menu';
import { LocalizationKey } from '../../../localization';
import { ClipboardIcon, CodeBracketIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import { useRecoilState } from 'recoil';
import { SelectedItemActionAtom } from '../../state';
import { CustomScript, MediaInfo, Snippet, ViewData } from '../../../models';
import { copyToClipboard } from '../../utils';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { CustomActions } from './CustomActions';
export interface IFooterActionsProps {
media: MediaInfo;
snippets: Snippet[];
relPath?: string;
viewData?: ViewData | undefined
scripts?: CustomScript[];
insertIntoArticle: () => void;
insertSnippet: () => void;
onDelete: () => void;
}
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
relPath,
media,
snippets,
viewData,
scripts,
insertIntoArticle,
insertSnippet,
onDelete,
}: React.PropsWithChildren<IFooterActionsProps>) => {
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
return (
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]`}>
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => setSelectedItemAction({
path: media.fsPath,
action: 'view'
})}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
</QuickAction>
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => setSelectedItemAction({
path: media.fsPath,
action: 'edit'
})}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
</QuickAction>
{viewData?.filePath ? (
<>
<QuickAction
title={
viewData.metadataInsert && viewData.fieldName
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={insertIntoArticle}
>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
{viewData?.position && snippets.length > 0 && (
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={insertSnippet}>
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)}
</>
) : (
<>
{
relPath && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => copyToClipboard(parseWinPath(relPath) || '')}>
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)
}
</>
)}
<CustomActions
filePath={media.fsPath}
scripts={scripts}
showTrigger />
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
);
};

View File

@@ -32,6 +32,7 @@ import { getRelPath } from '../../utils';
import { Snippet } from '../../../models';
import useMediaInfo from '../../hooks/useMediaInfo';
import { ItemSelection } from '../Common/ItemSelection';
import { FooterActions } from './FooterActions';
export interface IItemProps {
media: MediaInfo;
@@ -185,13 +186,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
}
}, [media.vsPath]);
const updateMetadata = useCallback(() => {
setSelectedItemAction({
path: media.fsPath,
action: 'edit'
});
}, [media]);
const renderMediaIcon = useMemo(() => {
const path = media.fsPath;
const extension = path.split('.').pop();
@@ -265,7 +259,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return (
<>
<li className={`group relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
<li className={`group flex flex-col relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
<button
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImage ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
onClick={hasViewData ? undefined : openLightbox}
@@ -318,7 +312,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
</div>
)}
</button>
<div className={`relative py-4 pl-4 pr-12`}>
<div className={`relative py-4 pl-4 pr-12 grow`}>
<ItemMenu
media={media}
relPath={relPath}
@@ -327,50 +322,65 @@ export const Item: React.FunctionComponent<IItemProps> = ({
snippets={mediaSnippets}
scripts={settings?.scripts}
insertIntoArticle={insertIntoArticle}
insertSnippet={insertSnippet}
showUpdateMedia={updateMetadata}
showMediaDetails={() => setSelectedItemAction({ path: media.fsPath, action: 'view' })}
showUpdateMedia={() => setSelectedItemAction({
path: media.fsPath,
action: 'edit'
})}
showMediaDetails={() => setSelectedItemAction({
path: media.fsPath,
action: 'view'
})}
processSnippet={processSnippet}
onDelete={() => setShowAlert(true)} />
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--vscode-foreground)]}`}>
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--frontmatter-text)]`}>
{basename(parseWinPath(media.fsPath) || '')}
</p>
{!isImage && media.metadata.title && (
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
<b className={`mr-2`}>
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}:
</b>
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.title}</span>
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.title}</span>
</p>
)}
{media.metadata.caption && (
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
<b className={`mr-2`}>
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}:
</b>
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.caption}</span>
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.caption}</span>
</p>
)}
{!media.metadata.caption && media.metadata.alt && (
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
<b className={`mr-2`}>
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}:
</b>
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.alt}</span>
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.alt}</span>
</p>
)}
{(media?.size || media?.dimensions) && (
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
<b className={`mr-1`}>
<b className={`mr-1 text-[var(--frontmatter-text)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonSize)}:
</b>
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>
{mediaDetails}
</span>
</p>
)}
</div>
<FooterActions
media={media}
relPath={relPath}
snippets={mediaSnippets}
viewData={viewData?.data}
scripts={settings?.scripts}
insertIntoArticle={insertIntoArticle}
insertSnippet={insertSnippet}
onDelete={() => setShowAlert(true)} />
</li>
{showSnippetSelection && (

View File

@@ -1,13 +1,14 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { QuickAction } from '../Menu';
import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
import { CustomScript, MediaInfo, ScriptType, Snippet, ViewData } from '../../../models';
import { ClipboardIcon, CodeBracketIcon, EllipsisHorizontalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
import { CustomScript, MediaInfo, Snippet, ViewData } from '../../../models';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { copyToClipboard } from '../../utils';
import { CustomActions } from './CustomActions';
export interface IItemMenuProps {
media: MediaInfo;
@@ -17,7 +18,6 @@ export interface IItemMenuProps {
snippets: Snippet[];
scripts?: CustomScript[];
insertIntoArticle: () => void;
insertSnippet: () => void;
showUpdateMedia: () => void;
showMediaDetails: () => void;
processSnippet: (snippet: Snippet) => void;
@@ -32,26 +32,16 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
snippets,
scripts,
insertIntoArticle,
insertSnippet,
showUpdateMedia,
showMediaDetails,
processSnippet,
onDelete,
}: React.PropsWithChildren<IItemMenuProps>) => {
const copyToClipboard = React.useCallback(() => {
if (relPath) {
messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || '');
}
const onCopyToClipboard = React.useCallback(() => {
copyToClipboard(parseWinPath(relPath) || '');
}, [relPath]);
const runCustomScript = React.useCallback((script: CustomScript) => {
messageHandler.send(DashboardMessage.runCustomScript, {
script,
path: media.fsPath
});
}, [media]);
const revealMedia = React.useCallback(() => {
messageHandler.send(DashboardMessage.revealMedia, {
file: media.fsPath,
@@ -59,81 +49,22 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
});
}, [selectedFolder]);
const customScriptActions = React.useMemo(() => {
return (scripts || [])
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
.map((script) => (
<DropdownMenuItem
key={script.title}
onClick={() => runCustomScript(script)}
>
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{script.title}</span>
</DropdownMenuItem>
));
}, [scripts]);
return (
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
<div className={`flex items-center border border-transparent rounded-full p-1 -mr-2 -mt-1 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
<div className="relative z-10 flex text-left">
<div className="hidden group-hover/actions:flex">
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)} onClick={showMediaDetails}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
</QuickAction>
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} onClick={showUpdateMedia}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
</QuickAction>
{viewData?.filePath ? (
<>
<QuickAction
title={
viewData.metadataInsert && viewData.fieldName
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
}
onClick={insertIntoArticle}
>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
{viewData?.position && snippets.length > 0 && (
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={insertSnippet}>
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)}
</>
) : (
<>
{
relPath && (
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} onClick={copyToClipboard}>
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)
}
</>
)}
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
<DropdownMenu>
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={showMediaDetails}>
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonView)}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={showUpdateMedia}>
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
@@ -160,21 +91,19 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
</DropdownMenuItem>
))
}
{customScriptActions}
</>
) : (
<>
<DropdownMenuItem onClick={copyToClipboard}>
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
</DropdownMenuItem>
{customScriptActions}
</>
<DropdownMenuItem onClick={onCopyToClipboard}>
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
</DropdownMenuItem>
)
}
<CustomActions
filePath={media.fsPath}
scripts={scripts} />
<DropdownMenuItem onClick={revealMedia}>
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)}</span>

View File

@@ -20,7 +20,7 @@ import { DashboardMessage } from '../../DashboardMessage';
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
import { FolderItem } from './FolderItem';
import useMedia from '../../hooks/useMedia';
import { STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
import { GeneralCommands, STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { basename, extname, join } from 'path';
@@ -150,9 +150,16 @@ export const Media: React.FunctionComponent<IMediaProps> = (
);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsMedia));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewMediaView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: `Media view loaded`,
location: 'DASHBOARD'
});
}, []);
const { getRootProps, isDragActive } = useDropzone({
@@ -177,7 +184,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
)}
{isDragActive && (
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--frontmatter-text)] bg-[var(--vscode-editor-background)] opacity-75`}>
<ArrowUpTrayIcon className={`h-32`} />
<p className={`text-xl max-w-md text-center`}>
{selectedFolder

View File

@@ -11,16 +11,24 @@ export interface IMediaItemPanelProps {
}
export const MediaItemPanel: React.FunctionComponent<IMediaItemPanelProps> = ({ allMedia }: React.PropsWithChildren<IMediaItemPanelProps>) => {
const [crntPath, setCrntPath] = useState<string>('');
const [media, setMedia] = useState<MediaInfo | undefined>(undefined);
const [showForm, setShowForm] = useState(false);
const [showDetails, setShowDetails] = useState(false);
const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
const { mediaFolder, mediaSize, mediaDimensions, isImage, isVideo } = useMediaInfo(media);
const onDismiss = () => {
setShowDetails(false);
setShowForm(false);
setMedia(undefined);
};
useEffect(() => {
if (selectedItemAction && selectedItemAction.path) {
const mediaFile = allMedia.find((m) => m.fsPath === selectedItemAction.path);
setMedia(mediaFile);
setCrntPath(selectedItemAction.path);
if (selectedItemAction.action === 'edit') {
setShowForm(true);
@@ -31,8 +39,11 @@ export const MediaItemPanel: React.FunctionComponent<IMediaItemPanelProps> = ({
}
setSelectedItemAction(undefined);
} else if (crntPath) {
const mediaFile = allMedia.find((m) => m.fsPath === crntPath);
setMedia(mediaFile);
}
}, [allMedia, selectedItemAction])
}, [allMedia, selectedItemAction, crntPath]);
if (showDetails && media) {
return (
@@ -47,11 +58,7 @@ export const MediaItemPanel: React.FunctionComponent<IMediaItemPanelProps> = ({
isVideoFile={isVideo}
onEdit={() => setShowForm(true)}
onEditClose={() => setShowForm(false)}
onDismiss={() => {
setShowDetails(false);
setShowForm(false);
setMedia(undefined);
}}
onDismiss={onDismiss}
/>
);
}

View File

@@ -6,7 +6,7 @@ import { ViewDataSelector } from '../../state';
import SnippetForm, { SnippetFormHandle } from '../SnippetsView/SnippetForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { SnippetSlideOver } from './SnippetSlideOver';
import { SlideOver } from '../Modals/SlideOver';
export interface IMediaSnippetFormProps {
media: MediaInfo;
@@ -32,7 +32,7 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
};
return (
<SnippetSlideOver
<SlideOver
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.metadata.title || media.filename)}
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.metadata.title || media.filename)}
isSaveDisabled={false}
@@ -48,6 +48,6 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
selection={viewData?.data?.selection}
onInsert={onInsert}
/>
</SnippetSlideOver>
</SlideOver>
);
};

View File

@@ -18,7 +18,7 @@ export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({
type="button"
title={title}
onClick={onClick}
className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)}
className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--frontmatter-text)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)}
>
{children}
<span className="sr-only">{title}</span>

View File

@@ -5,7 +5,7 @@ import { Fragment, useRef } from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
export interface ISnippetSlideOverProps {
export interface ISlideOverProps {
title: string;
description: string;
okBtnText: string;
@@ -16,7 +16,7 @@ export interface ISnippetSlideOverProps {
trigger: () => void;
}
export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> = ({
export const SlideOver: React.FunctionComponent<ISlideOverProps> = ({
title,
description,
okBtnText,
@@ -25,7 +25,7 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
dismiss,
trigger,
children
}: React.PropsWithChildren<ISnippetSlideOverProps>) => {
}: React.PropsWithChildren<ISlideOverProps>) => {
const cancelButtonRef = useRef(null);
return (
@@ -46,11 +46,12 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
>
<div className="pointer-events-auto w-screen max-w-md">
<div className={`flex h-full flex-col overflow-y-scroll border-l pb-6 shadow-xl bg-[var(--vscode-sideBar-background)] border-[var(--frontmatter-border)]`}>
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)]">
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBarTitle-foreground)]">
<div className="flex items-start justify-between">
<Dialog.Title className={`text-lg font-medium text-[var(--vscode-editor-foreground)]`}>
<Dialog.Title className={`text-lg font-medium`}>
{title}
</Dialog.Title>
<div className="ml-3 flex h-7 items-center">
<button
type="button"
@@ -62,12 +63,14 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
</button>
</div>
</div>
<div className="mt-1">
<p className="text-sm">{description}</p>
</div>
</div>
<div className="relative flex-1 px-4 sm:px-6">
<div className="space-y-4">
<p className="text-sm">{description}</p>
<div>
{children}
</div>

View File

@@ -8,11 +8,14 @@ import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
import { ContentFolders } from '../Configuration/Common/ContentFolders';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { COMMAND_NAME } from '../../../constants';
import { COMMAND_NAME, TelemetryEvent } from '../../../constants';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react';
import { CommonSettings } from './CommonSettings';
import { IntegrationsView } from './IntegrationsView';
import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
export interface ISettingsViewProps { }
@@ -20,6 +23,14 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
const [loading, setLoading] = React.useState<boolean>(false);
const settings = useRecoilValue(SettingsSelector);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.commonSettings));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSettings
});
}, []);
return (
<PageLayout>
{

View File

@@ -0,0 +1,84 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { QuickAction } from '../Menu';
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import { useCallback } from 'react';
import { openFile } from '../../utils/MessageHandlers';
export interface IFooterActionsProps {
insertEnabled: boolean;
sourcePath?: string;
onEdit?: () => void;
onInsert: () => void;
onDelete?: () => void;
}
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
insertEnabled,
sourcePath,
onEdit,
onInsert,
onDelete,
}: React.PropsWithChildren<IFooterActionsProps>) => {
const showFile = useCallback(() => {
openFile(sourcePath);
}, [sourcePath]);
if (!onEdit && !onDelete && !sourcePath && !insertEnabled) {
return null;
}
return (
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] z-50`}>
{insertEnabled && (
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={onInsert}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.commonInsertSnippet)}</span>
</QuickAction>
)}
{
!sourcePath ? (
<>
{
onEdit && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={onEdit}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}</span>
</QuickAction>
)
}
{
onDelete && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}</span>
</QuickAction>
)
}
</>
) : (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={showFile}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}</span>
</QuickAction>
)
}
</div >
);
};

View File

@@ -1,14 +1,8 @@
import * as l10n from '@vscode/l10n';
import { Messenger } from '@estruyf/vscode/dist/client';
import {
CodeBracketIcon,
DocumentTextIcon,
EllipsisHorizontalIcon,
EyeIcon,
PencilIcon,
PhotoIcon,
PlusIcon,
TrashIcon
} from '@heroicons/react/24/outline';
} from '@heroicons/react/24/solid';
import * as React from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
@@ -18,13 +12,13 @@ import { SnippetParser } from '../../../helpers/SnippetParser';
import { Snippet, Snippets } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { QuickAction } from '../Menu';
import { Alert } from '../Modals/Alert';
import { FormDialog } from '../Modals/FormDialog';
import { NewForm } from './NewForm';
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { FooterActions } from './FooterActions';
import { ItemMenu } from './ItemMenu';
import { SlideOver } from '../Modals/SlideOver';
export interface IItemProps {
snippetKey: string;
@@ -64,10 +58,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
setMediaSnippet(false);
};
const showFile = useCallback(() => {
Messenger.send(DashboardMessage.openFile, snippet.sourcePath);
}, [snippet]);
const onOpenEdit = useCallback(() => {
setSnippetTitle(snippet.title || snippetKey);
setSnippetDescription(snippet.description);
@@ -161,95 +151,67 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return (
<>
<li className={`group relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border p-4 space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<CodeBracketIcon className={`w-64 h-64 opacity-5 text-[var(--vscode-foreground)]`} />
<li className={`group flex flex-col relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
<div className='p-4 grow space-y-2'>
<h2
className="font-bold flex items-center"
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
>
<CodeBracketIcon className="w-5 h-5 mr-2" aria-hidden={true} />
{snippet.title || snippetKey}
</h2>
<FeatureFlag
features={mode?.features || []}
flag={FEATURE_FLAG.dashboard.snippets.manage}
alternative={
<ItemMenu
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onInsert={() => setShowInsertDialog(true)} />
}
>
<ItemMenu
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onEdit={onOpenEdit}
onInsert={() => setShowInsertDialog(true)}
onDelete={() => setShowAlert(true)} />
</FeatureFlag>
<div className='inline-block mr-1 mt-1 text-xs text-[var(--vscode-button-secondaryForeground)] bg-[var(--vscode-button-secondaryBackground)] border border-[var(--frontmatter-border)] rounded px-1 py-0.5'>
{
snippet.isMediaSnippet ? l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeContent) : l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeMedia)
}
</div>
<p className={`text-xs text-[var(--frontmatter-text)]`}>{snippet.description}</p>
</div>
<h2
className="mt-2 mb-2 font-bold flex items-center"
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
>
{snippet.isMediaSnippet ? (
<PhotoIcon className="w-5 h-5 mr-1" aria-hidden={true} />
) : (
<DocumentTextIcon className="w-5 h-5 mr-1" aria-hidden={true} />
)}
{snippet.title || snippetKey}
</h2>
<FeatureFlag
features={mode?.features || []}
flag={FEATURE_FLAG.dashboard.snippets.manage}
alternative={
insertToContent ? (
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
<div className="group-hover:hidden">
<EllipsisHorizontalIcon className="w-4 h-4" />
</div>
<div className="hidden group-hover:flex">
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
onClick={() => setShowInsertDialog(true)}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
</div>
</div>
) : undefined
<FooterActions
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onInsert={() => setShowInsertDialog(true)} />
}
>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
<div className="group-hover:hidden">
<EllipsisHorizontalIcon className="w-4 h-4" />
</div>
<div className="hidden group-hover:flex">
{insertToContent && !snippet.isMediaSnippet && (
<>
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={() => setShowInsertDialog(true)}>
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</>
)}
{!snippet.sourcePath ? (
<>
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
onClick={onOpenEdit}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
onClick={() => setShowAlert(true)}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</>
) : (
<QuickAction
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
onClick={showFile}>
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
)}
</div>
</div>
</div>
<FooterActions
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
sourcePath={snippet.sourcePath}
onEdit={onOpenEdit}
onInsert={() => setShowInsertDialog(true)}
onDelete={() => setShowAlert(true)} />
</FeatureFlag>
<p className={`text-xs text-[var(--vscode-foreground)]`}>{snippet.description}</p>
</li>
{showInsertDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogTitle, snippet.title || snippetKey)}
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
description={snippet.description || l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
isSaveDisabled={!insertToContent}
trigger={insertToArticle}
dismiss={() => setShowInsertDialog(false)}
@@ -263,11 +225,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
filePath={viewData?.data?.filePath}
fieldInfo={viewData?.data?.snippetInfo?.fields}
selection={viewData?.data?.selection} />
</FormDialog>
</SlideOver>
)}
{showEditDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogTitle, snippet.title || snippetKey)}
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
isSaveDisabled={!snippetTitle || !snippetOriginalBody}
@@ -286,7 +248,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)}
/>
</FormDialog>
</SlideOver>
)}
{showAlert && (

View File

@@ -0,0 +1,97 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { useCallback } from 'react';
import { LocalizationKey } from '../../../localization';
import { openFile } from '../../utils/MessageHandlers';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid';
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
export interface IItemMenuProps {
insertEnabled: boolean;
sourcePath?: string;
onEdit?: () => void;
onInsert: () => void;
onDelete?: () => void;
}
export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
insertEnabled,
sourcePath,
onEdit,
onInsert,
onDelete,
}: React.PropsWithChildren<IItemMenuProps>) => {
const showFile = useCallback(() => {
openFile(sourcePath);
}, [sourcePath]);
if (!onEdit && !onDelete && !sourcePath && !insertEnabled) {
return null;
}
return (
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
<div className={`flex items-center border border-transparent rounded-full p-1 -mr-2 -mt-3 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
<div className="relative z-10 flex text-left">
<DropdownMenu>
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{
insertEnabled && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.commonInsertSnippet)}
onClick={onInsert}>
<PlusIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonInsertSnippet)}</span>
</DropdownMenuItem>
)
}
{
!sourcePath ? (
<>
{
onEdit && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
onClick={onEdit}>
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}</span>
</DropdownMenuItem>
)
}
{
onDelete && (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
onClick={onDelete}
className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
<TrashIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}</span>
</DropdownMenuItem>
)
}
</>
) : (
<DropdownMenuItem
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
onClick={showFile}>
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}</span>
</DropdownMenuItem>
)
}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
);
};

View File

@@ -110,7 +110,7 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({
>
{l10n.t(LocalizationKey.dashboardSnippetsViewNewFormSnippetInputIsMediaSnippetCheckboxLabel)}
</label>
<p id="isMediaSnippet-description" className={`text-[var(--vscode-foreground)] text-left`}>
<p id="isMediaSnippet-description" className={`text-[var(--frontmatter-text)] text-left`}>
{l10n.t(LocalizationKey.dashboardSnippetsViewNewFormSnippetInputIsMediaSnippetCheckboxDescription)}
</p>
<p>

View File

@@ -8,6 +8,7 @@ import { DashboardMessage } from '../../DashboardMessage';
import { SettingsAtom, ViewDataSelector } from '../../state';
import { SnippetInputField } from './SnippetInputField';
import { SNIPPET } from '../../../constants/Snippet';
import { GeneralCommands } from '../../../constants';
export interface ISnippetFormProps {
snippetKey?: string;
@@ -144,23 +145,20 @@ ${snippetBody}
const allFields: SnippetField[] = [];
const snippetFields = snippet.fields || [];
// Loop over all fields to check if they are present in the snippet
console.log('placeholders', placeholders);
console.log('snippetFields', snippetFields);
for await (const field of snippetFields) {
console.log('field', field);
const idx = placeholders.findIndex((fieldName) => fieldName === field.name);
if (idx > -1) {
try {
const value = await insertPlaceholderValues(field.default || '');
console.log('value', value);
allFields.push({
...field,
value
});
} catch (e) {
console.log('Error', (e as Error).message)
messageHandler.send(GeneralCommands.toVSCode.logging.error, {
message: `SnippetForm: ${(e as Error).message}`,
location: 'DASHBOARD'
});
}
}
}
@@ -201,7 +199,10 @@ ${snippetBody}
{field.title || field.name}
</label>
<div className="mt-1">
<SnippetInputField field={field} fieldInfo={fieldInfo} onValueChange={onTextChange} />
<SnippetInputField
field={field}
fieldInfo={fieldInfo}
onValueChange={onTextChange} />
</div>
</div>
)

View File

@@ -27,31 +27,41 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
if (field.type === 'choice') {
return (
<div className="relative">
<select
name={field.name}
value={field.value || ''}
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
onChange={(e) => onValueChange(field, e.target.value)}
>
{(field.choices || [])?.map((option: string | Choice, index: number) =>
typeof option === 'string' ? (
<option key={index} value={option}>
{option}
</option>
) : (
<option key={index} value={option.id}>
{option.title}
</option>
)
)}
</select>
<>
<div className="relative">
<select
name={field.name}
value={field.value || ''}
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
onChange={(e) => onValueChange(field, e.target.value)}
>
{(field.choices || [])?.map((option: string | Choice, index: number) =>
typeof option === 'string' ? (
<option key={index} value={option}>
{option}
</option>
) : (
<option key={index} value={option.id}>
{option.title}
</option>
)
)}
</select>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
{
field.description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{field.description}
</p>
)
}
</>
);
}
@@ -60,6 +70,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
<TextField
name={field.name}
value={field.value || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
rows={4}
multiline
@@ -71,6 +82,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
<TextField
name={field.name}
value={field.value || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
/>
);

View File

@@ -4,19 +4,20 @@ 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, WEBSITE_LINKS } from '../../../constants';
import { FEATURE_FLAG, GeneralCommands, WEBSITE_LINKS } from '../../../constants';
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { FilterInput } from '../Header/FilterInput';
import { PageLayout } from '../Layout/PageLayout';
import { FormDialog } from '../Modals/FormDialog';
import { SponsorMsg } from '../Layout/SponsorMsg';
import { Item } from './Item';
import { NewForm } from './NewForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { List } from '../Media/List';
import { SlideOver } from '../Modals/SlideOver';
export interface ISnippetsProps { }
@@ -80,9 +81,16 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
};
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsSnippets));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSnippetsView
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: `Snippets view loaded`,
location: 'DASHBOARD'
});
}, []);
return (
@@ -128,14 +136,13 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
)}
{(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`}
<List
gap={4}
>
{snippetKeys.map((snippetKey: any, index: number) => (
<Item key={index} snippetKey={snippetKey} snippet={snippets[snippetKey]} />
))}
</ul>
</List>
) : (
<div className="w-full h-full flex items-center justify-center text-white">
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
@@ -157,7 +164,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
)}
{showCreateDialog && (
<FormDialog
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsFormDialogTitle)}
description={``}
isSaveDisabled={!snippetTitle || !snippetBody}
@@ -176,7 +183,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)}
/>
</FormDialog>
</SlideOver>
)}
</div>

View File

@@ -16,6 +16,7 @@ import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { ExtensionState } from '../../../constants';
import { LinkButton } from '../Common/LinkButton';
import { openFile } from '../../utils';
export interface ITaxonomyTaggingProps {
taxonomy: string | null;
@@ -125,7 +126,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
}, [untaggedPages, pageMappings.tagged]);
const onFileView = (filePath: string) => {
Messenger.send(DashboardMessage.openFile, filePath);
openFile(filePath);
}
React.useEffect(() => {

View File

@@ -3,7 +3,7 @@ import { ChevronRightIcon, ArrowDownTrayIcon } from '@heroicons/react/24/outline
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { TelemetryEvent } from '../../../constants';
import { GeneralCommands, TelemetryEvent } from '../../../constants';
import { TaxonomyData } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { Page, PageMappings } from '../../models';
@@ -53,9 +53,16 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
}, [settings?.tags, settings?.categories, settings?.customTaxonomy]);
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsTaxonomies));
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewTaxonomyDashboard
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Taxonomy view loaded',
location: 'DASHBOARD'
});
}, []);
return (

View File

@@ -2,12 +2,24 @@ import { StopIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
export interface IUnknownViewProps { }
export const UnknownView: React.FunctionComponent<IUnknownViewProps> = (
_: React.PropsWithChildren<IUnknownViewProps>
) => {
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, "Unknown View");
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewUnknown
});
}, []);
return (
<div className={`w-full h-full flex items-center justify-center`}>
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>

View File

@@ -1,6 +1,6 @@
import { BookOpenIcon, HeartIcon, StarIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { DOCUMENTATION_LINK, GITHUB_LINK, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../../constants';
import { DOCUMENTATION_LINK, GITHUB_LINK, GeneralCommands, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
import { GitHubIcon } from '../../../panelWebView/components/Icons/GitHubIcon';
@@ -29,6 +29,13 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({
event: TelemetryEvent.webviewWelcomeScreen
});
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: 'Welcome screen loaded',
location: "DASHBOARD"
});
Messenger.send(DashboardMessage.setTitle, "Welcome to Front Matter");
const crntState: any = Messenger.getState() || {};
Messenger.setState({
...crntState,

View File

@@ -11,12 +11,15 @@ import {
SearchReadyAtom,
ModeAtom
} from '../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { NavigationType } from '../models';
import { GeneralCommands } from '../../constants';
import { useNavigate } from 'react-router-dom';
import { routePaths } from '..';
export default function useMessages() {
const navigate = useNavigate();
const [loading, setLoading] = useRecoilState(LoadingAtom);
const [pages, setPages] = useState<Page[]>([]);
const [settings, setSettings] = useRecoilState(SettingsAtom);
@@ -28,6 +31,11 @@ export default function useMessages() {
const messageListener = (event: MessageEvent<EventData<any>>) => {
const message = event.data;
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: `Message received: ${message.command}`,
location: 'DASHBOARD'
});
switch (message.command) {
case DashboardCommand.loading:
setLoading(message.payload);
@@ -36,14 +44,19 @@ export default function useMessages() {
setViewData(message.payload);
if (message.payload?.type === NavigationType.Media) {
setView(NavigationType.Media);
navigate(routePaths[NavigationType.Media]);
} else if (message.payload?.type === NavigationType.Contents) {
setView(NavigationType.Contents);
navigate(routePaths[NavigationType.Contents]);
} else if (message.payload?.type === NavigationType.Data) {
setView(NavigationType.Data);
navigate(routePaths[NavigationType.Data]);
} else if (message.payload?.type === NavigationType.Taxonomy) {
setView(NavigationType.Taxonomy);
navigate(routePaths[NavigationType.Taxonomy]);
} else if (message.payload?.type === NavigationType.Snippets) {
setView(NavigationType.Snippets);
navigate(routePaths[NavigationType.Snippets]);
}
break;
case DashboardCommand.settings:
@@ -74,7 +87,7 @@ export default function useMessages() {
return () => {
Messenger.unlisten(messageListener);
};
}, ['']);
}, []);
return {
loading,

View File

@@ -52,7 +52,8 @@ export const routePaths: { [name: string]: string } = {
};
const mutationObserver = new MutationObserver((_, __) => {
updateCssVariables();
const darkMode = document.body.classList.contains('vscode-dark');
updateCssVariables(darkMode);
});
const elm = document.querySelector('#app');
@@ -67,7 +68,7 @@ if (elm) {
const webviewUrl = elm?.getAttribute('data-webview-url');
const isCrashDisabled = elm?.getAttribute('data-is-crash-disabled');
updateCssVariables();
updateCssVariables(document.body.classList.contains('vscode-dark'));
mutationObserver.observe(document.body, { childList: false, attributes: true });
if (isProd === 'true' && isCrashDisabled === 'false') {

View File

@@ -3,7 +3,7 @@ import { atom } from 'recoil';
export const SelectedItemActionAtom = atom<
| {
path: string;
action: 'view' | 'edit';
action: 'view' | 'edit' | 'delete';
}
| undefined
>({

View File

@@ -0,0 +1,47 @@
import { messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../DashboardMessage';
import { GeneralCommands } from '../../constants';
import { CustomScript } from '../../models';
export const openFile = (filePath?: string) => {
if (!filePath) {
return;
}
messageHandler.send(DashboardMessage.openFile, filePath);
};
export const deletePage = (filePath?: string) => {
if (!filePath) {
return;
}
messageHandler.send(DashboardMessage.deleteFile, filePath);
};
export const openOnWebsite = (websiteUrl?: string, filePath?: string) => {
if (!websiteUrl) {
return;
}
messageHandler.send(GeneralCommands.toVSCode.openOnWebsite, {
websiteUrl,
filePath
});
};
export const copyToClipboard = (value: string) => {
if (!value) {
return;
}
messageHandler.send(DashboardMessage.copyToClipboard, value);
};
export const runCustomScript = (script: CustomScript, path: string) => {
if (!script) {
return;
}
messageHandler.send(DashboardMessage.runCustomScript, { script, path });
};

View File

@@ -0,0 +1,71 @@
export const darkenColor = (color: string | undefined, percentage: number) => {
if (!color) {
return color;
}
// Remove any whitespace and convert to lowercase
color = color.trim().toLowerCase();
// Check if the color is in hex format
if (color.startsWith('#')) {
// Remove the "#" symbol
color = color.slice(1);
// Convert the color to rgb format
const hexToRgb = (hex: string) => {
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return [r, g, b];
};
const [r, g, b] = hexToRgb(color);
// Calculate the darkened color
const darkenValue = Math.round(255 * (percentage / 100));
const darkenedR = Math.max(r - darkenValue, 0);
const darkenedG = Math.max(g - darkenValue, 0);
const darkenedB = Math.max(b - darkenValue, 0);
// Convert the darkened color back to hex format
const rgbToHex = (r: number, g: number, b: number) => {
const toHex = (c: number) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
};
return rgbToHex(darkenedR, darkenedG, darkenedB);
}
// Check if the color is in rgb or rgba format
if (color.startsWith('rgb')) {
// Extract the RGB values
const rgbValues = color.match(/\d+/g);
if (rgbValues) {
const [r, g, b] = rgbValues.map(Number);
// Calculate the darkened color
const darkenValue = Math.round(255 * (percentage / 100));
const darkenedR = Math.max(r - darkenValue, 0);
const darkenedG = Math.max(g - darkenValue, 0);
const darkenedB = Math.max(b - darkenValue, 0);
// Check if the color is in rgba format
if (color.startsWith('rgba')) {
// Extract the alpha value
const alphaMatch = color.match(/[\d\.]+(?=\))/);
const alpha = alphaMatch ? Number(alphaMatch[0]) : 1;
return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`;
}
return `rgb(${darkenedR}, ${darkenedG}, ${darkenedB})`;
}
}
return color;
};

View File

@@ -1,3 +1,6 @@
export * from './MessageHandlers';
export * from './darkenColor';
export * from './getRelPath';
export * from './opacityColor';
export * from './preserveColor';
export * from './updateCssVariables';

View File

@@ -0,0 +1,19 @@
export const opacityColor = (color: string | undefined, opacity: number) => {
if (color) {
if (color.startsWith('#')) {
return `${color}${Math.round(opacity * 255)
.toString(16)
.padStart(2, '0')}`;
} else if (color.startsWith('rgba')) {
const splits = color.split(',');
splits.pop();
return `${splits.join(', ')}, ${opacity})`;
} else if (color.startsWith('rgb')) {
return `${color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)}`;
} else {
return color;
}
}
return color;
};

View File

@@ -1,6 +1,6 @@
import { preserveColor } from './preserveColor';
import { darkenColor, opacityColor, preserveColor } from '.';
export const updateCssVariables = () => {
export const updateCssVariables = (isDarkTheme: boolean = true) => {
const styles = getComputedStyle(document.documentElement);
// Lightbox
@@ -51,6 +51,17 @@ export const updateCssVariables = () => {
'var(--vscode-list-activeSelectionForeground)'
);
// Navigation
const tabActiveForeground = styles.getPropertyValue('--vscode-tab-activeForeground');
document.documentElement.style.setProperty(
'--frontmatter-nav-active',
preserveColor(tabActiveForeground) || 'var(--vscode-tab-activeForeground)'
);
document.documentElement.style.setProperty(
'--frontmatter-nav-inactive',
opacityColor(tabActiveForeground, 0.6) || 'var(--vscode-tab-inactiveForeground)'
);
// Borders
const borderColor = styles.getPropertyValue('--vscode-panel-border');
document.documentElement.style.setProperty('--frontmatter-border', 'var(--vscode-panel-border)');
@@ -75,4 +86,16 @@ export const updateCssVariables = () => {
preserveColor(buttonHoverBackground) || 'var(--vscode-button-hoverBackground)'
);
}
// Darken the background of a color
const sideBarBg = styles.getPropertyValue('--vscode-sideBar-background');
document.documentElement.style.setProperty(
'--frontmatter-sideBar-background',
darkenColor(sideBarBg, 2) || 'var(--vscode-sideBar-background)'
);
document.documentElement.style.setProperty(
'--frontmatter-border-active',
darkenColor(borderColor, isDarkTheme ? -30 : 30) || 'var(--vscode-activityBar-activeBorder)'
);
};

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