mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
181 Commits
copilot-in
...
v10.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4e39b806 | ||
|
|
1fa73efe11 | ||
|
|
ddefb9f138 | ||
|
|
5e258ac218 | ||
|
|
d2b0228809 | ||
|
|
a164a849da | ||
|
|
710ef136b4 | ||
|
|
d3b7f73c66 | ||
|
|
ee5af88851 | ||
|
|
482cbc3bf6 | ||
|
|
64f1da6355 | ||
|
|
e27adececb | ||
|
|
b391aa3270 | ||
|
|
b58c02b6d0 | ||
|
|
88cad8caa2 | ||
|
|
a04d56fbde | ||
|
|
ec86b079a6 | ||
|
|
838ced0560 | ||
|
|
1c269db91d | ||
|
|
1ed5131abe | ||
|
|
d944319d53 | ||
|
|
146bbbf6a1 | ||
|
|
7bfc72469d | ||
|
|
324184964b | ||
|
|
1f6ea6ac20 | ||
|
|
5a45fdc94f | ||
|
|
b043c22437 | ||
|
|
b48e34ecb0 | ||
|
|
3bdae40ff0 | ||
|
|
94df672f4c | ||
|
|
98c5b56310 | ||
|
|
f38144b8a7 | ||
|
|
231bd89619 | ||
|
|
4907a7aaa9 | ||
|
|
2dc4865581 | ||
|
|
f1ae0d60cc | ||
|
|
2f76de2a28 | ||
|
|
d7282b18eb | ||
|
|
eb22a97198 | ||
|
|
42fe1c2887 | ||
|
|
c02275d20b | ||
|
|
147823bfd0 | ||
|
|
a7f183b6cc | ||
|
|
e10ee11f0e | ||
|
|
fca8d260d5 | ||
|
|
8cecf8d8be | ||
|
|
22ce41c3eb | ||
|
|
cb649a9a97 | ||
|
|
65d430b7cf | ||
|
|
f1f0e0ab58 | ||
|
|
5f7f847ff8 | ||
|
|
2c4dbeb1eb | ||
|
|
17164df11f | ||
|
|
228c46084d | ||
|
|
e838f18abc | ||
|
|
3d6359bc2e | ||
|
|
57b710cc61 | ||
|
|
7796d52ff9 | ||
|
|
f8f539be0d | ||
|
|
fc96c8922c | ||
|
|
c84af8493b | ||
|
|
6f288ff757 | ||
|
|
0e04e687fa | ||
|
|
cec3cbee3a | ||
|
|
c6f40194b4 | ||
|
|
6c591a90bd | ||
|
|
bece544934 | ||
|
|
c40fcba088 | ||
|
|
ea9f8a2651 | ||
|
|
b9b927c800 | ||
|
|
bc0f2e7bf7 | ||
|
|
b18f5e1e36 | ||
|
|
8b05da5a76 | ||
|
|
0062117c3b | ||
|
|
1d485adbca | ||
|
|
9f2aa34aac | ||
|
|
46a7a49e7c | ||
|
|
61ae29c37a | ||
|
|
9d51531d59 | ||
|
|
0cb7d2463b | ||
|
|
ceeb1bf9a7 | ||
|
|
c11efa56f1 | ||
|
|
fa3215fa64 | ||
|
|
305c95fa86 | ||
|
|
3b7671afc9 | ||
|
|
8660f5f680 | ||
|
|
2269994b43 | ||
|
|
bdcd901e51 | ||
|
|
6d7df4266d | ||
|
|
8c2d243777 | ||
|
|
4282ec83e5 | ||
|
|
0f3c43e0fc | ||
|
|
a377f27765 | ||
|
|
17860a18f4 | ||
|
|
73609ca346 | ||
|
|
d6dfa8c9cf | ||
|
|
1c00362b1c | ||
|
|
63ea564734 | ||
|
|
38f128e1b6 | ||
|
|
39704f3a55 | ||
|
|
2020198e90 | ||
|
|
ba1cf95ffd | ||
|
|
aea87a6168 | ||
|
|
179a71db39 | ||
|
|
8d8e3fe3cc | ||
|
|
3d8c550f60 | ||
|
|
6fd526e962 | ||
|
|
788d0241fd | ||
|
|
017a2d7597 | ||
|
|
3019ba1dff | ||
|
|
13e58d26a1 | ||
|
|
634196b056 | ||
|
|
8b95468c78 | ||
|
|
dc23aba128 | ||
|
|
a778be9737 | ||
|
|
b9508df4f8 | ||
|
|
0110b7365c | ||
|
|
6588b90e7d | ||
|
|
47dba5f510 | ||
|
|
121a84659f | ||
|
|
620966c08e | ||
|
|
06718c3577 | ||
|
|
178207fd82 | ||
|
|
657e9054f6 | ||
|
|
36a8002cea | ||
|
|
07f124dcf5 | ||
|
|
ff1d4487f4 | ||
|
|
66151083c0 | ||
|
|
83abff67ac | ||
|
|
431a83b882 | ||
|
|
d240e8fdc8 | ||
|
|
e95e9a8fc7 | ||
|
|
d8e3338abe | ||
|
|
6f6b97e6ca | ||
|
|
3f8665cadf | ||
|
|
8cc68be4da | ||
|
|
27f2b57c24 | ||
|
|
9b1be1a6c1 | ||
|
|
d0b7af5c86 | ||
|
|
f13058c59b | ||
|
|
cf28e5fc85 | ||
|
|
cf787ab0f6 | ||
|
|
c7424a6d73 | ||
|
|
2d607bdb5b | ||
|
|
823d99aff2 | ||
|
|
9005a94355 | ||
|
|
dff2fb0149 | ||
|
|
7d3653589b | ||
|
|
31460026ee | ||
|
|
1a97a11c1c | ||
|
|
430760eca8 | ||
|
|
800acde914 | ||
|
|
06ff07bec8 | ||
|
|
27a4d9bc71 | ||
|
|
45f2794631 | ||
|
|
42f6557bd6 | ||
|
|
78d42ac09b | ||
|
|
73e00a7a94 | ||
|
|
36e0ef0171 | ||
|
|
380bc804fd | ||
|
|
458aadcbef | ||
|
|
adb541805a | ||
|
|
e7ca5488de | ||
|
|
f583e0e91a | ||
|
|
00bbb3879f | ||
|
|
1d7436d051 | ||
|
|
86de4fa767 | ||
|
|
ced7e41fe6 | ||
|
|
b81e92ef9e | ||
|
|
ec3c1eec58 | ||
|
|
c173fe973c | ||
|
|
47e8caeede | ||
|
|
5b3223abb6 | ||
|
|
2cb6c89d87 | ||
|
|
4197de2b2e | ||
|
|
fe7a296cc1 | ||
|
|
b03d972d31 | ||
|
|
0e6e776f70 | ||
|
|
bd1fc32f1c | ||
|
|
38d48b9fa7 | ||
|
|
c92a5cac00 |
@@ -12,6 +12,9 @@
|
||||
"no-throw-literal": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"curly": "error",
|
||||
"class-methods-use-this": "warn"
|
||||
"class-methods-use-this": "warn",
|
||||
"no-console": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"no-extra-boolean-cast": "off"
|
||||
}
|
||||
}
|
||||
|
||||
3
.github/actions/localization/action.yml
vendored
3
.github/actions/localization/action.yml
vendored
@@ -20,7 +20,7 @@ runs:
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -42,5 +42,6 @@ runs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
include-hidden-files: true
|
||||
name: ${{ inputs.PACKAGE_NAME }}
|
||||
path: .
|
||||
6
.github/workflows/release-beta.yml
vendored
6
.github/workflows/release-beta.yml
vendored
@@ -2,7 +2,7 @@ name: BETA Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- beta
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -10,12 +10,6 @@
|
||||
"values": ["#build", "#deploy", "#skip"]
|
||||
}
|
||||
],
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeBackground": "#15c2cb",
|
||||
"titleBar.inactiveBackground": "#44ffd299",
|
||||
"titleBar.activeForeground": "#0E131F",
|
||||
"titleBar.inactiveForeground": "#0E131F99"
|
||||
},
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
|
||||
127
CHANGELOG.md
127
CHANGELOG.md
@@ -1,16 +1,133 @@
|
||||
# Change Log
|
||||
|
||||
## [10.3.0] - 2024-xx-xx
|
||||
|
||||
### ✨ New features
|
||||
## [10.8.0] - 2025-02-27 - [Release notes](https://beta.frontmatter.codes/updates/v10.8.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#819](https://github.com/estruyf/vscode-front-matter/issues/819): Added new extensibility support for media scripts
|
||||
- [#822](https://github.com/estruyf/vscode-front-matter/issues/822): Added docs to the panel & dashboard views
|
||||
- [#915](https://github.com/estruyf/vscode-front-matter/issues/915): Added a new setting `frontMatter.panel.openOnSupportedFile` which allows you to open the panel view on supported files
|
||||
- [#921](https://github.com/estruyf/vscode-front-matter/issues/921): Improve the filename sanitization
|
||||
- [#922](https://github.com/estruyf/vscode-front-matter/issues/922): Added `{{fileName}}` and `{{sluggedFileName}}` placeholders for the slug template setting
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- Fix for media folder parsing on Windows
|
||||
- Refresh button was not available on the media dashboard when having custom scripts defined
|
||||
- [#909](https://github.com/estruyf/vscode-front-matter/issues/909): Schema fix for the view modes
|
||||
- [#913](https://github.com/estruyf/vscode-front-matter/issues/913): Fix for relative media paths in page bundles
|
||||
- [#914](https://github.com/estruyf/vscode-front-matter/issues/914): Fix sanitizing of default filenames with an `_` in it
|
||||
|
||||
## [10.7.0] - 2024-12-31 - [Release notes](https://beta.frontmatter.codes/updates/v10.7.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#405](https://github.com/estruyf/vscode-front-matter/issues/405): Added new `frontMatter.content.grouping` setting which allows you to define custom "group by" options
|
||||
- [#705](https://github.com/estruyf/vscode-front-matter/issues/705): UX improvements for the panel view
|
||||
- [#887](https://github.com/estruyf/vscode-front-matter/issues/887): Added new `frontMatter.global.timezone` setting, by default it is set to `UTC` for date formatting
|
||||
- [#888](https://github.com/estruyf/vscode-front-matter/issues/888): Added the ability to prompt GitHub Copilot from a custom script/action
|
||||
- [#892](https://github.com/estruyf/vscode-front-matter/issues/892): Added media folder common actions
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#895](https://github.com/estruyf/vscode-front-matter/issues/895): Fix issue with array values in filters
|
||||
|
||||
## [10.6.0] - 2024-11-06 - [Release notes](https://beta.frontmatter.codes/updates/v10.6.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#878](https://github.com/estruyf/vscode-front-matter/issues/878): Allow the `select all` button to work on other pages when there is a selection present
|
||||
- [#882](https://github.com/estruyf/vscode-front-matter/issues/882): Dynamic evaluation of the `node` executable path
|
||||
- [#884](https://github.com/estruyf/vscode-front-matter/issues/884): Hide WYSIWYG actions when the file is in git diff mode
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#859](https://github.com/estruyf/vscode-front-matter/issues/859): Fix label in the data view dropdown field
|
||||
- [#876](https://github.com/estruyf/vscode-front-matter/issues/876): Fix snippet type on the snippet card
|
||||
- [#879](https://github.com/estruyf/vscode-front-matter/issues/879): Fix for auto updating last modified date on save
|
||||
- [#885](https://github.com/estruyf/vscode-front-matter/issues/885): Fix content relationship for none i18n content
|
||||
|
||||
## [10.5.1] - 2024-10-23
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#873](https://github.com/estruyf/vscode-front-matter/issues/873): Add retry logic to get the AI model for calling GitHub Copilot
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#872](https://github.com/estruyf/vscode-front-matter/issues/872): Check the default field value as well for the field's `when` clause
|
||||
- [#874](https://github.com/estruyf/vscode-front-matter/issues/874): Fix media snippet markup insertion to article content's
|
||||
- [#875](https://github.com/estruyf/vscode-front-matter/issues/875): Clean up the exclamation marks from the file name when creating new content
|
||||
|
||||
## [10.5.0] - 2024-10-21 - [Release notes](https://beta.frontmatter.codes/updates/v10.5.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#840](https://github.com/estruyf/vscode-front-matter/issues/840): Added the `excludePaths` option for the content folder settings
|
||||
- [#850](https://github.com/estruyf/vscode-front-matter/issues/850): Extended the i18n/language button to open or create new language files (thanks to [Dennis Zoma](https://github.com/wottpal))
|
||||
- [#851](https://github.com/estruyf/vscode-front-matter/issues/851): Added `sameContentLocale` option to `contentRelationship` field (thanks to [Dennis Zoma](https://github.com/wottpal))
|
||||
- [#866](https://github.com/estruyf/vscode-front-matter/issues/866): Support Markdown in the WYSIWYG `string` field
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#858](https://github.com/estruyf/vscode-front-matter/issues/858): Fix button styling on the data screen
|
||||
- [#860](https://github.com/estruyf/vscode-front-matter/issues/860): Fix typo on the data screen
|
||||
- [#870](https://github.com/estruyf/vscode-front-matter/issues/870): Fix data number field styling
|
||||
|
||||
## [10.4.1] - 2024-09-27
|
||||
|
||||
- [#855](https://github.com/estruyf/vscode-front-matter/issues/855): Fix in panel sections
|
||||
|
||||
## [10.4.0] - 2024-09-25 - [Release notes](https://beta.frontmatter.codes/updates/v10.4.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#844](https://github.com/estruyf/vscode-front-matter/issues/844): New `{{filePrefix.index}}` placeholder to add the index number of the file in the folder
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#833](https://github.com/estruyf/vscode-front-matter/issues/833): Added support for Asciidoc files
|
||||
- [#834](https://github.com/estruyf/vscode-front-matter/issues/834): Added the ability to create new data files for a data folder
|
||||
- [#841](https://github.com/estruyf/vscode-front-matter/issues/841): Enable placeholders for file prefixes
|
||||
- [#846](https://github.com/estruyf/vscode-front-matter/issues/846): Added GitHub Copilot action for title field
|
||||
- [#848](https://github.com/estruyf/vscode-front-matter/issues/848): Set the default GitHub Copilot model to `gpt-4o-mini`
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#842](https://github.com/estruyf/vscode-front-matter/issues/842): Allow to set the `frontMatter.taxonomy.slugTemplate` setting to an empty string
|
||||
- [#845](https://github.com/estruyf/vscode-front-matter/issues/845): Fix empty values for number fields
|
||||
- [#849](https://github.com/estruyf/vscode-front-matter/issues/849): Show fields which are not empty in the metadata panel
|
||||
- [#853](https://github.com/estruyf/vscode-front-matter/issues/853): Allow empty values in date fields
|
||||
|
||||
### 🚧 Work in progress
|
||||
|
||||
- [#837](https://github.com/estruyf/vscode-front-matter/issues/837): Replacing the VSCode Webview UI Toolkit with [vscrui](https://github.com/estruyf/vscrui) due to the deprecation of the VSCode Webview UI Toolkit library
|
||||
|
||||
## [10.3.0] - 2024-08-13 - [Release notes](https://beta.frontmatter.codes/updates/v10.3.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#823](https://github.com/estruyf/vscode-front-matter/issues/823): Integrated GitHub Copilot support for titles, descriptions, and taxonomy field suggestions
|
||||
- [#824](https://github.com/estruyf/vscode-front-matter/issues/824): Added the ability to link custom actions to fields
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#467](https://github.com/estruyf/vscode-front-matter/issues/467): New `fmContentType` metadata field to link content type (fallback to the `type` field)
|
||||
- [#819](https://github.com/estruyf/vscode-front-matter/issues/819): Added new extensibility support for media scripts
|
||||
- [#820](https://github.com/estruyf/vscode-front-matter/issues/820): Moving the website and API to different hosts
|
||||
- [#821](https://github.com/estruyf/vscode-front-matter/issues/821): Added URI handler to support command links from the documentation
|
||||
- [#822](https://github.com/estruyf/vscode-front-matter/issues/822): Added docs to the panel & dashboard views
|
||||
- [#829](https://github.com/estruyf/vscode-front-matter/issues/829): UI extensibility is now generally available
|
||||
- [#831](https://github.com/estruyf/vscode-front-matter/issues/831): Added "select all" action bar button to the content and media dashboards
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#827](https://github.com/estruyf/vscode-front-matter/issues/827): Fix for `frontmatter.json` file which gets created when already present in a sub-folder
|
||||
- [#830](https://github.com/estruyf/vscode-front-matter/issues/830): Fix for using the SEO title field setting to change the title field reference
|
||||
- [#832](https://github.com/estruyf/vscode-front-matter/issues/832): Fix for finding folders with wildcards in the path
|
||||
|
||||
## [10.2.1] - 2024-08-08
|
||||
|
||||
- [#820](https://github.com/estruyf/vscode-front-matter/issues/820): Update API links to the new API URL
|
||||
|
||||
## [10.2.0] - 2024-06-12 - [Release notes](https://beta.frontmatter.codes/updates/v10.2.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
@@ -182,7 +182,7 @@ You can open showcase issues for the following things:
|
||||
## 💚 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
|
||||
<img src="https://api.frontmatter.codes/img-sponsors" alt="Front Matter sponsors" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
@@ -203,18 +203,7 @@ You can open showcase issues for the following things:
|
||||
|
||||
## 📊 Telemetry
|
||||
|
||||
The Front Matter CMS extension collects telemetry data to help us build a better understand which features from the CMS are used. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry), or you can only disable it for the extension by configuring the `frontMatter.telemetry.disable` setting.
|
||||
|
||||
We only collect the following data:
|
||||
|
||||
- Type of event
|
||||
- Extension title (main or beta)
|
||||
- Extension version
|
||||
|
||||
No user-specific data is collected, you can check the telemetry implementation in the following files:
|
||||
|
||||
- [Telemetry class](https://github.com/estruyf/vscode-front-matter/blob/59528a3db01be8d34dc40638e6cf827090e31986/src/helpers/Telemetry.ts)
|
||||
- [Metrics API](https://github.com/FrontMatter/web-documentation-nextjs/blob/main/pages/api/metrics.ts)
|
||||
The Front Matter CMS extension only uses telemetry on application crashes. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry).
|
||||
|
||||
For crash reports in the webviews, we make use of Sentry to help us understand what went wrong. This data is only used to fix issues and improve the extension. You can find more information about the Sentry implementation in the following files:
|
||||
|
||||
|
||||
15
README.md
15
README.md
@@ -180,7 +180,7 @@ You can open showcase issues for the following things:
|
||||
## 💚 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
|
||||
<img src="https://api.frontmatter.codes/img-sponsors" alt="Front Matter sponsors" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
@@ -209,18 +209,7 @@ You can open showcase issues for the following things:
|
||||
|
||||
## 📊 Telemetry
|
||||
|
||||
The Front Matter CMS extension collects telemetry data to help us build a better understand which features from the CMS are used. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry), or you can only disable it for the extension by configuring the `frontMatter.telemetry.disable` setting.
|
||||
|
||||
We only collect the following data:
|
||||
|
||||
- Type of event
|
||||
- Extension title (main or beta)
|
||||
- Extension version
|
||||
|
||||
No user-specific data is collected, you can check the telemetry implementation in the following files:
|
||||
|
||||
- [Telemetry class](https://github.com/estruyf/vscode-front-matter/blob/59528a3db01be8d34dc40638e6cf827090e31986/src/helpers/Telemetry.ts)
|
||||
- [Metrics API](https://github.com/FrontMatter/web-documentation-nextjs/blob/main/pages/api/metrics.ts)
|
||||
The Front Matter CMS extension only uses telemetry on application crashes. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry).
|
||||
|
||||
For crash reports in the webviews, we make use of Sentry to help us understand what went wrong. This data is only used to fix issues and improve the extension. You can find more information about the Sentry implementation in the following files:
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -1,45 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 28 28" style="enable-background:new 0 0 28 28;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFE45E;}
|
||||
.st1{fill:none;stroke:#FFE45E;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st2{font-family:'MyriadPro-Bold';}
|
||||
.st3{font-size:8px;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M4.1,10.2H2.4V2.1h3.1V4H4.1v1.2h1.2V7H4.1V10.2z"/>
|
||||
<path class="st0" d="M10.7,10.2H8.9L8,7.3c0-0.1,0-0.1,0-0.2C7.9,7.1,7.9,7,7.8,6.8v0.6v2.9H6.1V2.1h1.8c0.8,0,1.3,0.2,1.8,0.6
|
||||
c0.5,0.5,0.8,1.2,0.8,2.1c0,1-0.4,1.6-1.1,2L10.7,10.2z M7.9,5.8L7.9,5.8c0.3,0,0.5-0.1,0.6-0.3S8.7,5,8.7,4.8c0-0.6-0.3-1-0.8-1
|
||||
l0,0V5.8z"/>
|
||||
<path class="st0" d="M16.1,6.2c0,1.2-0.2,2.3-0.7,3.1s-1.1,1.2-1.7,1.2s-1.2-0.3-1.6-0.9c-0.6-0.8-0.9-1.9-0.9-3.4
|
||||
s0.3-2.6,0.9-3.4C12.6,2.3,13,2,13.7,2c0.8,0,1.3,0.4,1.8,1.2C15.8,3.8,16.1,4.8,16.1,6.2z M14.3,6.2c0-1.4-0.2-2.2-0.7-2.2
|
||||
c-0.2,0-0.4,0.2-0.5,0.6c-0.1,0.4-0.2,0.9-0.2,1.6c0,0.7,0.1,1.2,0.2,1.6c0.1,0.4,0.3,0.6,0.5,0.6c0.2,0,0.4-0.2,0.5-0.6
|
||||
C14.2,7.3,14.3,6.9,14.3,6.2z"/>
|
||||
<path class="st0" d="M16.8,10.2V2.1h1.7l0.9,2.9c0.1,0.1,0.1,0.3,0.2,0.6c0.1,0.2,0.1,0.5,0.2,0.8L20,7c-0.1-0.7-0.1-1.3-0.2-1.8
|
||||
s-0.1-1-0.1-1.2V2.1h1.7v8.2h-1.6l-0.9-3c-0.1-0.3-0.2-0.6-0.3-0.9c-0.1-0.3-0.1-0.6-0.2-0.8c0,0.6,0.1,1.1,0.1,1.5
|
||||
c0,0.4,0,0.8,0,1.2v2.1h-1.7V10.2z"/>
|
||||
<path class="st0" d="M24.6,10.2h-1.7V4h-1V2.1h3.7V4h-1.1V10.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect class="st1" width="28" height="28"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M3.1,11.6H4l0.6,3c0.1,0.4,0.2,0.8,0.2,1.2C4.9,16.2,4.9,16.6,5,17c0-0.1,0-0.1,0-0.1v-0.1l0.2-0.9l0.1-0.8
|
||||
l0.1-0.5l0.6-3h0.9l0.7,7.5h-1l-0.2-2.6c0-0.1,0-0.2,0-0.3c0-0.1,0-0.2,0-0.2v-1v-0.9l0,0c0,0,0,0,0-0.1v0.2c0,0.2,0,0.3-0.1,0.5
|
||||
c-0.1,0.2,0,0.2-0.1,0.3L6,15.7V16l-0.6,3.3H4.7l-0.6-2.8c-0.1-0.4-0.2-0.8-0.2-1.1c-0.1-0.4-0.1-0.8-0.2-1.2l-0.3,5.2h-1
|
||||
L3.1,11.6z"/>
|
||||
<path class="st0" d="M9.4,11.6h0.8l1.6,7.5h-1l-0.3-1.5H9l-0.3,1.5h-1L9.4,11.6z M10.4,16.8l-0.3-1.2C10,14.8,9.8,13.9,9.7,13
|
||||
c0,0.5-0.1,0.9-0.2,1.4c-0.1,0.5-0.2,1-0.3,1.5l-0.2,1L10.4,16.8L10.4,16.8z"/>
|
||||
<path class="st0" d="M11.6,11.6h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V11.6z"/>
|
||||
<path class="st0" d="M14.9,11.6h3.3v0.9h-1.1v6.7h-1v-6.7h-1.2V11.6z"/>
|
||||
<path class="st0" d="M18.8,11.6h2.7v0.9h-1.7v2.4h1.5v0.9h-1.5v2.6h1.7v0.9h-2.7V11.6z"/>
|
||||
<path class="st0" d="M22.3,11.6h1.3c0.6,0,1,0.1,1.2,0.4c0.3,0.3,0.5,0.9,0.5,1.6c0,0.5-0.1,1-0.3,1.3c-0.2,0.3-0.4,0.5-0.8,0.6
|
||||
l1.4,3.7h-1l-1.4-3.7v3.7h-1L22.3,11.6L22.3,11.6z M23.3,14.9c0.4,0,0.7-0.1,0.8-0.3c0.2-0.2,0.2-0.5,0.2-0.9c0-0.2,0-0.4-0.1-0.6
|
||||
c-0.1-0.2-0.1-0.3-0.2-0.4c-0.1-0.1-0.2-0.2-0.3-0.2s-0.3-0.1-0.4-0.1h-0.2v2.5H23.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<text transform="matrix(1 0 0 1 5.4457 25.9479)" class="st0 st2 st3">BETA</text>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 28 28">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3, .cls-4 {
|
||||
fill: #c91980;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
font-family: Futura-MediumItalic, Futura;
|
||||
font-size: 8px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cls-2, .cls-3, .cls-4, .cls-5 {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.cls-2, .cls-4 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
font-family: Futura-CondensedExtraBold, Futura;
|
||||
font-size: 10.6px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
font-family: Futura-CondensedMedium, Futura;
|
||||
font-size: 10.1px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g class="cls-5">
|
||||
<text class="cls-4" transform="translate(2.1 18.9) scale(1 1)"><tspan x="0" y="0">MATTER</tspan></text>
|
||||
</g>
|
||||
<g class="cls-5">
|
||||
<text class="cls-2" transform="translate(1.9 26)"><tspan x="0" y="0">BETA</tspan></text>
|
||||
</g>
|
||||
<rect class="cls-1" x="2.4" width="3" height="1"/>
|
||||
<rect class="cls-1" x="6.9" width="3" height="1"/>
|
||||
<rect class="cls-1" x="11.4" width="3" height="1"/>
|
||||
<rect class="cls-1" x="2.4" y="27" width="3" height="1"/>
|
||||
<rect class="cls-1" x="6.9" y="27" width="3" height="1"/>
|
||||
<rect class="cls-1" x="11.4" y="27" width="3" height="1"/>
|
||||
<g class="cls-5">
|
||||
<text class="cls-3" transform="translate(2.1 10.1) scale(.8 1)"><tspan x="0" y="0">FRONT</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
.frontmatter h3 {
|
||||
margin-bottom: 1rem;
|
||||
/* margin-bottom: 1rem; */
|
||||
}
|
||||
|
||||
.frontmatter p,
|
||||
@@ -99,11 +99,6 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.seo__status__details,
|
||||
.seo__status__keywords {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.collapsible__body h4 {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
@@ -131,7 +126,8 @@
|
||||
}
|
||||
|
||||
.article__tags__dropbox.open {
|
||||
border: 1px solid rgba(0, 0, 0, 0.9);
|
||||
border: 1px solid var(--vscode-focusBorder);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.article__tags ul {
|
||||
@@ -224,6 +220,7 @@
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.ext_link_block button.active {
|
||||
|
||||
@@ -258,9 +258,6 @@
|
||||
"panel.fields.textField.limit": "Feldgrenze erreicht {0}",
|
||||
"panel.fields.wrapperField.unknown": "Unbekannter Feldtyp: {0}",
|
||||
"panel.actions.title": "Aktionen",
|
||||
"panel.articleDetails.title": "Weitere Details",
|
||||
"panel.articleDetails.type": "Typ",
|
||||
"panel.articleDetails.total": "Gesamt",
|
||||
"panel.articleDetails.headings": "Überschriften",
|
||||
"panel.articleDetails.paragraphs": "Absätze",
|
||||
"panel.articleDetails.internalLinks": "Interne Links",
|
||||
@@ -299,16 +296,13 @@
|
||||
"panel.publishAction.publish": "Veröffentlichen",
|
||||
"panel.publishAction.unpublish": "Zurück zu Entwurf",
|
||||
"panel.seoDetails.recommended": "Empfohlen",
|
||||
"panel.seoKeywordInfo.density": "Stichwortdichte {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Verwendet in Überschrift(en)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Inhalt",
|
||||
"panel.seoKeywords.title": "Stichwörter",
|
||||
"panel.seoKeywords.header.keyword": "Stichwort",
|
||||
"panel.seoKeywords.header.details": "Details",
|
||||
"panel.seoKeywords.density": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.",
|
||||
"panel.seoStatus.title": "Empfehlungen",
|
||||
"panel.seoKeywords.density.description": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.",
|
||||
"panel.seoStatus.header.property": "Eigenschaft",
|
||||
"panel.seoStatus.header.length": "Länge",
|
||||
"panel.seoStatus.header.valid": "Gültig",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} Zeichen",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} Wörter",
|
||||
|
||||
@@ -263,9 +263,6 @@
|
||||
"panel.fields.textField.limit": "Limite de champ atteinte {0}",
|
||||
"panel.fields.wrapperField.unknown": "Type de champ inconnu : {0}",
|
||||
"panel.actions.title": "Actions",
|
||||
"panel.articleDetails.title": "Plus de détails",
|
||||
"panel.articleDetails.type": "Type",
|
||||
"panel.articleDetails.total": "Total",
|
||||
"panel.articleDetails.headings": "En-têtes",
|
||||
"panel.articleDetails.paragraphs": "Paragraphes",
|
||||
"panel.articleDetails.internalLinks": "Liens internes",
|
||||
@@ -304,16 +301,13 @@
|
||||
"panel.publishAction.publish": "Publié",
|
||||
"panel.publishAction.unpublish": "Retourner au brouillon",
|
||||
"panel.seoDetails.recommended": "Recommandé",
|
||||
"panel.seoKeywordInfo.density": "Utilisation du mot clé {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Utilisé dans le ou les en-tête(s)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Contenu",
|
||||
"panel.seoKeywords.title": "Mot-clés",
|
||||
"panel.seoKeywords.header.keyword": "Mot-clé",
|
||||
"panel.seoKeywords.header.details": "Détails",
|
||||
"panel.seoKeywords.density": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas",
|
||||
"panel.seoStatus.title": "Recommandations",
|
||||
"panel.seoKeywords.density.description": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas",
|
||||
"panel.seoStatus.header.property": "Propriété",
|
||||
"panel.seoStatus.header.length": "Longueur",
|
||||
"panel.seoStatus.header.valid": "Valide",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} caractères",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} mots",
|
||||
|
||||
@@ -263,9 +263,6 @@
|
||||
"panel.fields.textField.limit": "Limite di campi raggiunto {0}",
|
||||
"panel.fields.wrapperField.unknown": "Tipo di campo sconosciuto: {0}",
|
||||
"panel.actions.title": "Azioni",
|
||||
"panel.articleDetails.title": "Più dettagli",
|
||||
"panel.articleDetails.type": "Digitare",
|
||||
"panel.articleDetails.total": "Totale",
|
||||
"panel.articleDetails.headings": "Intestazioni",
|
||||
"panel.articleDetails.paragraphs": "Paragrafi",
|
||||
"panel.articleDetails.internalLinks": "Collegamenti esterni",
|
||||
@@ -304,16 +301,13 @@
|
||||
"panel.publishAction.publish": "Pubblica",
|
||||
"panel.publishAction.unpublish": "Tornare alla bozza",
|
||||
"panel.seoDetails.recommended": "Raccomandato",
|
||||
"panel.seoKeywordInfo.density": "Utilizzo delle parole chiave {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Utilizzato nelle rubriche",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Contenuto",
|
||||
"panel.seoKeywords.title": "Parole chiavi",
|
||||
"panel.seoKeywords.header.keyword": "Parola chiave",
|
||||
"panel.seoKeywords.header.details": "Dettagli",
|
||||
"panel.seoKeywords.density": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.",
|
||||
"panel.seoStatus.title": "Consigli",
|
||||
"panel.seoKeywords.density.description": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.",
|
||||
"panel.seoStatus.header.property": "Proprietà",
|
||||
"panel.seoStatus.header.length": "Lunghezza",
|
||||
"panel.seoStatus.header.valid": "Valido",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} caratteri",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} parole",
|
||||
|
||||
@@ -438,9 +438,6 @@
|
||||
|
||||
"panel.actions.title": "コマンド",
|
||||
|
||||
"panel.articleDetails.title": "詳細",
|
||||
"panel.articleDetails.type": "項目",
|
||||
"panel.articleDetails.total": "数",
|
||||
"panel.articleDetails.headings": "見出し",
|
||||
"panel.articleDetails.paragraphs": "パラグラフ",
|
||||
"panel.articleDetails.internalLinks": "内部リンク",
|
||||
@@ -489,18 +486,15 @@
|
||||
|
||||
"panel.seoDetails.recommended": "推奨",
|
||||
|
||||
"panel.seoKeywordInfo.density": "キーワード出現率 {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "見出しへの利用",
|
||||
"panel.seoKeywordInfo.validInfo.content": "本文",
|
||||
|
||||
"panel.seoKeywords.title": "キーワード",
|
||||
"panel.seoKeywords.header.keyword": "キーワード",
|
||||
"panel.seoKeywords.header.details": "詳細",
|
||||
"panel.seoKeywords.density": "* キーワード出現率は通常1~1.5%で十分です。",
|
||||
"panel.seoKeywords.density.description": "* キーワード出現率は通常1~1.5%で十分です。",
|
||||
|
||||
"panel.seoStatus.title": "推奨項目",
|
||||
"panel.seoStatus.header.property": "項目",
|
||||
"panel.seoStatus.header.length": "長さ",
|
||||
"panel.seoStatus.header.valid": "有効",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} 文字",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} 語",
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
"settings.view.integration": "Integration",
|
||||
|
||||
"settings.openOnStartup": "Open dashboard on startup",
|
||||
"settings.openPanelForSupportedFiles": "Open panel for supported files",
|
||||
"settings.openPanelForSupportedFiles.label": "Do you want to open the panel for supported files?",
|
||||
"settings.contentTypes": "Content types",
|
||||
"settings.contentFolders": "Content folders",
|
||||
"settings.diagnostic": "Diagnostic",
|
||||
@@ -140,8 +142,12 @@
|
||||
"dashboard.dataView.dataView.noDataFiles": "No data files found",
|
||||
"dashboard.dataView.dataView.getStarted.link": "Read more to get started using data files",
|
||||
"dashboard.dataView.dataView.update.message": "Updated your data entries",
|
||||
"dashboard.dataView.dataView.createNew": "Create new data file",
|
||||
"dashboard.dataView.dataView.selectDataFolder": "Select data folder",
|
||||
"dashboard.dataView.dataView.closeSelectedDataFile": "Close data file",
|
||||
|
||||
"dashboard.dataView.emptyView.heading": "Select your date type first",
|
||||
"dashboard.dataView.emptyView.heading": "Select your data type first",
|
||||
"dashboard.dataView.emptyView.heading.create": "Start by creating a new data file",
|
||||
|
||||
"dashboard.dataView.sortableItem.editButton.title": "Edit \"{0}\"",
|
||||
"dashboard.dataView.sortableItem.deleteButton.title": "Delete \"{0}\"",
|
||||
@@ -154,6 +160,7 @@
|
||||
"dashboard.filters.languageFilter.all": "All",
|
||||
|
||||
"dashboard.header.actionsBar.itemsSelected": "{0} selected",
|
||||
"dashboard.header.actionsBar.selectAll": "Select all",
|
||||
"dashboard.header.actionsBar.alertDelete.title": "Delete selected files",
|
||||
"dashboard.header.actionsBar.alertDelete.description": "Are you sure you want to delete the selected files?",
|
||||
|
||||
@@ -182,7 +189,7 @@
|
||||
|
||||
"dashboard.header.pagination.first": "First",
|
||||
"dashboard.header.pagination.previous": "Previous",
|
||||
"dashboard.header.pagination.next": "next",
|
||||
"dashboard.header.pagination.next": "Next",
|
||||
"dashboard.header.pagination.last": "Last",
|
||||
|
||||
"dashboard.header.paginationStatus.text": "Showing {0} to {1} of {2} results",
|
||||
@@ -242,6 +249,7 @@
|
||||
|
||||
"dashboard.media.folderItem.contentDirectory": "Content directory",
|
||||
"dashboard.media.folderItem.publicDirectory": "Public directory",
|
||||
"dashboard.media.folderItem.deleteDescription": "Are you sure you want to delete the folder ({0})?",
|
||||
|
||||
"dashboard.media.item.buttom.insert.image": "Insert image",
|
||||
"dashboard.media.item.buttom.insert.snippet": "Insert snippet",
|
||||
@@ -438,11 +446,11 @@
|
||||
|
||||
"panel.fields.wrapperField.unknown": "Unkown field type: {0}",
|
||||
|
||||
"panel.fields.fieldCustomAction.button.title": "Custom action",
|
||||
"panel.fields.fieldCustomAction.executing": "Executing field action...",
|
||||
|
||||
"panel.actions.title": "Actions",
|
||||
|
||||
"panel.articleDetails.title": "More details",
|
||||
"panel.articleDetails.type": "Type",
|
||||
"panel.articleDetails.total": "Total",
|
||||
"panel.articleDetails.headings": "Headings",
|
||||
"panel.articleDetails.paragraphs": "Paragraphs",
|
||||
"panel.articleDetails.internalLinks": "Internal links",
|
||||
@@ -491,18 +499,20 @@
|
||||
|
||||
"panel.seoDetails.recommended": "Recommended",
|
||||
|
||||
"panel.seoKeywordInfo.density": "Keyword usage {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Used in heading(s)",
|
||||
"panel.seoKeywords.checks": "Checks",
|
||||
"panel.seoKeywords.density.tableTitle": "Frequency",
|
||||
"panel.seoKeywords.density": "Keyword density",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Heading(s)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Content",
|
||||
"panel.seoKeywordInfo.density.tooltip": "Recommended frequency: 0.75% - 1.5%",
|
||||
|
||||
"panel.seoKeywords.title": "Keywords",
|
||||
"panel.seoKeywords.header.keyword": "Keyword",
|
||||
"panel.seoKeywords.header.details": "Details",
|
||||
"panel.seoKeywords.density": "* A keyword density of 1-1.5% is sufficient in most cases.",
|
||||
"panel.seoKeywords.density.description": "* A keyword density of 1-1.5% is sufficient in most cases.",
|
||||
|
||||
"panel.seoStatus.title": "Recommendations",
|
||||
"panel.seoStatus.title": "Insights",
|
||||
"panel.seoStatus.header.property": "Property",
|
||||
"panel.seoStatus.header.length": "Length",
|
||||
"panel.seoStatus.header.valid": "Valid",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} chars",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} words",
|
||||
@@ -575,6 +585,11 @@
|
||||
"commands.i18n.create.success.created": "Created \"{0}\" i18n content file.",
|
||||
"commands.i18n.create.quickPick.title": "Create content for locale",
|
||||
"commands.i18n.create.quickPick.placeHolder": "To which locale do you want to create a new content?",
|
||||
"commands.i18n.createOrOpen.quickPick.title": "Open or create translation",
|
||||
"commands.i18n.createOrOpen.quickPick.category.existing": "Existing translations",
|
||||
"commands.i18n.createOrOpen.quickPick.action.open": "Open \"{0}\"",
|
||||
"commands.i18n.createOrOpen.quickPick.category.new": "New translations",
|
||||
"commands.i18n.createOrOpen.quickPick.action.create": "Create \"{0}\"",
|
||||
"commands.i18n.translate.progress.title": "Translating content...",
|
||||
|
||||
"commands.preview.panel.title": "Preview: {0}",
|
||||
@@ -763,6 +778,9 @@
|
||||
"listeners.dashboard.dashboardListener.pinItem.coundNotPin.error": "Could not pin item.",
|
||||
"listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error": "Could not unpin item.",
|
||||
|
||||
"listeners.dashboard.mediaListeners.deleteMediaFolder.progress.title": "Deleting folder...",
|
||||
"listeners.dashboard.mediaListeners.updateMediaFolder.progress.title": "Updating folder...",
|
||||
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.notification": "Template files copied.",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.progress.title": "Downloading and initializing the template...",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.download.error": "Failed to download the template.",
|
||||
@@ -779,7 +797,9 @@
|
||||
"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.dataListener.createDataFile.inputTitle": "What is the name of the data file?",
|
||||
"listeners.panel.dataListener.createDataFile.error": "No data file id or path defined.",
|
||||
"listeners.panel.dataListener.createDataFile.noFileName": "No filename provided.",
|
||||
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error": "No article data",
|
||||
@@ -798,4 +818,4 @@
|
||||
"services.sponsorAi.getTaxonomySuggestions.warning": "The AI taxonomy generation took too long. Please try again later.",
|
||||
|
||||
"services.terminal.openLocalServerTerminal.terminalOption.message": "Starting local server"
|
||||
}
|
||||
}
|
||||
|
||||
6278
package-lock.json
generated
6278
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
201
package.json
201
package.json
@@ -3,7 +3,7 @@
|
||||
"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.3.0",
|
||||
"version": "10.8.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -32,7 +32,7 @@
|
||||
"l10n": "./l10n",
|
||||
"categories": [
|
||||
"AI",
|
||||
"Other"
|
||||
"Visualization"
|
||||
],
|
||||
"keywords": [
|
||||
"Front Matter",
|
||||
@@ -291,6 +291,14 @@
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description%"
|
||||
},
|
||||
"excludePaths": {
|
||||
"type": "array",
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludePaths.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"previewPath": {
|
||||
"type": [
|
||||
"null",
|
||||
@@ -476,6 +484,34 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"frontMatter.content.grouping": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "%setting.frontMatter.content.grouping.markdownDescription%",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.grouping.items.properties.id.description%"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.grouping.items.properties.title.description%"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.grouping.items.properties.name.description%"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.sorting": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
@@ -577,6 +613,7 @@
|
||||
"default": [],
|
||||
"markdownDescription": "%setting.frontMatter.custom.scripts.markdownDescription%",
|
||||
"items": {
|
||||
"$id": "#customscript",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -869,6 +906,20 @@
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.singleEntry.description%",
|
||||
"default": false
|
||||
},
|
||||
"enableFileCreation": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.enableFileCreation.description%",
|
||||
"default": false
|
||||
},
|
||||
"fileType": {
|
||||
"type": "string",
|
||||
"default": "json",
|
||||
"enum": [
|
||||
"json",
|
||||
"yaml"
|
||||
],
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.fileType.description%"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -1010,8 +1061,9 @@
|
||||
"panel.globalSettings",
|
||||
"panel.seo",
|
||||
"panel.actions",
|
||||
"panel.contentType",
|
||||
"panel.metadata",
|
||||
"panel.contentType",
|
||||
"panel.gitActions",
|
||||
"panel.recentlyModified",
|
||||
"panel.otherActions",
|
||||
"dashboard.snippets.view",
|
||||
@@ -1045,7 +1097,7 @@
|
||||
"error"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.global.notifications.markdownDescription%",
|
||||
"scope": "Templates"
|
||||
"scope": "Global"
|
||||
},
|
||||
"frontMatter.global.disabledNotifications": {
|
||||
"type": "array",
|
||||
@@ -1055,6 +1107,12 @@
|
||||
"requiredFieldValidation"
|
||||
]
|
||||
},
|
||||
"frontMatter.global.timezone": {
|
||||
"default": "UTC",
|
||||
"type": "string",
|
||||
"markdownDescription": "%setting.frontMatter.global.timezone.markdownDescription%",
|
||||
"scope": "Global"
|
||||
},
|
||||
"frontMatter.media.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -1156,6 +1214,12 @@
|
||||
},
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.panel.openOnSupportedFile": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "%setting.frontMatter.panel.openOnSupportedFile.markdownDescription%",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.panel.freeform": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -1243,9 +1307,16 @@
|
||||
"fileType": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fileType.description%"
|
||||
},
|
||||
@@ -1352,7 +1423,14 @@
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description%"
|
||||
},
|
||||
"wysiwyg": {
|
||||
"type": "boolean",
|
||||
"type": [
|
||||
"boolean",
|
||||
"string"
|
||||
],
|
||||
"enum": [
|
||||
"html",
|
||||
"markdown"
|
||||
],
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description%"
|
||||
},
|
||||
@@ -1521,6 +1599,11 @@
|
||||
"default": "path",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description%"
|
||||
},
|
||||
"sameContentLocale": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description%"
|
||||
},
|
||||
"when": {
|
||||
"type": "object",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description%",
|
||||
@@ -1564,6 +1647,13 @@
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.actions.description%",
|
||||
"items": {
|
||||
"$ref": "#customscript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -1986,7 +2076,11 @@
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.slugTemplate": {
|
||||
"type": "string",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "%setting.frontMatter.taxonomy.slugTemplate.markdownDescription%",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
@@ -1998,11 +2092,6 @@
|
||||
},
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.telemetry.disable": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "%setting.frontMatter.telemetry.disable.markdownDescription%"
|
||||
},
|
||||
"frontMatter.templates.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -2017,7 +2106,7 @@
|
||||
},
|
||||
"frontMatter.templates.prefix": {
|
||||
"type": "string",
|
||||
"default": "yyyy-MM-dd",
|
||||
"default": "{{date|yyyy-MM-dd}}",
|
||||
"markdownDescription": "%setting.frontMatter.templates.prefix.markdownDescription%",
|
||||
"scope": "Templates"
|
||||
},
|
||||
@@ -2034,6 +2123,11 @@
|
||||
"info",
|
||||
"verbose"
|
||||
]
|
||||
},
|
||||
"frontMatter.copilot.family": {
|
||||
"type": "string",
|
||||
"default": "gpt-4o-mini",
|
||||
"markdownDescription": "%setting.frontMatter.copilot.family.markdownDescription%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2360,6 +2454,15 @@
|
||||
"title": "%command.frontMatter.cache.clear%",
|
||||
"category": "Front Matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.createOrOpen",
|
||||
"title": "%command.frontMatter.i18n.createOrOpen%",
|
||||
"category": "Front Matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/i18n-light.svg",
|
||||
"dark": "assets/icons/i18n-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"title": "%command.frontMatter.i18n.create%",
|
||||
@@ -2387,72 +2490,72 @@
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.bold",
|
||||
"group": "navigation@-132",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.italic",
|
||||
"group": "navigation@-131",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.hyperlink",
|
||||
"group": "navigation@-130",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertSnippet",
|
||||
"group": "navigation@-129",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertMedia",
|
||||
"group": "navigation@-128",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
"when": "frontMatter:file:isValid == true && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"command": "frontMatter.i18n.createOrOpen",
|
||||
"group": "navigation@-127",
|
||||
"when": "frontMatter:file:isValid && frontMatter:i18n:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.options",
|
||||
"group": "navigation@-126",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.orderedlist",
|
||||
"group": "1_markup@1",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.unorderedlist",
|
||||
"group": "1_markup@2",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.tasklist",
|
||||
"group": "1_markup@3",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.code",
|
||||
"group": "1_markup@4",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.codeblock",
|
||||
"group": "1_markup@5",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"group": "1_markup@6",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
@@ -2682,23 +2785,23 @@
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"group": "navigation@1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.mode.switch",
|
||||
"group": "navigation@2",
|
||||
"group": "navigation@1",
|
||||
"when": "view == frontMatter.explorer && frontMatter:has:modes == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"group": "navigation@3",
|
||||
"group": "navigation@2",
|
||||
"when": "view == frontMatter.explorer && frontMatter:project:switch:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.settings.refresh",
|
||||
"group": "navigation@3",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"group": "navigation@4",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
@@ -2803,6 +2906,7 @@
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -2819,11 +2923,10 @@
|
||||
"@types/react-datepicker": "^4.8.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@types/webpack-bundle-analyzer": "^4.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
"@vscode/l10n": "^0.0.14",
|
||||
"@vscode/webview-ui-toolkit": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.7.0",
|
||||
"ajv": "^8.12.0",
|
||||
"array-move": "^4.0.0",
|
||||
@@ -2832,10 +2935,12 @@
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"clsx": "^2.1.0",
|
||||
"css-loader": "5.2.7",
|
||||
"date-fns": "2.23.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"downshift": "6.0.6",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-webpack-plugin": "^4.2.0",
|
||||
"fuse.js": "6.5.3",
|
||||
"github-directory-downloader": "^1.3.6",
|
||||
"glob": "^10.3.12",
|
||||
@@ -2869,8 +2974,16 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
"simple-git": "^3.16.0",
|
||||
@@ -2880,24 +2993,24 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"unified": "^11.0.5",
|
||||
"uniforms": "^3.10.2",
|
||||
"uniforms-antd": "^3.10.2",
|
||||
"uniforms-bridge-json-schema": "^3.10.2",
|
||||
"uniforms-unstyled": "^3.10.2",
|
||||
"url-join-ts": "^1.0.5",
|
||||
"vscrui": "^0.1.0-beta.1094721",
|
||||
"wc-react": "github:estruyf/wc-react",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.7.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-ignore-dynamic-require": "^1.0.0",
|
||||
"webpack-manifest-plugin": "^5.0.0",
|
||||
"yaml": "^2.2.1",
|
||||
"yawn-yaml": "^1.5.0"
|
||||
},
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"command.frontMatter.git.sync": "Sync",
|
||||
"command.frontMatter.cache.clear": "Clear cache",
|
||||
"command.frontMatter.i18n.create": "Create new translation",
|
||||
"command.frontMatter.i18n.createOrOpen": "Create or open translation",
|
||||
"settings.configuration.title": "Front Matter: use frontmatter.json for shared team settings",
|
||||
"setting.frontMatter.projects.markdownDescription": "Specify the list of projects to load in the Front Matter CMS. [Local](https://file%2B.vscode-resource.vscode-cdn.net/Users/eliostruyf/nodejs/frontmatter-test-projects/astro-blog/test.html) - [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.projects) - [View in VS Code](vscode://simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.projects%22%5D)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Specify the name of the project.",
|
||||
@@ -73,6 +74,7 @@
|
||||
"setting.frontMatter.content.pageFolders.items.properties.title.description": "Name of the folder",
|
||||
"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.excludePaths.description": "Exclude paths (e.g. api, _*.*)",
|
||||
"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.",
|
||||
@@ -94,6 +96,10 @@
|
||||
"setting.frontMatter.content.publicFolder.properties.relative.description": "Defines if the path to your media files be relative to the content file?",
|
||||
"setting.frontMatter.snippets.wrapper.enabled.markdownDescription": "Specify if you want to wrap the snippets. [Docs](https://frontmatter.codes/docs/settings/overview#frontMatter.snippets.wrapper.enabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.snippets.wrapper.enabled%22%5D)",
|
||||
"setting.frontMatter.content.snippets.markdownDescription": "Define the snippets you want to use in your content. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.snippets) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.snippets%22%5D)",
|
||||
"setting.frontMatter.content.grouping.markdownDescription": "Specify the grouping options for your dashboard content. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.grouping) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.grouping%22%5D)",
|
||||
"setting.frontMatter.content.grouping.items.properties.id.description": "The ID of the grouping option.",
|
||||
"setting.frontMatter.content.grouping.items.properties.title.description": "Title of the grouping which will be shown in the UI.",
|
||||
"setting.frontMatter.content.grouping.items.properties.name.description": "Name of the content-type field to group by.",
|
||||
"setting.frontMatter.content.sorting.markdownDescription": "Define the sorting options for your dashboard content. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.sorting) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.sorting%22%5D)",
|
||||
"setting.frontMatter.content.sorting.items.properties.id.description": "The ID of the sorting option. This will be used for the storing the last used sorting option or the default option.",
|
||||
"setting.frontMatter.content.sorting.items.properties.title.description": "Name of the sorting label",
|
||||
@@ -145,6 +151,8 @@
|
||||
"setting.frontMatter.data.folders.items.properties.path.description": "Path to the folder to load files.",
|
||||
"setting.frontMatter.data.folders.items.properties.type.description": "If you are using data types, you can specify your type ID.",
|
||||
"setting.frontMatter.data.folders.items.properties.singleEntry.description": "If you want to use a single entry for your data files in the folder.",
|
||||
"setting.frontMatter.data.folders.items.properties.enableFileCreation.description": "Enable the creation of new data files in the folder.",
|
||||
"setting.frontMatter.data.folders.items.properties.fileType.description": "Defines the file type for when the file creation is enabled. JSON is the default.",
|
||||
"setting.frontMatter.data.types.markdownDescription": "Specify the data types. These types can be used in for your data files. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.data.types) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.data.types%22%5D)",
|
||||
"setting.frontMatter.data.types.items.properties.id.description": "Your unique ID you want to use for your data type.",
|
||||
"setting.frontMatter.file.preserveCasing.markdownDescription": "Specify if you want to preserve the casing of your file names from the title. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.file.preservecasing) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.file.preservecasing%22%5D)",
|
||||
@@ -162,6 +170,7 @@
|
||||
"setting.frontMatter.global.modes.items.properties.features.description": "The features you want to use for your mode.",
|
||||
"setting.frontMatter.global.notifications.markdownDescription": "Specifies the notifications you want to see. By default, all notifications types will be shown. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.global.notifications) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.notifications%22%5D)",
|
||||
"setting.frontMatter.global.disabledNotifications.markdownDescription": "This is an array with the notifications types that can be disabled for Front Matter CMS. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.disablednotifications%22%5D)",
|
||||
"setting.frontMatter.global.timezone.markdownDescription": "Specify the timezone for your date formatting. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.datetimezone) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.datetimezone%22%5D)",
|
||||
"setting.frontMatter.media.defaultSorting.markdownDescription": "Specify the default sorting option for the media dashboard. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.media.defaultsorting%22%5D)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "Specify the mime types to support for the media files. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.media.supportedmimetypes%22%5D)",
|
||||
|
||||
@@ -175,6 +184,7 @@
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "Define the type of field",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "Is a single line field",
|
||||
|
||||
"setting.frontMatter.panel.openOnSupportedFile.markdownDescription": "Specifies if you want to open the panel when opening a supported file. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.openonsupportedfile) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.openonsupportedfile%22%5D)",
|
||||
"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. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.freeform%22%5D)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.actions.disabled%22%5D)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.preview.host%22%5D)",
|
||||
@@ -200,7 +210,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "The choice ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "The choice title",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "Is a single line field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field (HTML output)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field. You can set it to markdown or HTML.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "Do you allow to select multiple values?",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "Specify if the image field can be used as preview. Be aware, you can only have one preview image per content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "Do you want to hide the field from the metadata section?",
|
||||
@@ -226,11 +236,13 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.required.description": "Specify if the field is required",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeName.description": "Specify the content type name to filter content for the contentRelationship field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description": "Specify the value to insert for the contentRelationship field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description": "Specify if you only want to show the content with the same locale",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description": "Specify the conditions to show the field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.fieldRef.description": "The field ID to use",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.operator.description": "The operator to use",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.value.description": "The value to compare",
|
||||
"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.fields.items.properties.actions.description": "Specify the field custom actions",
|
||||
"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.",
|
||||
@@ -279,5 +291,6 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "Specify if the content type is sub content.",
|
||||
|
||||
"setting.frontMatter.git.disableOnBranches.markdownDescription": "Specify the branches on which you want to disable the Git actions. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.disableonbranches%22%5D)",
|
||||
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "Specify if you want to require a commit message when publishing your changes for a specified branch. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.requirescommitmessage%22%5D)"
|
||||
}
|
||||
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "Specify if you want to require a commit message when publishing your changes for a specified branch. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.requirescommitmessage%22%5D)",
|
||||
"setting.frontMatter.copilot.family.markdownDescription": "Specify the LLM family of the Copilot you want to use. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.copilot.family) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.copilot.family%22%5D)"
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@ const core = require('@actions/core');
|
||||
const packageJson = require('../package.json');
|
||||
const version = packageJson.version.split('.');
|
||||
|
||||
packageJson.version = `${version[0]}.${version[1]}.${process.argv[process.argv.length-1].substr(0, 7)}`;
|
||||
packageJson.version = `${version[0]}.${version[1]}.${process.argv[
|
||||
process.argv.length - 1
|
||||
].substring(0, 9)}`;
|
||||
packageJson.preview = true;
|
||||
packageJson.name = "vscode-front-matter-beta";
|
||||
packageJson.name = 'vscode-front-matter-beta';
|
||||
packageJson.displayName = `${packageJson.displayName} (BETA)`;
|
||||
packageJson.description = `BETA Version of Front Matter. ${packageJson.description}`;
|
||||
packageJson.icon = "assets/frontmatter-beta.png";
|
||||
packageJson.homepage = "https://beta.frontmatter.codes";
|
||||
packageJson.icon = 'assets/frontmatter-beta.png';
|
||||
packageJson.homepage = 'https://beta.frontmatter.codes';
|
||||
|
||||
console.log(packageJson.version);
|
||||
|
||||
@@ -20,19 +22,24 @@ core.summary.addHeading(`Version info`).addRaw(`Version: ${packageJson.version}`
|
||||
const scripts = packageJson.scripts;
|
||||
for (const key in scripts) {
|
||||
if (key.startsWith(`prod:`)) {
|
||||
scripts[key] = scripts[key].replace("production", "development");
|
||||
scripts[key] = scripts[key].replace('production', 'development');
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(packageJson.scripts, null, 2));
|
||||
|
||||
fs.writeFileSync(path.join(path.resolve('.'), 'package.json'), JSON.stringify(packageJson, null, 2));
|
||||
fs.writeFileSync(
|
||||
path.join(path.resolve('.'), 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2)
|
||||
);
|
||||
|
||||
let readme = fs.readFileSync(path.join(__dirname, '../README.beta.md'), 'utf8');
|
||||
fs.writeFileSync(path.join(__dirname, '../README.md'), readme);
|
||||
|
||||
// Update the .vscodeignore file
|
||||
const ignoreFilePath = path.join(path.resolve('.'), '.vscodeignore');
|
||||
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
|
||||
vscodeignore = vscodeignore.replace(`**/*.map`, '');
|
||||
fs.writeFileSync(ignoreFilePath, vscodeignore);
|
||||
if (fs.existsSync(ignoreFilePath)) {
|
||||
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
|
||||
vscodeignore = vscodeignore.replace(`**/*.map`, '');
|
||||
fs.writeFileSync(ignoreFilePath, vscodeignore);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,9 @@ import {
|
||||
SETTING_SLUG_PREFIX,
|
||||
SETTING_SLUG_SUFFIX,
|
||||
SETTING_CONTENT_PLACEHOLDERS,
|
||||
TelemetryEvent,
|
||||
SETTING_SLUG_TEMPLATE
|
||||
} from './../constants';
|
||||
import { CustomPlaceholder, Field } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Logger,
|
||||
@@ -39,13 +37,13 @@ import { COMMAND_NAME, DefaultFields } from '../constants';
|
||||
import { DashboardData, SnippetInfo, SnippetRange } from '../models/DashboardData';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { MediaListener } from '../listeners/panel';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { SNIPPET } from '../constants/Snippet';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { formatInTimezone, getTitleField } from '../utils';
|
||||
|
||||
export class Article {
|
||||
/**
|
||||
@@ -53,7 +51,7 @@ export class Article {
|
||||
*
|
||||
* @param subscriptions - The array of subscriptions to register the commands with.
|
||||
*/
|
||||
public static async registerCommands(subscriptions: unknown[]) {
|
||||
public static registerCommands(subscriptions: unknown[]) {
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate)
|
||||
);
|
||||
@@ -67,6 +65,15 @@ export class Article {
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers event listeners for the Article class.
|
||||
*
|
||||
* @param subscriptions - An array to which the event listener will be added.
|
||||
*/
|
||||
public static registerListeners(subscriptions: unknown[]) {
|
||||
subscriptions.push(workspace.onWillSaveTextDocument(Article.autoUpdate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the article date
|
||||
*/
|
||||
@@ -165,7 +172,12 @@ export class Article {
|
||||
/**
|
||||
* Generate the new slug
|
||||
*/
|
||||
public static generateSlug(title: string, article?: ParsedFrontMatter, slugTemplate?: string) {
|
||||
public static generateSlug(
|
||||
title: string,
|
||||
article?: ParsedFrontMatter,
|
||||
filePath?: string,
|
||||
slugTemplate?: string
|
||||
) {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
@@ -174,9 +186,9 @@ export class Article {
|
||||
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
|
||||
|
||||
if (article?.data) {
|
||||
const slug = SlugHelper.createSlug(title, article?.data, slugTemplate);
|
||||
const slug = SlugHelper.createSlug(title, article?.data, filePath, slugTemplate);
|
||||
|
||||
if (slug) {
|
||||
if (typeof slug === 'string') {
|
||||
return {
|
||||
slug,
|
||||
slugWithPrefixAndSuffix: `${prefix}${slug}${suffix}`
|
||||
@@ -191,8 +203,6 @@ export class Article {
|
||||
* Generate the slug based on the article title
|
||||
*/
|
||||
public static async updateSlug() {
|
||||
Telemetry.send(TelemetryEvent.generateSlug);
|
||||
|
||||
const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
@@ -205,19 +215,32 @@ export class Article {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = getTitleField();
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const articleDate = await ArticleHelper.getDate(article);
|
||||
|
||||
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
filePrefix = await ArticleHelper.getFilePrefix(
|
||||
filePrefix,
|
||||
editor.document.uri.fsPath,
|
||||
contentType
|
||||
contentType,
|
||||
articleTitle,
|
||||
articleDate
|
||||
);
|
||||
|
||||
const titleField = 'title';
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const slugInfo = Article.generateSlug(articleTitle, article, contentType.slugTemplate);
|
||||
const slugInfo = Article.generateSlug(
|
||||
articleTitle,
|
||||
article,
|
||||
editor.document.uri.fsPath,
|
||||
contentType.slugTemplate
|
||||
);
|
||||
|
||||
if (slugInfo && slugInfo.slug && slugInfo.slugWithPrefixAndSuffix) {
|
||||
if (
|
||||
slugInfo &&
|
||||
typeof slugInfo.slug === 'string' &&
|
||||
typeof slugInfo.slugWithPrefixAndSuffix === 'string'
|
||||
) {
|
||||
article.data['slug'] = slugInfo.slugWithPrefixAndSuffix;
|
||||
|
||||
if (contentType) {
|
||||
@@ -242,7 +265,8 @@ export class Article {
|
||||
article.data[pField.name] = processArticlePlaceholdersFromData(
|
||||
article.data[pField.name],
|
||||
article.data,
|
||||
contentType
|
||||
contentType,
|
||||
editor.document.uri.fsPath
|
||||
);
|
||||
article.data[pField.name] = processTimePlaceholders(
|
||||
article.data[pField.name],
|
||||
@@ -267,7 +291,11 @@ export class Article {
|
||||
|
||||
let newFileName = `${slugName}${ext}`;
|
||||
if (filePrefix && typeof filePrefix === 'string') {
|
||||
newFileName = `${filePrefix}-${newFileName}`;
|
||||
if (filePrefix.endsWith('/')) {
|
||||
newFileName = `${filePrefix}${newFileName}`;
|
||||
} else {
|
||||
newFileName = `${filePrefix}-${newFileName}`;
|
||||
}
|
||||
}
|
||||
|
||||
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
|
||||
@@ -307,17 +335,18 @@ export class Article {
|
||||
|
||||
const parsedFile = parse(file);
|
||||
|
||||
const titleField = getTitleField();
|
||||
const slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE);
|
||||
if (slugTemplate) {
|
||||
if (slugTemplate === '{{title}}') {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data?.title) {
|
||||
return article.data.title.toLowerCase().replace(/\s/g, '-');
|
||||
if (article?.data && article.data[titleField]) {
|
||||
return article.data[titleField].toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
} else {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
return SlugHelper.createSlug(article.data.title, article.data, slugTemplate);
|
||||
return SlugHelper.createSlug(article.data[titleField], article.data, file, slugTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,7 +388,7 @@ export class Article {
|
||||
* Article auto updater
|
||||
* @param event
|
||||
*/
|
||||
public static async autoUpdate(event: TextDocumentWillSaveEvent) {
|
||||
public static autoUpdate(event: TextDocumentWillSaveEvent) {
|
||||
const document = event.document;
|
||||
if (document && ArticleHelper.isSupportedFile(document)) {
|
||||
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
|
||||
@@ -367,7 +396,7 @@ export class Article {
|
||||
// Is article located in one of the content folders
|
||||
const folders = Folders.getCached();
|
||||
const documentPath = parseWinPath(document.fileName);
|
||||
const folder = folders.find((f) => documentPath.startsWith(f.path));
|
||||
const folder = folders?.find((f) => documentPath.startsWith(f.path));
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
@@ -388,10 +417,10 @@ export class Article {
|
||||
|
||||
if (fieldDateFormat) {
|
||||
Logger.verbose(`Article:formatDate:FieldDateFormat - ${fieldDateFormat}`);
|
||||
return format(dateValue, DateHelper.formatUpdate(fieldDateFormat) as string);
|
||||
return formatInTimezone(dateValue, DateHelper.formatUpdate(fieldDateFormat) as string);
|
||||
} else if (dateFormat && typeof dateFormat === 'string') {
|
||||
Logger.verbose(`Article:formatDate:DateFormat - ${dateFormat}`);
|
||||
return format(dateValue, DateHelper.formatUpdate(dateFormat) as string);
|
||||
return formatInTimezone(dateValue, DateHelper.formatUpdate(dateFormat) as string);
|
||||
} else {
|
||||
Logger.verbose(`Article:formatDate:toISOString - ${dateValue}`);
|
||||
return typeof dateValue.toISOString === 'function'
|
||||
@@ -490,11 +519,12 @@ export class Article {
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType = article ? await ArticleHelper.getContentType(article) : undefined;
|
||||
const tileField = getTitleField();
|
||||
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: NavigationType.Snippets,
|
||||
data: {
|
||||
fileTitle: article?.data.title || '',
|
||||
fileTitle: article?.data[tileField] || '',
|
||||
filePath: editor.document.uri.fsPath,
|
||||
fieldName: basename(editor.document.uri.fsPath),
|
||||
contentType,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { authentication, commands, ExtensionContext } from 'vscode';
|
||||
import { COMMAND_NAME, CONTEXT } from '../constants';
|
||||
import { COMMAND_NAME, CONTEXT, WEBSITE_LINKS } from '../constants';
|
||||
import { Extension, Logger } from '../helpers';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { SettingsListener } from '../listeners/panel';
|
||||
@@ -22,9 +22,8 @@ export class Backers {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], { silent: true });
|
||||
if (githubAuth && githubAuth.accessToken) {
|
||||
try {
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const response = await fetch(
|
||||
`https://${isBeta ? `beta.` : ``}frontmatter.codes/api/v2/backers`,
|
||||
`${WEBSITE_LINKS.api.baseUrl}${WEBSITE_LINKS.api.endpoints.backers}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { TelemetryEvent, PreviewCommands, GeneralCommands } from './../constants';
|
||||
import { PreviewCommands, GeneralCommands } from './../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, window } from 'vscode';
|
||||
import { Extension } from '../helpers';
|
||||
@@ -7,6 +6,7 @@ import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { getWebviewJsFiles } from '../utils';
|
||||
|
||||
export class Chatbot {
|
||||
/**
|
||||
@@ -33,31 +33,36 @@ export class Chatbot {
|
||||
|
||||
const cspSource = webView.webview.cspSource;
|
||||
|
||||
const fetchLocalization = async (requestId: string) => {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
};
|
||||
|
||||
webView.webview.onDidReceiveMessage(async (message) => {
|
||||
switch (message.command) {
|
||||
const { command, requestId, payload, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (message.data) {
|
||||
commands.executeCommand('vscode.open', message.data);
|
||||
if (payload || data) {
|
||||
commands.executeCommand('vscode.open', payload || data);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case GeneralCommands.toVSCode.getLocalization:
|
||||
const { requestId } = message;
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
fetchLocalization(requestId);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
@@ -67,7 +72,6 @@ export class Chatbot {
|
||||
const isProd = ext.isProductionMode;
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const extensionUri = ext.extensionPath;
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -83,13 +87,11 @@ export class Chatbot {
|
||||
}`
|
||||
];
|
||||
|
||||
let scriptUri = '';
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView.webview
|
||||
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView.webview);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
// By default, the chatbot is seen as experimental
|
||||
@@ -112,11 +114,13 @@ export class Chatbot {
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} style="width:100%;height:100%;margin:0;padding:0;"></div>
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`chatbot-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
Telemetry.send(TelemetryEvent.openChatbot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ import {
|
||||
CONTEXT,
|
||||
ExtensionState,
|
||||
SETTING_EXPERIMENTAL,
|
||||
SETTING_EXTENSIBILITY_SCRIPTS,
|
||||
COMMAND_NAME,
|
||||
TelemetryEvent
|
||||
COMMAND_NAME
|
||||
} from '../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode';
|
||||
@@ -18,7 +16,6 @@ import {
|
||||
DashboardListener,
|
||||
MediaListener,
|
||||
SettingsListener,
|
||||
TelemetryListener,
|
||||
DataListener,
|
||||
PagesListener,
|
||||
ExtensionListener,
|
||||
@@ -29,12 +26,11 @@ import {
|
||||
} from '../listeners/dashboard';
|
||||
import { MediaListener as PanelMediaListener } from '../listeners/panel';
|
||||
import { GitListener, ModeListener } from '../listeners/general';
|
||||
import { Folders } from './Folders';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { ignoreMsgCommand } from '../utils';
|
||||
import { getExtensibilityScripts, getWebviewJsFiles, ignoreMsgCommand } from '../utils';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -67,7 +63,6 @@ export class Dashboard {
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openContentDashboard);
|
||||
if (!data) {
|
||||
Dashboard.open({ type: NavigationType.Contents });
|
||||
} else {
|
||||
@@ -78,35 +73,30 @@ export class Dashboard {
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardMedia, () => {
|
||||
Telemetry.send(TelemetryEvent.openMediaDashboard);
|
||||
Dashboard.open({ type: NavigationType.Media });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardSnippets, () => {
|
||||
Telemetry.send(TelemetryEvent.openSnippetsDashboard);
|
||||
Dashboard.open({ type: NavigationType.Snippets });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardData, () => {
|
||||
Telemetry.send(TelemetryEvent.openDataDashboard);
|
||||
Dashboard.open({ type: NavigationType.Data });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardTaxonomy, () => {
|
||||
Telemetry.send(TelemetryEvent.openTaxonomyDashboard);
|
||||
Dashboard.open({ type: NavigationType.Taxonomy });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardClose, () => {
|
||||
Telemetry.send(TelemetryEvent.closeDashboard);
|
||||
Dashboard.close();
|
||||
})
|
||||
);
|
||||
@@ -241,7 +231,6 @@ export class Dashboard {
|
||||
PagesListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
DataListener.process(msg);
|
||||
TelemetryListener.process(msg);
|
||||
SnippetListener.process(msg);
|
||||
ModeListener.process(msg);
|
||||
GitListener.process(msg);
|
||||
@@ -285,18 +274,17 @@ export class Dashboard {
|
||||
* @param webView
|
||||
*/
|
||||
private static async getWebviewContent(webView: Webview, extensionPath: Uri): Promise<string> {
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
let scriptUri = '';
|
||||
const isProd = Extension.getInstance().isProductionMode;
|
||||
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView
|
||||
.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
@@ -307,20 +295,8 @@ export class Dashboard {
|
||||
|
||||
// Get experimental setting
|
||||
const experimental = SettingsHelper.get(SETTING_EXPERIMENTAL);
|
||||
const extensibilityScripts = SettingsHelper.get<string[]>(SETTING_EXTENSIBILITY_SCRIPTS) || [];
|
||||
|
||||
const scriptsToLoad: string[] = [];
|
||||
if (experimental) {
|
||||
for (const script of extensibilityScripts) {
|
||||
if (script.startsWith('https://')) {
|
||||
scriptsToLoad.push(script);
|
||||
} else {
|
||||
const absScriptPath = Folders.getAbsFilePath(script);
|
||||
const scriptUri = webView.asWebviewUri(Uri.file(absScriptPath));
|
||||
scriptsToLoad.push(scriptUri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
const scriptsToLoad: string[] = getExtensibilityScripts(webView);
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -374,7 +350,9 @@ export class Dashboard {
|
||||
})
|
||||
.join('')}
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Folders } from './Folders';
|
||||
import { ViewColumn, commands, workspace } from 'vscode';
|
||||
import { ViewColumn, commands, version, workspace } from 'vscode';
|
||||
import ContentProvider from '../providers/ContentProvider';
|
||||
import { join } from 'path';
|
||||
import { ContentFolder } from '../models';
|
||||
@@ -37,6 +37,8 @@ export class Diagnostics {
|
||||
|
||||
Beta: \`${Extension.getInstance().isBetaVersion()}\`
|
||||
Version: \`${Extension.getInstance().version}\`
|
||||
OS: \`${process.platform}\`
|
||||
VSCode version: \`${version}\`
|
||||
|
||||
## Project name
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ import {
|
||||
SETTING_CONTENT_PAGE_FOLDERS,
|
||||
SETTING_CONTENT_STATIC_FOLDER,
|
||||
SETTING_CONTENT_SUPPORTED_FILETYPES,
|
||||
SETTING_DATE_FORMAT,
|
||||
TelemetryEvent
|
||||
SETTING_DATE_FORMAT
|
||||
} from './../constants';
|
||||
import { commands, Uri, workspace, window } from 'vscode';
|
||||
import { basename, dirname, join, relative, sep } from 'path';
|
||||
@@ -24,16 +23,14 @@ import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Extension, Logger, Settings, processTimePlaceholders } from '../helpers';
|
||||
import { existsSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { MediaHelpers } from '../helpers/MediaHelpers';
|
||||
import { MediaListener, PagesListener, SettingsListener } from '../listeners/dashboard';
|
||||
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { glob } from 'glob';
|
||||
import { mkdirAsync } from '../utils/mkdirAsync';
|
||||
import { existsAsync, isWindows } from '../utils';
|
||||
import { existsAsync, formatInTimezone, isWindows, lstatAsync } from '../utils';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { Preview } from './Preview';
|
||||
@@ -41,7 +38,7 @@ import { Preview } from './Preview';
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
export class Folders {
|
||||
private static _folders: ContentFolder[] = [];
|
||||
private static _folders: ContentFolder[] | undefined = undefined;
|
||||
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
@@ -52,7 +49,7 @@ export class Folders {
|
||||
|
||||
public static clearCached() {
|
||||
Logger.verbose(`Folders:clearCached`);
|
||||
Folders._folders = [];
|
||||
Folders._folders = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +84,7 @@ export class Folders {
|
||||
prompt: l10n.t(LocalizationKey.commandsFoldersAddMediaFolderInputBoxPrompt),
|
||||
value: startPath,
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: `${format(new Date(), `yyyy/MM`)}`
|
||||
placeHolder: `${formatInTimezone(new Date(), `yyyy/MM`)}`
|
||||
});
|
||||
|
||||
if (!folderName) {
|
||||
@@ -107,8 +104,6 @@ export class Folders {
|
||||
MediaHelpers.resetMedia();
|
||||
MediaListener.sendMediaFiles(0, folderPath);
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.addMediaFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,8 +178,6 @@ export class Folders {
|
||||
|
||||
Notifications.info(l10n.t(LocalizationKey.commandsFoldersCreateSuccess));
|
||||
|
||||
Telemetry.send(TelemetryEvent.registerFolder);
|
||||
|
||||
SettingsListener.getSettings(true);
|
||||
}
|
||||
}
|
||||
@@ -198,8 +191,6 @@ export class Folders {
|
||||
let folders = await Folders.get();
|
||||
folders = folders.filter((f) => f.path !== folder.fsPath);
|
||||
await Folders.update(folders);
|
||||
|
||||
Telemetry.send(TelemetryEvent.unregisterFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +219,9 @@ export class Folders {
|
||||
: Folders.getAbsFilePath(assetFolder);
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const relativePath = relative(parseWinPath(wsFolder.fsPath), parseWinPath(assetFolder));
|
||||
const relativePath = parseWinPath(
|
||||
relative(parseWinPath(wsFolder.fsPath), parseWinPath(assetFolder))
|
||||
);
|
||||
return relativePath === '' ? '/' : relativePath;
|
||||
}
|
||||
}
|
||||
@@ -335,7 +328,7 @@ export class Folders {
|
||||
public static async get(): Promise<ContentFolder[]> {
|
||||
Logger.verbose('Folders:get:start');
|
||||
|
||||
if (Folders._folders.length > 0) {
|
||||
if (Folders._folders && Folders._folders.length > 0) {
|
||||
Logger.verbose('Folders:get:end - cached folders');
|
||||
return Folders._folders;
|
||||
}
|
||||
@@ -409,8 +402,8 @@ export class Folders {
|
||||
folder.locales && folder.locales.length > 0 ? folder.locales : i18nSettings;
|
||||
|
||||
let defaultLocale;
|
||||
let sourcePath = folderPath;
|
||||
let localeFolders: ContentFolder[] = [];
|
||||
const sourcePath = folderPath;
|
||||
const localeFolders: ContentFolder[] = [];
|
||||
|
||||
if (i18nConfig && i18nConfig.length > 0) {
|
||||
for (const i18n of i18nConfig) {
|
||||
@@ -460,10 +453,23 @@ export class Folders {
|
||||
* Get the cached folder settings
|
||||
* @returns {ContentFolder[]} - The cached folder settings
|
||||
*/
|
||||
public static getCached(): ContentFolder[] {
|
||||
public static getCached(): ContentFolder[] | undefined {
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cached content folders if available, otherwise fetches fresh content folders.
|
||||
*
|
||||
* @returns {Promise<ContentFolder[]>} A promise that resolves to an array of content folders.
|
||||
*/
|
||||
public static async getCachedOrFresh(): Promise<ContentFolder[]> {
|
||||
if (Folders._folders && Folders._folders.length > 0) {
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
return await Folders.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the folder settings
|
||||
* @param folders
|
||||
@@ -565,6 +571,22 @@ export class Folders {
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given file path to a workspace-relative path.
|
||||
*
|
||||
* @param path - The file path to convert.
|
||||
* @returns The workspace-relative path.
|
||||
*/
|
||||
public static wsPath(path: string) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let absPath = parseWinPath(path).replace(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
WORKSPACE_PLACEHOLDER
|
||||
);
|
||||
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate relative folder path
|
||||
* @param folder
|
||||
@@ -616,15 +638,23 @@ export class Folders {
|
||||
}
|
||||
}
|
||||
|
||||
// For Windows, we need to make sure the drive letter is lowercased for consistency
|
||||
if (isWindows()) {
|
||||
folders = folders.map((folder) => parseWinPath(folder));
|
||||
}
|
||||
|
||||
// Filter out the workspace folder
|
||||
if (wsFolder) {
|
||||
folders = folders.filter((folder) => folder !== wsFolder.fsPath);
|
||||
folders = folders.filter((folder) => folder !== parseWinPath(wsFolder.fsPath));
|
||||
}
|
||||
|
||||
const uniqueFolders = [...new Set(folders)];
|
||||
const relativeFolderPaths = uniqueFolders.map((folder) =>
|
||||
parseWinPath(relative(parseWinPath(wsFolder.fsPath), folder))
|
||||
);
|
||||
|
||||
Logger.verbose('Folders:getContentFolders:end');
|
||||
return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder));
|
||||
return relativeFolderPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -680,7 +710,7 @@ export class Folders {
|
||||
public static async getPageFolderByFilePath(
|
||||
filePath: string
|
||||
): Promise<ContentFolder | undefined> {
|
||||
const folders = Folders.getCached();
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
const parsedPath = parseWinPath(filePath);
|
||||
const pageFolderMatches = folders
|
||||
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
|
||||
@@ -711,7 +741,7 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = Folders.getCached();
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
let selectedFolder: ContentFolder | undefined;
|
||||
|
||||
// Try to find the folder by content type
|
||||
@@ -780,7 +810,11 @@ export class Folders {
|
||||
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
|
||||
}
|
||||
|
||||
let foundFiles = await Folders.findFiles(filePath);
|
||||
let foundFiles = await Folders.findFiles(
|
||||
filePath,
|
||||
join(folderPath, folder.excludeSubdir ? '/' : '**'),
|
||||
folder.excludePaths
|
||||
);
|
||||
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) =>
|
||||
@@ -835,9 +869,26 @@ export class Folders {
|
||||
|
||||
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)];
|
||||
const folders = await glob(pattern, {
|
||||
ignore: '**/node_modules/**',
|
||||
dot: true
|
||||
});
|
||||
|
||||
const onlyFolders = [];
|
||||
for (const folder of folders) {
|
||||
try {
|
||||
const stats = await lstatAsync(folder);
|
||||
if (stats.isDirectory()) {
|
||||
onlyFolders.push(folder);
|
||||
} else {
|
||||
onlyFolders.push(dirname(folder));
|
||||
}
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueFolders = [...new Set(onlyFolders)];
|
||||
Logger.verbose(`Folders:findFolders:end - ${uniqueFolders.length}`);
|
||||
return uniqueFolders;
|
||||
} catch (e) {
|
||||
@@ -851,12 +902,27 @@ export class Folders {
|
||||
* @param pattern
|
||||
* @returns
|
||||
*/
|
||||
private static async findFiles(pattern: string): Promise<Uri[]> {
|
||||
private static async findFiles(
|
||||
pattern: string,
|
||||
folderPath: string,
|
||||
excludePaths: string[] = []
|
||||
): Promise<Uri[]> {
|
||||
Logger.verbose(`Folders:findFiles:start - ${pattern}`);
|
||||
|
||||
try {
|
||||
pattern = isWindows() ? parseWinPath(pattern) : pattern;
|
||||
const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true });
|
||||
const files = await glob(pattern, {
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
...excludePaths.map((path) => {
|
||||
// path can be a folder name or a wildcard.
|
||||
// If its a folder name, we need to add a wildcard to the end
|
||||
path = path.includes('*') ? path : join(path, '**');
|
||||
return parseWinPath(join(folderPath, path));
|
||||
})
|
||||
],
|
||||
dot: true
|
||||
});
|
||||
const allFiles = (files || []).map((file) => Uri.file(file));
|
||||
Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`);
|
||||
return allFiles;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
SETTING_PREVIEW_HOST,
|
||||
SETTING_PREVIEW_PATHNAME,
|
||||
CONTEXT,
|
||||
TelemetryEvent,
|
||||
PreviewCommands,
|
||||
SETTING_EXPERIMENTAL,
|
||||
SETTING_DATE_FORMAT,
|
||||
@@ -20,7 +19,6 @@ import {
|
||||
processFmPlaceholders,
|
||||
processPathPlaceholders,
|
||||
Settings,
|
||||
Telemetry,
|
||||
processDateTimePlaceholders
|
||||
} from '../helpers';
|
||||
import { ContentFolder, ContentType, PreviewSettings } from '../models';
|
||||
@@ -31,7 +29,7 @@ import { ParsedFrontMatter } from '../parsers';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { joinUrl } from '../utils';
|
||||
import { getTitleField, getWebviewJsFiles, joinUrl } from '../utils';
|
||||
import { i18n } from './i18n';
|
||||
|
||||
export class Preview {
|
||||
@@ -77,11 +75,13 @@ export class Preview {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = getTitleField();
|
||||
|
||||
// Create the preview webview
|
||||
const webView = window.createWebviewPanel(
|
||||
'frontMatterPreview',
|
||||
article?.data?.title
|
||||
? l10n.t(LocalizationKey.commandsPreviewPanelTitle, article?.data.title)
|
||||
article?.data && article?.data[titleField]
|
||||
? l10n.t(LocalizationKey.commandsPreviewPanelTitle, article?.data[titleField])
|
||||
: 'Front Matter Preview',
|
||||
{
|
||||
viewColumn: ViewColumn.Beside,
|
||||
@@ -110,31 +110,36 @@ export class Preview {
|
||||
webView.dispose();
|
||||
});
|
||||
|
||||
const fetchLocalization = async (requestId: string) => {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
};
|
||||
|
||||
webView.webview.onDidReceiveMessage(async (message) => {
|
||||
switch (message.command) {
|
||||
const { command, payload, requestId } = message;
|
||||
|
||||
switch (command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (message.payload) {
|
||||
commands.executeCommand('vscode.open', message.payload);
|
||||
if (payload) {
|
||||
commands.executeCommand('vscode.open', payload);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case GeneralCommands.toVSCode.getLocalization:
|
||||
const { requestId } = message;
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
return;
|
||||
fetchLocalization(requestId);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
@@ -144,7 +149,6 @@ export class Preview {
|
||||
const isProd = ext.isProductionMode;
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const extensionUri = ext.extensionPath;
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -161,13 +165,11 @@ export class Preview {
|
||||
`frame-src ${localhostUrl} ${cspSource} http: https:;`
|
||||
];
|
||||
|
||||
let scriptUri = '';
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView.webview
|
||||
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView.webview);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
// Get experimental setting
|
||||
@@ -193,12 +195,14 @@ export class Preview {
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} style="width:100%;height:100%;margin:0;padding:0;"></div>
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`preview-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
Telemetry.send(TelemetryEvent.openPreview);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,7 +316,7 @@ export class Preview {
|
||||
|
||||
try {
|
||||
const articleDate = await ArticleHelper.getDate(article);
|
||||
pathname = processDateTimePlaceholders(pathname, dateFormat, articleDate);
|
||||
pathname = processDateTimePlaceholders(pathname, articleDate);
|
||||
slug = join(pathname, slug);
|
||||
} catch (error) {
|
||||
slug = join(pathname, slug);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { workspace, Uri, commands, window } from 'vscode';
|
||||
import { join } from 'path';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
@@ -16,8 +15,7 @@ import {
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
SETTING_CONTENT_DEFAULT_FILETYPE,
|
||||
SETTING_TAXONOMY_CONTENT_TYPES,
|
||||
TelemetryEvent
|
||||
SETTING_TAXONOMY_CONTENT_TYPES
|
||||
} from '../constants';
|
||||
import { SettingsListener } from '../listeners/dashboard';
|
||||
import { existsAsync, writeFileAsync } from '../utils';
|
||||
@@ -44,7 +42,7 @@ categories: []
|
||||
|
||||
// Initialize command
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
|
||||
commands.registerCommand(COMMAND_NAME.init, async (cb: () => void) => {
|
||||
await Project.init();
|
||||
|
||||
if (cb) {
|
||||
@@ -96,8 +94,6 @@ categories: []
|
||||
// Initialize the taxonomy database
|
||||
TaxonomyHelper.initDb();
|
||||
|
||||
Telemetry.send(TelemetryEvent.initialization);
|
||||
|
||||
// Check if you can find the framework
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const framework = await FrameworkDetector.get(wsFolder?.fsPath || '');
|
||||
|
||||
@@ -3,15 +3,12 @@ import {
|
||||
CONTEXT,
|
||||
EXTENSION_NAME,
|
||||
NOTIFICATION_TYPE,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_DESCRIPTION_LENGTH,
|
||||
SETTING_SEO_TITLE_FIELD,
|
||||
SETTING_SEO_TITLE_LENGTH
|
||||
} from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { ArticleHelper, Notifications, SeoHelper, Settings } from '../helpers';
|
||||
import { PanelProvider } from '../panelWebView/PanelProvider';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { DataListener } from '../listeners/panel';
|
||||
import { commands } from 'vscode';
|
||||
@@ -20,6 +17,7 @@ import { Preview } from './Preview';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { i18n } from './i18n';
|
||||
import { getDescriptionField, getTitleField } from '../utils';
|
||||
|
||||
export class StatusListener {
|
||||
/**
|
||||
@@ -56,12 +54,10 @@ export class StatusListener {
|
||||
collection.clear();
|
||||
|
||||
// Retrieve the SEO config properties
|
||||
const titleLength = (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1;
|
||||
const descLength = (Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number) || -1;
|
||||
const titleField =
|
||||
(Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField =
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
const titleLength = Settings.get<number>(SETTING_SEO_TITLE_LENGTH) || -1;
|
||||
const descLength = Settings.get<number>(SETTING_SEO_DESCRIPTION_LENGTH) || -1;
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
if (editor && article.data[titleField] && titleLength > -1) {
|
||||
SeoHelper.checkLength(editor, collection, article, titleField, titleLength);
|
||||
|
||||
33
src/commands/Taxonomy.ts
Normal file
33
src/commands/Taxonomy.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { commands } from 'vscode';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
import { TagType } from '../panelWebView/TagType';
|
||||
import { PanelProvider } from '../panelWebView/PanelProvider';
|
||||
|
||||
export class Taxonomy {
|
||||
/**
|
||||
* Registers the commands for the Article class.
|
||||
*
|
||||
* @param subscriptions - The array of subscriptions to register the commands with.
|
||||
*/
|
||||
public static async registerCommands(subscriptions: unknown[]) {
|
||||
const explorerSidebar = PanelProvider.getInstance();
|
||||
|
||||
if (explorerSidebar) {
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.insertTags, async () => {
|
||||
await commands.executeCommand('workbench.view.extension.frontmatter-explorer');
|
||||
await commands.executeCommand('workbench.action.focusSideBar');
|
||||
explorerSidebar.triggerInputFocus(TagType.tags);
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.insertCategories, async () => {
|
||||
await commands.executeCommand('workbench.view.extension.frontmatter-explorer');
|
||||
await commands.executeCommand('workbench.action.focusSideBar');
|
||||
explorerSidebar.triggerInputFocus(TagType.categories);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
DefaultFields,
|
||||
SETTING_CONTENT_DEFAULT_FILETYPE,
|
||||
SETTING_TEMPLATES_FOLDER,
|
||||
TelemetryEvent
|
||||
SETTING_TEMPLATES_FOLDER
|
||||
} from '../constants';
|
||||
import { ArticleHelper, Extension, Settings } from '../helpers';
|
||||
import { Article, Folders } from '.';
|
||||
@@ -15,7 +15,6 @@ import { ContentType } from '../helpers/ContentType';
|
||||
import { ContentType as IContentType } from '../models';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import { extname } from 'path';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { writeFileAsync, copyFileAsync } from '../utils';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
@@ -163,8 +162,14 @@ export class Template {
|
||||
|
||||
const templateData = await ArticleHelper.getFrontMatterByPath(template.fsPath);
|
||||
let contentType: IContentType | undefined;
|
||||
if (templateData && templateData.data && templateData.data.type) {
|
||||
contentType = contentTypes?.find((t) => t.name === templateData.data.type);
|
||||
if (templateData && templateData.data) {
|
||||
if (templateData.data[DefaultFields.ContentType]) {
|
||||
contentType = contentTypes?.find(
|
||||
(t) => t.name === templateData.data[DefaultFields.ContentType]
|
||||
);
|
||||
} else if (templateData.data[DefaultFields.Type]) {
|
||||
contentType = contentTypes?.find((t) => t.name === templateData.data[DefaultFields.Type]);
|
||||
}
|
||||
}
|
||||
|
||||
const fileExtension = extname(template.fsPath).replace('.', '');
|
||||
@@ -217,8 +222,6 @@ export class Template {
|
||||
|
||||
Notifications.info(l10n.t(LocalizationKey.commandsTemplateCreateSuccess));
|
||||
|
||||
Telemetry.send(TelemetryEvent.createContentFromTemplate);
|
||||
|
||||
// Trigger a refresh for the dashboard
|
||||
PagesListener.refresh();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { commands, window, Selection, QuickPickItem, TextEditor } from 'vscode';
|
||||
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { LocalizationKey, localize } from '../localization';
|
||||
|
||||
enum MarkupType {
|
||||
bold = 1,
|
||||
@@ -18,6 +17,8 @@ enum MarkupType {
|
||||
hyperlink
|
||||
}
|
||||
|
||||
type DocType = 'markdown' | 'asciidoc';
|
||||
|
||||
export class Wysiwyg {
|
||||
/**
|
||||
* Registers the markup commands for the WYSIWYG controls
|
||||
@@ -83,45 +84,45 @@ export class Wysiwyg {
|
||||
commands.registerCommand(COMMAND_NAME.options, async () => {
|
||||
const qpItems: QuickPickItem[] = [
|
||||
{
|
||||
label: `$(list-unordered) ${LocalizationKey.commandsWysiwygCommandUnorderedListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandUnorderedListDetail,
|
||||
label: `$(list-unordered) ${localize(LocalizationKey.commandsWysiwygCommandUnorderedListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandUnorderedListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(list-ordered) ${LocalizationKey.commandsWysiwygCommandOrderedListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandOrderedListDetail,
|
||||
label: `$(list-ordered) ${localize(LocalizationKey.commandsWysiwygCommandOrderedListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandOrderedListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(tasklist) ${LocalizationKey.commandsWysiwygCommandTaskListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandTaskListDetail,
|
||||
label: `$(tasklist) ${localize(LocalizationKey.commandsWysiwygCommandTaskListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandTaskListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(code) ${LocalizationKey.commandsWysiwygCommandCodeLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandCodeDetail,
|
||||
label: `$(code) ${localize(LocalizationKey.commandsWysiwygCommandCodeLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandCodeDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(symbol-namespace) ${LocalizationKey.commandsWysiwygCommandCodeblockLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandCodeblockDetail,
|
||||
label: `$(symbol-namespace) ${localize(LocalizationKey.commandsWysiwygCommandCodeblockLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandCodeblockDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(quote) ${LocalizationKey.commandsWysiwygCommandBlockquoteLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandBlockquoteDetail,
|
||||
label: `$(quote) ${localize(LocalizationKey.commandsWysiwygCommandBlockquoteLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandBlockquoteDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(symbol-text) ${LocalizationKey.commandsWysiwygCommandStrikethroughLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandStrikethroughDetail,
|
||||
label: `$(symbol-text) ${localize(LocalizationKey.commandsWysiwygCommandStrikethroughLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandStrikethroughDetail),
|
||||
alwaysShow: true
|
||||
}
|
||||
];
|
||||
|
||||
const option = await window.showQuickPick([...qpItems], {
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygQuickPickTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygQuickPickPlaceholder),
|
||||
title: localize(LocalizationKey.commandsWysiwygQuickPickTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygQuickPickPlaceholder),
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
@@ -147,6 +148,15 @@ export class Wysiwyg {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the document type based on the file extension.
|
||||
* @param filePath - The path of the file.
|
||||
* @returns The document type ('asciidoc' or 'markdown').
|
||||
*/
|
||||
public static getDocType(filePath: string): DocType {
|
||||
return filePath.endsWith('.adoc') ? 'asciidoc' : 'markdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the markup to the content
|
||||
* @param type
|
||||
@@ -161,11 +171,12 @@ export class Wysiwyg {
|
||||
const selection = editor.selection;
|
||||
const hasTextSelection = !selection.isEmpty;
|
||||
|
||||
const docType: DocType = Wysiwyg.getDocType(editor.document.fileName);
|
||||
if (type === MarkupType.hyperlink) {
|
||||
return this.addHyperlink(editor, selection);
|
||||
return this.addHyperlink(editor, selection, docType);
|
||||
}
|
||||
|
||||
const markers = this.getMarkers(type);
|
||||
const markers = this.getMarkers(type, docType);
|
||||
if (!markers) {
|
||||
return;
|
||||
}
|
||||
@@ -175,13 +186,13 @@ export class Wysiwyg {
|
||||
if (hasTextSelection) {
|
||||
// Replace the selection and surround with the markup
|
||||
const selectionText = editor.document.getText(selection);
|
||||
const txt = await this.insertText(markers, type, selectionText);
|
||||
const txt = await this.insertText(markers, type, selectionText, docType);
|
||||
|
||||
editor.edit((builder) => {
|
||||
builder.replace(selection, txt);
|
||||
});
|
||||
} else {
|
||||
const txt = await this.insertText(markers, type);
|
||||
const txt = await this.insertText(markers, type, null, docType);
|
||||
|
||||
// Insert the markers where cursor is located.
|
||||
const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length;
|
||||
@@ -198,6 +209,10 @@ export class Wysiwyg {
|
||||
newPosition = crntSelection.with(crntSelection.line + 1, 0);
|
||||
}
|
||||
|
||||
if (type === MarkupType.blockquote && docType === 'asciidoc') {
|
||||
newPosition = crntSelection.with(crntSelection.line + 1, 0);
|
||||
}
|
||||
|
||||
editor.selection = new Selection(newPosition, newPosition);
|
||||
}
|
||||
}
|
||||
@@ -206,28 +221,39 @@ export class Wysiwyg {
|
||||
* Add a hyperlink to the content
|
||||
* @returns void
|
||||
*/
|
||||
private static async addHyperlink(editor: TextEditor, selection: Selection) {
|
||||
private static async addHyperlink(
|
||||
editor: TextEditor,
|
||||
selection: Selection,
|
||||
docType: DocType = 'markdown'
|
||||
) {
|
||||
const hasTextSelection = !selection.isEmpty;
|
||||
const linkText = hasTextSelection ? editor.document.getText(selection) : '';
|
||||
|
||||
const link = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
title: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
prompt: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
value: linkText,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
const text = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
title: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputTitle),
|
||||
prompt: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
value: linkText,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (link) {
|
||||
const txt = `[${text || link}](${link})`;
|
||||
let txt = `[${text || link}](${link})`;
|
||||
|
||||
if (docType === 'asciidoc') {
|
||||
txt = !link.startsWith('http') ? `link:${link}` : link;
|
||||
if (text) {
|
||||
txt = `${txt}[${text}]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTextSelection) {
|
||||
editor.edit((builder) => {
|
||||
@@ -255,14 +281,23 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static isMarkupWrapping(type: MarkupType) {
|
||||
return (
|
||||
type === MarkupType.blockquote ||
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
private static isMarkupWrapping(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (docType === 'markdown') {
|
||||
return (
|
||||
type === MarkupType.blockquote ||
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
} else if (docType === 'asciidoc') {
|
||||
return (
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,17 +306,18 @@ export class Wysiwyg {
|
||||
private static async insertText(
|
||||
marker: string | undefined,
|
||||
type: MarkupType,
|
||||
text: string | null = null
|
||||
text: string | null = null,
|
||||
docType: DocType = 'markdown'
|
||||
) {
|
||||
const crntText = text || this.lineBreak(type);
|
||||
const crntText = text || this.lineBreak(type, docType);
|
||||
|
||||
if (this.isMarkupWrapping(type)) {
|
||||
if (this.isMarkupWrapping(type, docType)) {
|
||||
if (type === MarkupType.heading) {
|
||||
const headingLvl = await window.showQuickPick(
|
||||
['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'],
|
||||
{
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputPlaceholder),
|
||||
title: localize(LocalizationKey.commandsWysiwygInsertTextHeadingInputTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygInsertTextHeadingInputPlaceholder),
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
@@ -298,9 +334,12 @@ export class Wysiwyg {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
if (type === MarkupType.orderedList) {
|
||||
if (type === MarkupType.orderedList && docType === 'markdown') {
|
||||
const lines = crntText.split('\n').map((line, idx) => `${idx + 1}. ${line}`);
|
||||
return lines.join('\n');
|
||||
} else if (type === MarkupType.orderedList && docType === 'asciidoc') {
|
||||
const lines = crntText.split('\n').map((line) => `${marker} ${line}`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
return `${marker} ${crntText}`;
|
||||
@@ -314,9 +353,11 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static lineBreak(type: MarkupType) {
|
||||
private static lineBreak(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (type === MarkupType.codeblock) {
|
||||
return `\n\n`;
|
||||
} else if (type === MarkupType.blockquote && docType === 'asciidoc') {
|
||||
return `\n\n`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -326,30 +367,57 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static getMarkers(type: MarkupType) {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `**`;
|
||||
case MarkupType.italic:
|
||||
return `*`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '```';
|
||||
case MarkupType.blockquote:
|
||||
return '>';
|
||||
case MarkupType.heading:
|
||||
return '#';
|
||||
case MarkupType.unorderedList:
|
||||
return '-';
|
||||
case MarkupType.orderedList:
|
||||
return '1.';
|
||||
case MarkupType.taskList:
|
||||
return '- [ ]';
|
||||
default:
|
||||
return;
|
||||
private static getMarkers(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (docType === 'markdown') {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `**`;
|
||||
case MarkupType.italic:
|
||||
return `*`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '```';
|
||||
case MarkupType.blockquote:
|
||||
return '>';
|
||||
case MarkupType.heading:
|
||||
return '#';
|
||||
case MarkupType.unorderedList:
|
||||
return '-';
|
||||
case MarkupType.orderedList:
|
||||
return '1.';
|
||||
case MarkupType.taskList:
|
||||
return '- [ ]';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else if (docType === 'asciidoc') {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `*`;
|
||||
case MarkupType.italic:
|
||||
return `_`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '----';
|
||||
case MarkupType.blockquote:
|
||||
return '____';
|
||||
case MarkupType.heading:
|
||||
return '=';
|
||||
case MarkupType.unorderedList:
|
||||
return '*';
|
||||
case MarkupType.orderedList:
|
||||
return '.';
|
||||
case MarkupType.taskList:
|
||||
return '* [ ]';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { ProgressLocation, Uri, commands, window, workspace } from 'vscode';
|
||||
import {
|
||||
ProgressLocation,
|
||||
QuickPickItem,
|
||||
QuickPickItemKind,
|
||||
QuickPickOptions,
|
||||
Uri,
|
||||
commands,
|
||||
window,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
ContentType,
|
||||
@@ -12,12 +21,11 @@ import {
|
||||
import { COMMAND_NAME, SETTING_CONTENT_I18N } from '../constants';
|
||||
import { ContentFolder, Field, I18nConfig, ContentType as IContentType } from '../models';
|
||||
import { join, parse } from 'path';
|
||||
import { existsAsync } from '../utils';
|
||||
import { existsAsync, getDescriptionField, getTitleField } from '../utils';
|
||||
import { Folders } from '.';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { LocalizationKey, localize } from '../localization';
|
||||
import { Translations } from '../services/Translations';
|
||||
|
||||
export class i18n {
|
||||
@@ -32,6 +40,7 @@ export class i18n {
|
||||
const subscriptions = Extension.getInstance().subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.create, i18n.create));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.createOrOpen, i18n.createOrOpen));
|
||||
|
||||
i18n.clearFiles();
|
||||
}
|
||||
@@ -264,7 +273,7 @@ export class i18n {
|
||||
}
|
||||
|
||||
if (!fileUri) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,19 +283,19 @@ export class i18n {
|
||||
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
if (!pageFolder || !pageFolder.localeSourcePath) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
return;
|
||||
}
|
||||
|
||||
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
|
||||
if (!i18nSettings) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
|
||||
if (!sourceLocale || !sourceLocale.locale) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,15 +309,15 @@ export class i18n {
|
||||
});
|
||||
|
||||
if (targetLocales.length === 0) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
return;
|
||||
}
|
||||
|
||||
const locale = await window.showQuickPick(
|
||||
targetLocales.map((i18n) => i18n.title || i18n.locale),
|
||||
{
|
||||
title: l10n.t(LocalizationKey.commandsI18nCreateQuickPickTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
|
||||
title: localize(LocalizationKey.commandsI18nCreateQuickPickTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
);
|
||||
@@ -321,19 +330,19 @@ export class i18n {
|
||||
(i18n) => i18n.title === locale || i18n.locale === locale
|
||||
);
|
||||
if (!targetLocale || !targetLocale.path) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
|
||||
if (!article) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +374,7 @@ export class i18n {
|
||||
|
||||
const newFilePath = join(i18nDir, fileInfo.base);
|
||||
if (await existsAsync(newFilePath)) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorFileExists));
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorFileExists));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -384,7 +393,188 @@ export class i18n {
|
||||
PagesListener.refresh();
|
||||
|
||||
Notifications.info(
|
||||
l10n.t(
|
||||
localize(
|
||||
LocalizationKey.commandsI18nCreateSuccessCreated,
|
||||
sourceLocale.title || sourceLocale.locale
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the process of creating a new translation file if it doesn't exist,
|
||||
* or opening an existing translation file if it's already present.
|
||||
* @param filePath The path of the file where the new content file should be created or being switched to. Behaves like `create` if not provided.
|
||||
*/
|
||||
private static async createOrOpen(fileUri?: Uri | string) {
|
||||
if (!fileUri) {
|
||||
const filePath = ArticleHelper.getActiveFile();
|
||||
fileUri = filePath ? Uri.file(filePath) : undefined;
|
||||
}
|
||||
|
||||
if (!fileUri) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof fileUri === 'string') {
|
||||
fileUri = Uri.file(fileUri);
|
||||
}
|
||||
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
if (!pageFolder || !pageFolder.localeSourcePath) {
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
return;
|
||||
}
|
||||
|
||||
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
|
||||
if (!article) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
}
|
||||
|
||||
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
|
||||
if (!i18nSettings) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
|
||||
if (!sourceLocale || !sourceLocale.locale) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine translation file paths
|
||||
const fileInfo = parse(fileUri.fsPath);
|
||||
let pageBundleDir = '';
|
||||
if (await ArticleHelper.isPageBundle(fileUri.fsPath)) {
|
||||
const dir = ArticleHelper.getPageFolderFromBundlePath(fileUri.fsPath);
|
||||
pageBundleDir = fileUri.fsPath.replace(dir, '');
|
||||
pageBundleDir = join(parse(pageBundleDir).dir);
|
||||
}
|
||||
|
||||
// Gather target locales & metadata
|
||||
const translations = (await i18n.getTranslations(fileUri.fsPath)) || {};
|
||||
const targetLocales = i18nSettings
|
||||
.filter((i18n) => {
|
||||
return i18n.path && i18n.locale !== sourceLocale.locale;
|
||||
})
|
||||
.map((i18n) => {
|
||||
return {
|
||||
...i18n,
|
||||
dir: join(pageFolder.localeSourcePath!, i18n.path!, pageBundleDir),
|
||||
absolutePath: join(
|
||||
pageFolder.localeSourcePath!,
|
||||
i18n.path!,
|
||||
pageBundleDir,
|
||||
fileInfo.base
|
||||
),
|
||||
relativePath: join(i18n.path!, pageBundleDir, fileInfo.base)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => (a.title || a.locale).localeCompare(b.title || b.locale));
|
||||
|
||||
if (targetLocales.length === 0) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure quick pick items & options
|
||||
const existingTargetLocales = targetLocales.filter((i18n) => translations[i18n.locale]);
|
||||
const newTargetLocales = targetLocales.filter((i18n) => !translations[i18n.locale]);
|
||||
const quickPickItems: QuickPickItem[] = [
|
||||
...(existingTargetLocales.length
|
||||
? [
|
||||
{
|
||||
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryExisting),
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...existingTargetLocales.map((i18n) => ({
|
||||
label: i18n.title || i18n.locale,
|
||||
detail: localize(
|
||||
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionOpen,
|
||||
i18n.relativePath
|
||||
)
|
||||
}))
|
||||
]
|
||||
: []),
|
||||
...(newTargetLocales.length
|
||||
? [
|
||||
{
|
||||
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryNew),
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...newTargetLocales.map((i18n) => ({
|
||||
label: i18n.title || i18n.locale,
|
||||
detail: `$(file-add) ${localize(
|
||||
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionCreate,
|
||||
i18n.relativePath
|
||||
)}`
|
||||
}))
|
||||
]
|
||||
: [])
|
||||
];
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
title: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickTitle),
|
||||
ignoreFocusOut: true,
|
||||
matchOnDetail: true
|
||||
};
|
||||
|
||||
const localeItem = await window.showQuickPick<QuickPickItem>(quickPickItems, quickPickOptions);
|
||||
const locale = localeItem?.label;
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetLocale = targetLocales.find(
|
||||
(i18n) => i18n.title === locale || i18n.locale === locale
|
||||
);
|
||||
if (!targetLocale || !targetLocale.path) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
// If it exists, open the translation file
|
||||
if (await existsAsync(targetLocale.absolutePath)) {
|
||||
await openFileInEditor(targetLocale.absolutePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it doesn't exist, create the new translation file & update front matter
|
||||
if (!(await existsAsync(targetLocale.dir))) {
|
||||
await workspace.fs.createDirectory(Uri.file(targetLocale.dir));
|
||||
}
|
||||
|
||||
article = await i18n.updateFrontMatter(
|
||||
article,
|
||||
fileUri.fsPath,
|
||||
contentType,
|
||||
sourceLocale,
|
||||
targetLocale,
|
||||
targetLocale.dir
|
||||
);
|
||||
if (sourceLocale?.locale) {
|
||||
article = await i18n.translate(article, sourceLocale, targetLocale);
|
||||
}
|
||||
|
||||
const newFileUri = Uri.file(targetLocale.absolutePath);
|
||||
await workspace.fs.writeFile(
|
||||
newFileUri,
|
||||
Buffer.from(ArticleHelper.stringifyFrontMatter(article.content, article.data))
|
||||
);
|
||||
|
||||
await openFileInEditor(targetLocale.absolutePath);
|
||||
|
||||
PagesListener.refresh();
|
||||
|
||||
Notifications.info(
|
||||
localize(
|
||||
LocalizationKey.commandsI18nCreateSuccessCreated,
|
||||
sourceLocale.title || sourceLocale.locale
|
||||
)
|
||||
@@ -403,17 +593,20 @@ export class i18n {
|
||||
sourceLocale: I18nConfig,
|
||||
targetLocale: I18nConfig
|
||||
) {
|
||||
return new Promise<ParsedFrontMatter>(async (resolve) => {
|
||||
await window.withProgress(
|
||||
return new Promise<ParsedFrontMatter>((resolve) => {
|
||||
window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: l10n.t(LocalizationKey.commandsI18nTranslateProgressTitle),
|
||||
title: localize(LocalizationKey.commandsI18nTranslateProgressTitle),
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const title = article.data.title || '';
|
||||
const description = article.data.description || '';
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
const title = article.data[titleField] || '';
|
||||
const description = article.data[descriptionField] || '';
|
||||
const content = article.content || '';
|
||||
|
||||
const text = [title, description, content];
|
||||
@@ -428,8 +621,8 @@ export class i18n {
|
||||
return;
|
||||
}
|
||||
|
||||
article.data.title = article.data.title ? translations[0] : '';
|
||||
article.data.description = article.data.description ? translations[1] : '';
|
||||
article.data[titleField] = article.data[titleField] ? translations[0] : '';
|
||||
article.data[descriptionField] = article.data[descriptionField] ? translations[1] : '';
|
||||
article.content = article.content ? translations[2] : '';
|
||||
} catch (error) {
|
||||
Notifications.error(`${(error as Error).message}`);
|
||||
|
||||
@@ -10,5 +10,7 @@ export * from './Preview';
|
||||
export * from './Project';
|
||||
export * from './Settings';
|
||||
export * from './StatusListener';
|
||||
export * from './Taxonomy';
|
||||
export * from './Template';
|
||||
export * from './Wysiwyg';
|
||||
export * from './i18n';
|
||||
|
||||
26
src/components/common/Tooltip.tsx
Normal file
26
src/components/common/Tooltip.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { Tooltip as TT } from 'react-tooltip'
|
||||
|
||||
export interface ITooltipProps {
|
||||
id: string;
|
||||
render?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
export const Tooltip: React.FunctionComponent<ITooltipProps> = ({
|
||||
id,
|
||||
render
|
||||
}: React.PropsWithChildren<ITooltipProps>) => {
|
||||
|
||||
const tooltipClasses = `!py-[2px] !px-[8px] !rounded-[3px] !border-[var(--vscode-editorHoverWidget-border)] !border !border-solid !bg-[var(--vscode-editorHoverWidget-background)] !text-[var(--vscode-editorHoverWidget-foreground)] !font-normal !opacity-100 shadow-[0_2px_8px_var(--vscode-widget-shadow)] text-left`;
|
||||
|
||||
return (
|
||||
<TT
|
||||
id={id}
|
||||
className={tooltipClasses}
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '19px'
|
||||
}}
|
||||
render={render} />
|
||||
);
|
||||
};
|
||||
@@ -30,6 +30,7 @@ function Num({
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<input
|
||||
className='block w-full py-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'
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
max={max}
|
||||
|
||||
@@ -42,6 +42,7 @@ function Select({
|
||||
...props
|
||||
}: SelectFieldProps) {
|
||||
const multiple = fieldType === Array;
|
||||
|
||||
return (
|
||||
<div className="autoform__select_field" {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={required} />
|
||||
@@ -84,11 +85,12 @@ function Select({
|
||||
}}
|
||||
ref={inputRef}
|
||||
value={value ?? ''}
|
||||
className='text-[var(--vscode-foreground)] bg-[var(--vscode-list-activeSelectionBackground)] rounded-[2px] active:border-transparent disabled:opacity-40 disabled:cursor-not-allowed focus:outline-none'
|
||||
style={{ width: '100%', padding: '0.5rem' }}
|
||||
>
|
||||
{(!!placeholder || !required || value === undefined) && !multiple && (
|
||||
{(!required || value === undefined) && !multiple && (
|
||||
<option value="" disabled={required} hidden={required}>
|
||||
{placeholder || label}
|
||||
{""}
|
||||
</option>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,5 +3,8 @@ export const DefaultFields = {
|
||||
LastModified: `lastmod`,
|
||||
Description: `description`,
|
||||
Title: `title`,
|
||||
Slug: `slug`
|
||||
Slug: `slug`,
|
||||
Type: `type`,
|
||||
ContentType: `fmContentType`,
|
||||
Keywords: `keywords`
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const extensionName = 'frontMatter';
|
||||
export const EXTENSION_COMMAND_PREFIX = 'frontMatter';
|
||||
|
||||
export const EXTENSION_ID = 'eliostruyf.vscode-front-matter';
|
||||
export const EXTENSION_BETA_ID = 'eliostruyf.vscode-front-matter-beta';
|
||||
|
||||
export const getCommandName = (command: string) => {
|
||||
return `${extensionName}.${command}`;
|
||||
return `${EXTENSION_COMMAND_PREFIX}.${command}`;
|
||||
};
|
||||
|
||||
export const COMMAND_NAME = {
|
||||
@@ -70,7 +70,8 @@ export const COMMAND_NAME = {
|
||||
|
||||
// i18n
|
||||
i18n: {
|
||||
create: getCommandName('i18n.create')
|
||||
create: getCommandName('i18n.create'),
|
||||
createOrOpen: getCommandName('i18n.createOrOpen')
|
||||
},
|
||||
|
||||
// Project
|
||||
|
||||
@@ -4,10 +4,10 @@ export const FEATURE_FLAG = {
|
||||
seo: 'panel.seo',
|
||||
actions: 'panel.actions',
|
||||
metadata: 'panel.metadata',
|
||||
recentlyModified: 'panel.recentlyModified',
|
||||
otherActions: 'panel.otherActions',
|
||||
contentType: 'panel.contentType',
|
||||
gitActions: 'panel.gitActions'
|
||||
gitActions: 'panel.gitActions',
|
||||
recentlyModified: 'panel.recentlyModified',
|
||||
otherActions: 'panel.otherActions'
|
||||
},
|
||||
dashboard: {
|
||||
snippets: {
|
||||
|
||||
@@ -14,8 +14,20 @@ export const DOCS_SUBMODULES = 'https://frontmatter.codes/docs/git-integration#g
|
||||
export const WEBSITE_LINKS = {
|
||||
root: 'https://frontmatter.codes',
|
||||
api: {
|
||||
metrics: 'https://frontmatter.codes/api/metrics',
|
||||
ai: 'https://frontmatter.codes/api/ai'
|
||||
baseUrl: 'https://api.frontmatter.codes',
|
||||
endpoints: {
|
||||
ai: {
|
||||
description: '/ai/description',
|
||||
taxonomy: '/ai/taxonomy',
|
||||
title: '/ai/title'
|
||||
},
|
||||
chat: {
|
||||
init: '/ai-init',
|
||||
message: '/ai-chat',
|
||||
feedback: '/ai-feedback'
|
||||
},
|
||||
backers: '/v2/backers'
|
||||
}
|
||||
},
|
||||
docs: {
|
||||
dataDashboard: 'https://frontmatter.codes/docs/dashboard/datafiles-view',
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
export const TelemetryEvent = {
|
||||
activate: 'activate',
|
||||
initialization: 'initialization',
|
||||
registerFolder: 'registerFolder',
|
||||
unregisterFolder: 'unregisterFolder',
|
||||
promoteSettings: 'promoteSettings',
|
||||
|
||||
// Commands
|
||||
openContentDashboard: 'openContentDashboard',
|
||||
openMediaDashboard: 'openMediaDashboard',
|
||||
openDataDashboard: 'openDataDashboard',
|
||||
openSnippetsDashboard: 'openSnippetsDashboard',
|
||||
openTaxonomyDashboard: 'openTaxonomyDashboard',
|
||||
closeDashboard: 'closeDashboard',
|
||||
|
||||
// Other actions
|
||||
generateSlug: 'generateSlug',
|
||||
createContentFromTemplate: 'createContentFromTemplate',
|
||||
createContentFromContentType: 'createContentFromContentType',
|
||||
addMediaFolder: 'addMediaFolder',
|
||||
openPreview: 'openPreview',
|
||||
uploadMedia: 'uploadMedia',
|
||||
refreshMedia: 'refreshMedia',
|
||||
deleteMedia: 'deleteMedia',
|
||||
insertContentSnippet: 'insertContentSnippet',
|
||||
insertMediaToContent: 'insertMediaToContent',
|
||||
insertFileToContent: 'insertFileToContent',
|
||||
updateMediaMetadata: 'updateMediaMetadata',
|
||||
openPanelWebview: 'openPanelWebview',
|
||||
|
||||
// Chatbot
|
||||
openChatbot: 'openChatbot',
|
||||
|
||||
// Content types
|
||||
generateContentType: 'generateContentType',
|
||||
addMissingFields: 'addMissingFields',
|
||||
setContentType: 'setContentType',
|
||||
|
||||
// Custom scripts
|
||||
runCustomScript: 'runCustomScript',
|
||||
runMediaScript: 'runMediaScript',
|
||||
|
||||
// Webviews
|
||||
webviewWelcomeScreen: 'webviewWelcomeScreen',
|
||||
webviewMediaView: 'webviewMediaView',
|
||||
webviewDataView: 'webviewDataView',
|
||||
webviewContentsView: 'webviewContentsView',
|
||||
webviewSnippetsView: 'webviewSnippetsView',
|
||||
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
|
||||
webviewSettings: 'webviewSettings',
|
||||
webviewUnknown: 'webviewUnknown',
|
||||
|
||||
// Git
|
||||
gitSync: 'gitSync',
|
||||
gitFetch: 'gitFetch'
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './ContentType';
|
||||
export * from './DefaultFeatureFlags';
|
||||
export * from './DefaultFieldValues';
|
||||
export * from './DefaultFields';
|
||||
export * from './DefaultFileTypes';
|
||||
@@ -17,7 +18,6 @@ export * from './SentryIgnore';
|
||||
export * from './Snippet';
|
||||
export * from './SsgScripts';
|
||||
export * from './StaticFolderPlaceholder';
|
||||
export * from './TelemetryEvent';
|
||||
export * from './Templates';
|
||||
export * from './charCode';
|
||||
export * from './charMap';
|
||||
|
||||
@@ -13,6 +13,7 @@ export const SETTING_GLOBAL_NOTIFICATIONS = 'global.notifications';
|
||||
export const SETTING_GLOBAL_NOTIFICATIONS_DISABLED = 'global.disabledNotifications';
|
||||
export const SETTING_GLOBAL_MODES = 'global.modes';
|
||||
export const SETTING_GLOBAL_ACTIVE_MODE = 'global.activeMode';
|
||||
export const SETTING_GLOBAL_TIMEZONE = 'global.timezone';
|
||||
|
||||
export const SETTING_TAXONOMY_TAGS = 'taxonomy.tags';
|
||||
export const SETTING_TAXONOMY_CATEGORIES = 'taxonomy.categories';
|
||||
@@ -45,8 +46,7 @@ export const SETTING_TEMPLATES_FOLDER = 'templates.folder';
|
||||
export const SETTING_TEMPLATES_PREFIX = 'templates.prefix';
|
||||
export const SETTING_TEMPLATES_ENABLED = 'templates.enabled';
|
||||
|
||||
export const SETTING_TELEMETRY_DISABLE = 'telemetry.disable';
|
||||
|
||||
export const SETTING_PANEL_OPEN_ON_SUPPORTED_FILE = 'panel.openOnSupportedFile';
|
||||
export const SETTING_PANEL_FREEFORM = 'panel.freeform';
|
||||
export const SETTING_PANEL_ACTIONS_DISABLED = 'panel.actions.disabled';
|
||||
|
||||
@@ -63,6 +63,7 @@ export const SETTING_CONTENT_STATIC_FOLDER = 'content.publicFolder';
|
||||
export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = 'content.fmHighlight';
|
||||
export const SETTING_CONTENT_DRAFT_FIELD = 'content.draftField';
|
||||
export const SETTING_CONTENT_SORTING = 'content.sorting';
|
||||
export const SETTING_CONTENT_GROUPING = 'content.grouping';
|
||||
export const SETTING_CONTENT_FILTERS = 'content.filters';
|
||||
export const SETTING_CONTENT_WYSIWYG = 'content.wysiwyg';
|
||||
export const SETTING_CONTENT_PLACEHOLDERS = 'content.placeholders';
|
||||
@@ -115,6 +116,8 @@ export const SETTING_SNIPPETS_WRAPPER = 'snippets.wrapper.enabled';
|
||||
|
||||
export const SETTING_WEBSITE_URL = 'website.host';
|
||||
|
||||
export const SETTING_COPILOT_FAMILY = 'copilot.family';
|
||||
|
||||
export const SETTING_LOGGING = 'logging';
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,8 @@ export enum DashboardMessage {
|
||||
insertMedia = 'insertMedia',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
updateMediaFolder = 'updateMediaFolder',
|
||||
deleteMediaFolder = 'deleteMediaFolder',
|
||||
insertFile = 'insertFile',
|
||||
createHexoAssetFolder = 'createHexoAssetFolder',
|
||||
getUnmappedMedia = 'getUnmappedMedia',
|
||||
@@ -50,6 +52,7 @@ export enum DashboardMessage {
|
||||
// Data dashboard
|
||||
getDataEntries = 'getDataEntries',
|
||||
putDataEntries = 'putDataEntries',
|
||||
createDataFile = 'createDataFile',
|
||||
|
||||
// Snippets dashboard
|
||||
insertSnippet = 'insertSnippet',
|
||||
@@ -74,7 +77,6 @@ export enum DashboardMessage {
|
||||
setState = 'setState',
|
||||
getState = 'getState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
showNotification = 'showNotification',
|
||||
setTitle = 'setTitle',
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view);
|
||||
}, [mode?.features]);
|
||||
|
||||
const checkDevMode = (retry: number = 0) => {
|
||||
const checkDevMode = (retry = 0) => {
|
||||
if (!window.fmExternal) {
|
||||
if (retry < 5) {
|
||||
setTimeout(() => checkDevMode(retry + 1), 150);
|
||||
|
||||
@@ -10,11 +10,12 @@ import { AiInitResponse } from './models/AiInitResponse';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
|
||||
import { l10nJsonFormat } from '@vscode/l10n';
|
||||
|
||||
export interface IChatbotProps { }
|
||||
|
||||
export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.PropsWithChildren<IChatbotProps>) => {
|
||||
export const Chatbot: React.FunctionComponent<IChatbotProps> = () => {
|
||||
const { aiUrl } = useSettingsContext();
|
||||
const [company, setCompany] = React.useState<string | undefined>(undefined);
|
||||
const [chatId, setChatId] = React.useState<number | undefined>(undefined);
|
||||
@@ -27,7 +28,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
|
||||
const init = async () => {
|
||||
setLoading(true);
|
||||
messageHandler.request<any>(GeneralCommands.toVSCode.getLocalization).then((data) => {
|
||||
messageHandler.request<l10nJsonFormat>(GeneralCommands.toVSCode.getLocalization).then((data) => {
|
||||
if (data) {
|
||||
l10n.config({
|
||||
contents: data
|
||||
@@ -36,7 +37,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
setLocaleReady(true);
|
||||
});
|
||||
|
||||
const initResponse = await fetch(`${aiUrl}/api/ai-init`);
|
||||
const initResponse = await fetch(`${aiUrl}${WEBSITE_LINKS.api.endpoints.chat.init}`);
|
||||
|
||||
if (!initResponse.ok) {
|
||||
return;
|
||||
@@ -70,7 +71,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${aiUrl}/api/ai-chat`, {
|
||||
const response = await fetch(`${aiUrl}${WEBSITE_LINKS.api.endpoints.chat.message}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
|
||||
import { HandThumbDownIcon as ThumbDownSolidIcon, HandThumbUpIcon as ThumbUpSolidIcon } from '@heroicons/react/24/solid';
|
||||
import { useCallback } from 'react';
|
||||
import { useSettingsContext } from '../../providers/SettingsProvider';
|
||||
import { WEBSITE_LINKS } from '../../../constants';
|
||||
|
||||
export interface IFeedbackProps {
|
||||
answerId: number;
|
||||
@@ -28,7 +29,7 @@ export const Feedback: React.FunctionComponent<IFeedbackProps> = ({
|
||||
}, []);
|
||||
|
||||
const callVote = useCallback(async (vote: boolean) => {
|
||||
await fetch(`${aiUrl}/api/ai-feedback`, {
|
||||
await fetch(`${aiUrl}${WEBSITE_LINKS.api.endpoints.chat.feedback}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IButtonProps {
|
||||
secondary?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const Button: React.FunctionComponent<IButtonProps> = ({
|
||||
onClick,
|
||||
className,
|
||||
disabled,
|
||||
secondary,
|
||||
children
|
||||
}: React.PropsWithChildren<IButtonProps>) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`${className || ''
|
||||
} inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium focus:outline-none rounded disabled:opacity-50 ${secondary ?
|
||||
`bg-[var(--vscode-button-secondaryBackground)] text-[--vscode-button-secondaryForeground] hover:bg-[var(--vscode-button-secondaryHoverBackground)]` :
|
||||
`bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)] hover:bg-[var(--frontmatter-button-hoverBackground)]`
|
||||
}
|
||||
`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemSelectionProps {
|
||||
@@ -24,11 +24,11 @@ export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
|
||||
return (
|
||||
<div className={`${cssNames} group-hover:block`}>
|
||||
<VSCodeCheckbox
|
||||
style={{
|
||||
boxShadow: show ? "" : "0 0 3px var(--frontmatter-border-preserve)"
|
||||
}}
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
className={show ? "" : " shadow-[0_0_3px_var(--frontmatter-border-preserve)]"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onChange={() => {
|
||||
onMultiSelect(filePath);
|
||||
}}
|
||||
checked={selectedFiles.includes(filePath)} />
|
||||
|
||||
@@ -10,7 +10,7 @@ import usePages from '../../hooks/usePages';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { GeneralCommands, TelemetryEvent } from '../../../constants';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
@@ -67,10 +67,6 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
}, [JSON.stringify(pageItems)]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewContentsView
|
||||
});
|
||||
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsContents));
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
date: pageData.date,
|
||||
title: pageData.title,
|
||||
description: pageData.description,
|
||||
type: pageData.type,
|
||||
type: pageData.fmContentType,
|
||||
pageData
|
||||
});
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { groupBy } from '../../../helpers/GroupBy';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import { GroupingSelector, PageAtom, ViewSelector } from '../../state';
|
||||
import { GroupingSelector, PageAtom, PagedItems, ViewSelector } from '../../state';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -34,6 +33,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
const page = useRecoilValue(PageAtom);
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const [, setPagedItems] = useRecoilState(PagedItems);
|
||||
|
||||
const pagedPages = useMemo(() => {
|
||||
if (pageSetNr) {
|
||||
@@ -53,13 +53,18 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
|
||||
const groupName = useCallback(
|
||||
(groupId, groupedPages) => {
|
||||
const count = groupedPages[groupId].length;
|
||||
if (grouping === GroupOption.Draft) {
|
||||
return `${groupId} (${groupedPages[groupId].length})`;
|
||||
return `${groupId} (${count})`;
|
||||
} else if (typeof grouping === 'string') {
|
||||
const group = settings?.grouping?.find((g) => g.name === grouping);
|
||||
const prefix = group?.title ? `${group.title}: ` : '';
|
||||
return `${prefix}${groupId} (${count})`;
|
||||
}
|
||||
|
||||
return `${GroupOption[grouping]}: ${groupId} (${groupedPages[groupId].length})`;
|
||||
return `${GroupOption[grouping]}: ${groupId} (${count})`;
|
||||
},
|
||||
[grouping]
|
||||
[grouping, settings?.grouping]
|
||||
);
|
||||
|
||||
const { groupKeys, groupedPages } = useMemo(() => {
|
||||
@@ -67,7 +72,18 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
return { groupKeys: [], groupedPages: {} };
|
||||
}
|
||||
|
||||
let groupedPages = groupBy(pages, grouping === GroupOption.Year ? 'fmYear' : 'fmDraft');
|
||||
let groupName: string | undefined;
|
||||
if (grouping === GroupOption.Year) {
|
||||
groupName = 'fmYear';
|
||||
} else if (grouping === GroupOption.Draft) {
|
||||
groupName = 'fmDraft';
|
||||
} else if (typeof grouping === 'string') {
|
||||
groupName = grouping;
|
||||
} else {
|
||||
return { groupKeys: [], groupedPages: {} };
|
||||
}
|
||||
|
||||
let groupedPages = groupBy(pages, groupName);
|
||||
let groupKeys = Object.keys(groupedPages);
|
||||
|
||||
if (grouping === GroupOption.Year) {
|
||||
@@ -95,11 +111,17 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
...groupedPages,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupKeys = groupKeys.sort();
|
||||
}
|
||||
|
||||
return { groupKeys, groupedPages };
|
||||
}, [pages, grouping, settings?.draftField]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setPagedItems(pagedPages.map((page) => page.fmFilePath));
|
||||
}, [pagedPages]);
|
||||
|
||||
React.useEffect(() => {
|
||||
messageHandler.request<string[]>(DashboardMessage.getPinnedItems).then((items) => {
|
||||
setIsReady(true);
|
||||
@@ -122,9 +144,11 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
|
||||
/>
|
||||
{settings && settings?.contentFolders?.length > 0 ? (
|
||||
<p className={`text-xl font-medium`}>{l10n.t(LocalizationKey.dashboardContentsOverviewNoMarkdown)}</p>
|
||||
<p className={`text-xl font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoMarkdown)}</p>
|
||||
|
||||
) : (
|
||||
<p className={`text-lg font-medium`}>{l10n.t(LocalizationKey.dashboardContentsOverviewNoFolders)}</p>
|
||||
<p className={`text-lg font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoFolders)}</p>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,7 +195,8 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
<div className='mb-8'>
|
||||
<h1 className='text-xl flex space-x-2 items-center mb-4'>
|
||||
<PinIcon className={`-rotate-45`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsOverviewPinned)}</span>
|
||||
<span>{localize(LocalizationKey.dashboardContentsOverviewPinned)}</span>
|
||||
|
||||
</h1>
|
||||
<List>
|
||||
{pinnedPages.map((page, idx) => (
|
||||
|
||||
@@ -53,7 +53,7 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
};
|
||||
} catch (error) {
|
||||
setError((error as Error).message);
|
||||
return () => { };
|
||||
return () => void 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { useForm } from 'uniforms';
|
||||
import { Button } from '../Common/Button';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { SubmitField } from '../../../components/uniforms-frontmatter';
|
||||
import { Button } from 'vscrui';
|
||||
|
||||
export interface IDataFormControlsProps {
|
||||
model: any | null;
|
||||
@@ -21,8 +21,8 @@ export const DataFormControls: React.FunctionComponent<IDataFormControlsProps> =
|
||||
<SubmitField value={model ? `Update` : `Add`} />
|
||||
|
||||
<Button
|
||||
className="ml-4"
|
||||
secondary
|
||||
className="ml-4 !py-2"
|
||||
appearance="secondary"
|
||||
onClick={() => {
|
||||
if (onClear) {
|
||||
onClear();
|
||||
|
||||
@@ -5,34 +5,36 @@ import { SettingsSelector } from '../../state';
|
||||
import { DataForm } from './DataForm';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { DataFile } from '../../../models/DataFile';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { messageHandler, Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SponsorMsg } from '../Layout/SponsorMsg';
|
||||
import { EventData } from '@estruyf/vscode';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { Button } from '../Common/Button';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
import { EmptyView } from './EmptyView';
|
||||
import { Container } from './SortableContainer';
|
||||
import { SortableItem } from './SortableItem';
|
||||
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
|
||||
import { ChevronRightIcon, CircleStackIcon, EyeIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { DataType } from '../../../models/DataType';
|
||||
import { GeneralCommands, TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
|
||||
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
|
||||
import { NavigationItem } from '../Layout';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { DataFolder } from '../../../models';
|
||||
import { ActionsBarItem } from '../Header/ActionsBarItem';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
import { openFile } from '../../utils/MessageHandlers';
|
||||
import { Button } from 'vscrui';
|
||||
|
||||
export interface IDataViewProps { }
|
||||
|
||||
export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
_: React.PropsWithChildren<IDataViewProps>
|
||||
) => {
|
||||
export const DataView: React.FunctionComponent<IDataViewProps> = () => {
|
||||
const [selectedData, setSelectedData] = useState<DataFile | null>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
const [dataEntries, setDataEntries] = useState<any | any[] | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const setSchema = (dataFile: DataFile) => {
|
||||
@@ -64,14 +66,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(data: any) => {
|
||||
(data: unknown) => {
|
||||
if (selectedData?.singleEntry) {
|
||||
// Needs to add a single entry
|
||||
updateData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataClone: any[] = Object.assign([], dataEntries);
|
||||
const dataClone: unknown[] = Object.assign([], dataEntries);
|
||||
if (selectedIndex !== null && selectedIndex !== undefined) {
|
||||
dataClone[selectedIndex] = data;
|
||||
} else {
|
||||
@@ -110,11 +112,23 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
entries: data
|
||||
});
|
||||
|
||||
Messenger.send(DashboardMessage.showNotification, l10n.t(LocalizationKey.dashboardDataViewDataViewUpdateMessage));
|
||||
Messenger.send(DashboardMessage.showNotification, localize(LocalizationKey.dashboardDataViewDataViewUpdateMessage));
|
||||
},
|
||||
[selectedData]
|
||||
);
|
||||
|
||||
const createDataFile = (folder: DataFolder) => {
|
||||
setLoading(true);
|
||||
messageHandler.request<DataFile>(DashboardMessage.createDataFile, folder).then(dataFile => {
|
||||
if (dataFile) {
|
||||
setSchema(dataFile);
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch((_: any) => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
const dataEntry = useMemo(() => {
|
||||
if (selectedData?.singleEntry) {
|
||||
return dataEntries || {};
|
||||
@@ -123,16 +137,44 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
return dataEntries && selectedIndex !== null && selectedIndex !== undefined
|
||||
? dataEntries[selectedIndex]
|
||||
: null;
|
||||
}, [selectedData, , dataEntries, selectedIndex]);
|
||||
}, [selectedData, dataEntries, selectedIndex]);
|
||||
|
||||
// Retrieve the data files, check if they have a schema or ID, if not, they shouldn't be shown
|
||||
const dataFiles = useMemo(() => {
|
||||
return (settings?.dataFiles || [])
|
||||
.map((dataFile: DataFile) => {
|
||||
if (!dataFile.schema && !dataFile.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clonedFile = Object.assign({}, dataFile);
|
||||
|
||||
if (clonedFile.type) {
|
||||
const dataType = settings?.dataTypes?.find(
|
||||
(dataType: DataType) => dataType.id === clonedFile.type
|
||||
);
|
||||
if (!dataType) {
|
||||
return null;
|
||||
}
|
||||
clonedFile.schema = Object.assign({}, dataType.schema);
|
||||
}
|
||||
|
||||
return clonedFile;
|
||||
})
|
||||
.filter((d) => d !== null) as DataFile[];
|
||||
}, [settings?.dataFiles]);
|
||||
|
||||
const fileCreationFolders = useMemo(() => {
|
||||
return (settings?.dataFolders || [])
|
||||
.filter((folder) => folder.enableFileCreation);
|
||||
}, [settings?.dataFolders]);
|
||||
|
||||
const hasOnlyDataCreationFolders = useMemo(() => (!dataFiles || dataFiles.length === 0) && (fileCreationFolders && fileCreationFolders.length > 0), [dataFiles, fileCreationFolders]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsData));
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewDataView
|
||||
});
|
||||
Messenger.send(DashboardMessage.setTitle, localize(LocalizationKey.dashboardHeaderTabsData));
|
||||
|
||||
Messenger.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: 'Data view loaded',
|
||||
@@ -144,49 +186,27 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Retrieve the data files, check if they have a schema or ID, if not, they shouldn't be shown
|
||||
const dataFiles = (settings?.dataFiles || [])
|
||||
.map((dataFile: DataFile) => {
|
||||
if (!dataFile.schema && !dataFile.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clonedFile = Object.assign({}, dataFile);
|
||||
|
||||
if (clonedFile.type) {
|
||||
const dataType = settings?.dataTypes?.find(
|
||||
(dataType: DataType) => dataType.id === clonedFile.type
|
||||
);
|
||||
if (!dataType) {
|
||||
return null;
|
||||
}
|
||||
clonedFile.schema = Object.assign({}, dataType.schema);
|
||||
}
|
||||
|
||||
return clonedFile;
|
||||
})
|
||||
.filter((d) => d !== null) as DataFile[];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto inset-y-0">
|
||||
<Header settings={settings} />
|
||||
|
||||
{dataFiles && dataFiles.length > 0 ? (
|
||||
{(dataFiles && dataFiles.length > 0) || (fileCreationFolders && fileCreationFolders.length > 0) ? (
|
||||
<div className={`relative w-full flex-grow mx-auto overflow-hidden`}>
|
||||
{
|
||||
!selectedData && (
|
||||
!selectedData && (dataFiles && dataFiles.length > 0) && (
|
||||
<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)}
|
||||
{localize(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 &&
|
||||
dataFiles.map((dataFile, idx) => (
|
||||
@@ -212,30 +232,82 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
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}
|
||||
/>
|
||||
<div className={`flex justify-between`}>
|
||||
<div className={`flex gap-4`}>
|
||||
{
|
||||
selectedData && (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(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>
|
||||
)}
|
||||
<DropdownMenuContent>
|
||||
{dataFiles.map((dataFile) => (
|
||||
<MenuItem
|
||||
key={dataFile.id}
|
||||
title={dataFile.title}
|
||||
value={dataFile}
|
||||
isCurrent={selectedData.id === dataFile.id}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
fileCreationFolders && fileCreationFolders.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(LocalizationKey.dashboardDataViewDataViewCreateNew)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewSelectDataFolder)}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{fileCreationFolders.map((folder) => (
|
||||
<MenuItem
|
||||
key={folder.id}
|
||||
title={folder.id}
|
||||
value={folder}
|
||||
onClick={() => createDataFile(folder)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedData && (
|
||||
<div className={`flex gap-2`}>
|
||||
<ActionsBarItem
|
||||
className='flex items-center'
|
||||
onClick={() => openFile(selectedData.file)}
|
||||
title={localize(LocalizationKey.commonView)}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{localize(LocalizationKey.commonView)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
<ActionsBarItem
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedData(null)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewCloseSelectedDataFile)}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{localize(LocalizationKey.dashboardDataViewDataViewCloseSelectedDataFile)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : "pl-64"}`}>
|
||||
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : hasOnlyDataCreationFolders ? "" : "pl-64"}`}>
|
||||
{selectedData ? (
|
||||
<>
|
||||
{!selectedData.singleEntry && (
|
||||
@@ -243,7 +315,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
className={`w-1/3 py-6 px-4 flex-1 border-r overflow-auto border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewTitle, selectedData?.title?.toLowerCase() || '')}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewTitle, selectedData?.title?.toLowerCase() || '')}
|
||||
</h2>
|
||||
|
||||
<div className="py-4">
|
||||
@@ -262,14 +334,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
<Button className="mt-4" onClick={() => setSelectedIndex(null)}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewAdd)}
|
||||
<Button className="mt-4 !py-2" onClick={() => setSelectedIndex(null)}>
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewAdd)}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className={`flex flex-col items-center justify-center`}>
|
||||
<p className={`text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewEmpty, selectedData.title.toLowerCase())}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewEmpty, selectedData.title.toLowerCase())}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -281,7 +353,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
} py-6 px-4 overflow-auto`}
|
||||
>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewCreateOrModify, selectedData.title.toLowerCase())}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewCreateOrModify, selectedData.title.toLowerCase())}
|
||||
</h2>
|
||||
{selectedData ? (
|
||||
<DataForm
|
||||
@@ -291,12 +363,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
onClear={() => setSelectedIndex(null)}
|
||||
/>
|
||||
) : (
|
||||
<p>{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStarted)}</p>
|
||||
<p>{localize(LocalizationKey.dashboardDataViewDataViewGetStarted)}</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<EmptyView />
|
||||
<EmptyView
|
||||
folders={fileCreationFolders}
|
||||
onCreate={createDataFile} />
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
@@ -304,14 +378,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
|
||||
<CircleStackIcon className="w-32 h-32" />
|
||||
<p className="text-3xl mt-2">{l10n.t(LocalizationKey.dashboardDataViewDataViewNoDataFiles)}</p>
|
||||
<p className="text-3xl mt-2">{localize(LocalizationKey.dashboardDataViewDataViewNoDataFiles)}</p>
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
|
||||
href={WEBSITE_LINKS.docs.dataDashboard}
|
||||
title={l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -319,6 +393,8 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
)
|
||||
}
|
||||
|
||||
{loading && <Spinner />}
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
|
||||
@@ -1,20 +1,56 @@
|
||||
import { ExclamationCircleIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DataFolder } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
|
||||
export interface IEmptyViewProps { }
|
||||
export interface IEmptyViewProps {
|
||||
folders: DataFolder[];
|
||||
onCreate: (folder: DataFolder) => void;
|
||||
}
|
||||
|
||||
export const EmptyView: React.FunctionComponent<IEmptyViewProps> = (
|
||||
props: React.PropsWithChildren<IEmptyViewProps>
|
||||
{ folders, onCreate }: React.PropsWithChildren<IEmptyViewProps>
|
||||
) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full">
|
||||
<div className="flex flex-col items-center justify-center w-full space-y-2">
|
||||
<ExclamationCircleIcon className={`w-1/12 opacity-90 text-[var(--frontmatter-secondary-text)]`} />
|
||||
<h2 className={`text-xl text-[var(--frontmatter-secondary-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewEmptyViewHeading)}
|
||||
{
|
||||
(folders && folders.length > 0) ?
|
||||
localize(LocalizationKey.dashboardDataViewEmptyViewHeadingCreate) :
|
||||
l10n.t(LocalizationKey.dashboardDataViewEmptyViewHeading)
|
||||
}
|
||||
</h2>
|
||||
|
||||
{
|
||||
onCreate && folders && folders.length > 0 && (
|
||||
<div className=''>
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(LocalizationKey.dashboardDataViewDataViewCreateNew)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewSelectDataFolder)}
|
||||
className={`text-lg`}
|
||||
labelClass={`font-normal text-[var(--frontmatter-secondary-text)]`}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{folders.map((folder) => (
|
||||
<MenuItem
|
||||
key={folder.id}
|
||||
title={folder.id}
|
||||
value={folder}
|
||||
onClick={() => onCreate(folder)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ILanguageFilterProps { }
|
||||
|
||||
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = ({ }: React.PropsWithChildren<ILanguageFilterProps>) => {
|
||||
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = () => {
|
||||
const locales = useRecoilValue(LocalesAtom);
|
||||
const [crntLocale, setCrntLocale] = useRecoilState(LocaleAtom);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationType, Page } from '../../models';
|
||||
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon, CheckIcon } from '@heroicons/react/24/outline';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { MultiSelectedItemsAtom, PagedItems, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { ActionsBarItem } from './ActionsBarItem';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -29,6 +28,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { files } = useFilesContext();
|
||||
const pagedItems = useRecoilValue(PagedItems);
|
||||
|
||||
const viewFile = React.useCallback(() => {
|
||||
if (selectedFiles.length === 1) {
|
||||
@@ -66,6 +66,16 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const selectAllItems = React.useCallback(() => {
|
||||
const allSelected = [...selectedFiles, ...pagedItems];
|
||||
setSelectedFiles(Array.from(new Set(allSelected)));
|
||||
}, [selectedFiles, pagedItems]);
|
||||
|
||||
const hasAllItemsSelectedOnPage = React.useMemo(() => {
|
||||
const selectedItemsOnPage = selectedFiles.filter((file) => pagedItems.includes(file));
|
||||
return selectedItemsOnPage.length >= pagedItems.length;
|
||||
}, [selectedFiles, pagedItems]);
|
||||
|
||||
const languageActions = React.useMemo(() => {
|
||||
const actions: React.ReactNode[] = [];
|
||||
|
||||
@@ -87,7 +97,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
})
|
||||
}}>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonTranslate)}</span>
|
||||
<span>{localize(LocalizationKey.commonTranslate)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
|
||||
@@ -102,7 +112,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'
|
||||
>
|
||||
<LanguageIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonLanguages)}</span>
|
||||
<span>{localize(LocalizationKey.commonLanguages)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -158,7 +168,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonScripts)}</span>
|
||||
<span>{localize(LocalizationKey.commonScripts)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -192,9 +202,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={viewFile}
|
||||
title={localize(LocalizationKey.commonView)}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonView)}</span>
|
||||
<span>{localize(LocalizationKey.commonView)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
{
|
||||
@@ -205,9 +216,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
messageHandler.send(DashboardMessage.rename, selectedFiles[0]);
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
title={localize(LocalizationKey.commonRename)}
|
||||
>
|
||||
<RenameIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonRename)}</span>
|
||||
<span>{localize(LocalizationKey.commonRename)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
}
|
||||
@@ -221,9 +233,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
path: selectedFiles[0],
|
||||
action: 'edit'
|
||||
})}
|
||||
title={localize(LocalizationKey.commonEdit)}
|
||||
>
|
||||
<PencilIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
<span>{localize(LocalizationKey.commonEdit)}</span>
|
||||
</ActionsBarItem>
|
||||
</>
|
||||
)
|
||||
@@ -237,36 +250,51 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
onClick={() => setShowAlert(true)}
|
||||
title={localize(LocalizationKey.commonDelete)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
<span>{localize(LocalizationKey.commonDelete)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedFiles.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedFiles([])}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='flex gap-4'>
|
||||
{
|
||||
selectedFiles.length > 0 && (
|
||||
<ActionsBarItem
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedFiles([])}
|
||||
title={localize(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{localize(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
}
|
||||
|
||||
<ActionsBarItem
|
||||
disabled={hasAllItemsSelectedOnPage}
|
||||
onClick={selectAllItems}
|
||||
title={localize(LocalizationKey.dashboardHeaderActionsBarSelectAll)}
|
||||
>
|
||||
<div className='w-4 h-4 inline-flex items-center justify-center border border-[var(--vscode-sideBar-foreground)] group-hover:border-[var(--vscode-statusBarItem-warningBackground)] rounded mr-1'>
|
||||
<CheckIcon className="w-3 h-3" aria-hidden="true" />
|
||||
</div>
|
||||
<span>{localize(LocalizationKey.dashboardHeaderActionsBarSelectAll)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
</div >
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteTitle)}`}
|
||||
description={l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteDescription)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
title={`${localize(LocalizationKey.dashboardHeaderActionsBarAlertDeleteTitle)}`}
|
||||
description={localize(LocalizationKey.dashboardHeaderActionsBarAlertDeleteDescription)}
|
||||
okBtnText={localize(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={localize(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
trigger={onDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IActionsBarItemProps {
|
||||
title?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
@@ -11,11 +12,13 @@ export const ActionsBarItem: React.FunctionComponent<IActionsBarItemProps> = ({
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
onClick
|
||||
onClick,
|
||||
title
|
||||
}: React.PropsWithChildren<IActionsBarItemProps>) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title={title || ''}
|
||||
className={cn(`flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]`, className)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
|
||||
38
src/dashboardWebView/components/Header/BooleanOption.tsx
Normal file
38
src/dashboardWebView/components/Header/BooleanOption.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface IBooleanOptionProps {
|
||||
value: boolean | undefined | null;
|
||||
name: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const BooleanOption: React.FunctionComponent<IBooleanOptionProps> = ({
|
||||
value,
|
||||
name,
|
||||
label
|
||||
}: React.PropsWithChildren<IBooleanOptionProps>) => {
|
||||
const [isChecked, setIsChecked] = React.useState(false);
|
||||
|
||||
const onChange = React.useCallback((newValue: boolean) => {
|
||||
setIsChecked(newValue);
|
||||
Messenger.send(DashboardMessage.updateSetting, {
|
||||
name: name,
|
||||
value: newValue
|
||||
});
|
||||
}, [name]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsChecked(!!value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
onChange={onChange}
|
||||
checked={isChecked}>
|
||||
{label}
|
||||
</VSCodeCheckbox>
|
||||
);
|
||||
};
|
||||
@@ -21,16 +21,16 @@ import { useEffect, useMemo } from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export const guardRecoilDefaultValue = (candidate: any): candidate is DefaultValue => {
|
||||
if (candidate instanceof DefaultValue) return true;
|
||||
export const guardRecoilDefaultValue = (candidate: unknown): candidate is DefaultValue => {
|
||||
if (candidate instanceof DefaultValue) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export interface IClearFiltersProps { }
|
||||
|
||||
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
_: React.PropsWithChildren<IClearFiltersProps>
|
||||
) => {
|
||||
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = () => {
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
@@ -75,7 +75,9 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
}
|
||||
}, [folder, tag, category, locale, hasCustomFilters]);
|
||||
|
||||
if (!show) return null;
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LanguageFilter } from '../Filters/LanguageFilter';
|
||||
|
||||
export interface IFiltersProps { }
|
||||
|
||||
export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWithChildren<IFiltersProps>) => {
|
||||
export const Filters: React.FunctionComponent<IFiltersProps> = () => {
|
||||
const [crntFilters, setCrntFilters] = useRecoilState(FiltersAtom);
|
||||
const [crntTag, setCrntTag] = useRecoilState(TagAtom);
|
||||
const [crntCategory, setCrntCategory] = useRecoilState(CategoryAtom);
|
||||
@@ -24,19 +24,37 @@ export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWi
|
||||
return otherFilters?.map((filter) => {
|
||||
const filterName = typeof filter === "string" ? filter : filter.name;
|
||||
const filterTitle = typeof filter === "string" ? firstToUpper(filter) : filter.title;
|
||||
const values = filterValues?.[filterName];
|
||||
let values = filterValues?.[filterName];
|
||||
if (!values || values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get all the unique values
|
||||
const individualValues = new Set<string>();
|
||||
values.forEach((value) => {
|
||||
if (value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => individualValues.add(v));
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
individualValues.add(value);
|
||||
}
|
||||
});
|
||||
|
||||
values = Array.from(individualValues);
|
||||
|
||||
return (
|
||||
<Filter
|
||||
key={filterName}
|
||||
label={filterTitle}
|
||||
activeItem={crntFilters[filterName]}
|
||||
items={values}
|
||||
items={values as string[]}
|
||||
onClick={(value) => setCrntFilters((prev) => {
|
||||
let clone = Object.assign({}, prev);
|
||||
const clone = Object.assign({}, prev);
|
||||
if (!clone[filterName] && value) {
|
||||
clone[filterName] = value;
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface IFoldersFilterProps { }
|
||||
|
||||
export const FoldersFilter: React.FunctionComponent<
|
||||
IFoldersFilterProps
|
||||
> = ({ }: React.PropsWithChildren<IFoldersFilterProps>) => {
|
||||
> = () => {
|
||||
const DEFAULT_TYPE = l10n.t(LocalizationKey.dashboardHeaderFoldersDefault);
|
||||
const [crntFolder, setCrntFolder] = useRecoilState(FolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import { AllPagesAtom, GroupingAtom } from '../../state';
|
||||
import { AllPagesAtom, GroupingAtom, SettingsAtom } from '../../state';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IGroupingProps { }
|
||||
|
||||
export const Grouping: React.FunctionComponent<
|
||||
IGroupingProps
|
||||
> = ({ }: React.PropsWithChildren<IGroupingProps>) => {
|
||||
> = () => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const [group, setGroup] = useRecoilState(GroupingAtom);
|
||||
const pages = useRecoilValue(AllPagesAtom);
|
||||
|
||||
const GROUP_OPTIONS = React.useMemo(() => {
|
||||
let options: { name: string, id: GroupOption }[] = [];
|
||||
const options: { name: string, id?: GroupOption | string }[] = [];
|
||||
|
||||
if (pages.length > 0) {
|
||||
if (settings?.grouping) {
|
||||
const groups = settings.grouping.map((g) => ({ name: g.title, id: g.name }));
|
||||
options.push(...groups);
|
||||
}
|
||||
|
||||
if (pages.some((x) => x.fmYear)) {
|
||||
options.push({ name: l10n.t(LocalizationKey.dashboardHeaderGroupingOptionYear), id: GroupOption.Year })
|
||||
options.push({ name: localize(LocalizationKey.dashboardHeaderGroupingOptionYear), id: GroupOption.Year })
|
||||
}
|
||||
|
||||
if (pages.some((x) => x.fmDraft)) {
|
||||
options.push({ name: l10n.t(LocalizationKey.dashboardHeaderGroupingOptionDraft), id: GroupOption.Draft })
|
||||
options.push({ name: localize(LocalizationKey.dashboardHeaderGroupingOptionDraft), id: GroupOption.Draft })
|
||||
}
|
||||
}
|
||||
|
||||
if (options.length > 0) {
|
||||
options.unshift({ name: l10n.t(LocalizationKey.dashboardHeaderGroupingOptionNone), id: GroupOption.none })
|
||||
options.unshift({ name: localize(LocalizationKey.dashboardHeaderGroupingOptionNone), id: GroupOption.none })
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [pages])
|
||||
}, [pages, settings?.grouping])
|
||||
|
||||
const crntGroup = GROUP_OPTIONS.find((x) => x.id === group);
|
||||
|
||||
@@ -43,7 +48,7 @@ export const Grouping: React.FunctionComponent<
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<MenuButton label={l10n.t(LocalizationKey.dashboardHeaderGroupingMenuButtonLabel)} title={crntGroup?.name || ''} />
|
||||
<MenuButton label={localize(LocalizationKey.dashboardHeaderGroupingMenuButtonLabel)} title={crntGroup?.name || ''} />
|
||||
|
||||
<DropdownMenuContent>
|
||||
{GROUP_OPTIONS.map((option) => (
|
||||
|
||||
@@ -37,9 +37,7 @@ const NavigationItem: React.FunctionComponent<INavigationItemProps> = ({
|
||||
)
|
||||
};
|
||||
|
||||
export const Navigation: React.FunctionComponent<INavigationProps> = ({
|
||||
|
||||
}: React.PropsWithChildren<INavigationProps>) => {
|
||||
export const Navigation: React.FunctionComponent<INavigationProps> = () => {
|
||||
const [crntTab, setCrntTab] = useRecoilState(TabAtom);
|
||||
const tabInfo = useRecoilValue(TabInfoAtom);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
@@ -23,15 +23,28 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
|
||||
totalMedia
|
||||
);
|
||||
|
||||
const getButtons = useCallback((): number[] => {
|
||||
const buttons = useMemo((): JSX.Element[] => {
|
||||
const maxButtons = 5;
|
||||
const buttons: number[] = [];
|
||||
const buttons: JSX.Element[] = [];
|
||||
const start = page - maxButtons;
|
||||
const end = page + maxButtons;
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
if (i >= 0 && i <= totalPagesNr) {
|
||||
buttons.push(i);
|
||||
buttons.push(
|
||||
<button
|
||||
key={i}
|
||||
disabled={i === page}
|
||||
onClick={() => {
|
||||
setPage(i);
|
||||
}}
|
||||
className={`max-h-8 rounded ${page === i
|
||||
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
|
||||
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
@@ -67,20 +80,7 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
{getButtons().map((button) => (
|
||||
<button
|
||||
key={button}
|
||||
disabled={button === page}
|
||||
onClick={() => {
|
||||
setPage(button);
|
||||
}}
|
||||
className={`max-h-8 rounded ${page === button
|
||||
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
|
||||
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
|
||||
>
|
||||
{button + 1}
|
||||
</button>
|
||||
))}
|
||||
{buttons}
|
||||
|
||||
<PaginationButton
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderPaginationNext)}
|
||||
|
||||
@@ -21,9 +21,7 @@ import { ArrowClockwiseIcon } from '../../../components/icons/ArrowClockwiseIcon
|
||||
|
||||
export interface IRefreshDashboardDataProps { }
|
||||
|
||||
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = (
|
||||
{ }: React.PropsWithChildren<IRefreshDashboardDataProps>
|
||||
) => {
|
||||
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = () => {
|
||||
const view = useRecoilValue(DashboardViewAtom);
|
||||
const [, setLoading] = useRecoilState(LoadingAtom);
|
||||
const resetSearch = useResetRecoilState(SearchAtom);
|
||||
|
||||
@@ -165,7 +165,7 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
let sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
|
||||
const sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
|
||||
setCrntSort(sort);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SETTING_DASHBOARD_OPENONSTART } from '../../../constants';
|
||||
import * as l10n from "@vscode/l10n"
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface IStartupProps {
|
||||
settings: Settings | null;
|
||||
@@ -31,7 +30,7 @@ export const Startup: React.FunctionComponent<IStartupProps> = ({
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.checked)}
|
||||
onChange={onChange}
|
||||
checked={isChecked}>
|
||||
{l10n.t(LocalizationKey.dashboardHeaderStartupLabel)}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -33,6 +33,9 @@ export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
if (!customActions.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (showTrigger) {
|
||||
return (
|
||||
|
||||
@@ -91,8 +91,9 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
|
||||
if (scripts.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-1 justify-start">
|
||||
<div className="flex flex-1 justify-start space-x-2">
|
||||
{renderPostAssetsButton}
|
||||
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}
|
||||
choices={scripts.map((s) => ({
|
||||
@@ -103,6 +104,8 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
onClick={onFolderCreation}
|
||||
disabled={!settings?.initialized}
|
||||
/>
|
||||
|
||||
<RefreshDashboardData />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { FolderIcon } from '@heroicons/react/24/solid';
|
||||
import { FolderIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { useState } from 'react';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
|
||||
export interface IFolderItemProps {
|
||||
folder: string;
|
||||
@@ -17,6 +22,7 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
staticFolder
|
||||
}: React.PropsWithChildren<IFolderItemProps>) => {
|
||||
const { updateFolder } = useMediaFolder();
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
|
||||
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
|
||||
|
||||
@@ -25,28 +31,73 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
[relFolderPath, staticFolder]
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
className={`group relative hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}
|
||||
>
|
||||
<button
|
||||
title={isContentFolder ? l10n.t(LocalizationKey.dashboardMediaFolderItemContentDirectory) : l10n.t(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
|
||||
className={`p-4 w-full flex flex-row items-center h-full`}
|
||||
onClick={() => updateFolder(folder)}
|
||||
>
|
||||
<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(--frontmatter-text)]`}>
|
||||
C
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
const updateFolderName = React.useCallback(() => {
|
||||
messageHandler.send(DashboardMessage.updateMediaFolder, { folder, wsFolder, staticFolder })
|
||||
}, []);
|
||||
|
||||
<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
|
||||
{basename(relFolderPath)}
|
||||
</p>
|
||||
</button>
|
||||
</li>
|
||||
const onDelete = React.useCallback(() => {
|
||||
setShowAlert(true);
|
||||
}, []);
|
||||
|
||||
const confirmDeletion = React.useCallback(() => {
|
||||
messageHandler.send(DashboardMessage.deleteMediaFolder, { folder });
|
||||
setShowAlert(false);
|
||||
}, [folder]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<li
|
||||
className={`flex flex-col group relative text-[var(--vscode-sideBarTitle-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)] shadow-md hover:shadow-xl dark:shadow-none bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border border-[var(--frontmatter-border)] rounded`}
|
||||
>
|
||||
<button
|
||||
title={isContentFolder ? localize(LocalizationKey.dashboardMediaFolderItemContentDirectory) : localize(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
|
||||
className={`p-4 w-full flex flex-row items-center h-full`}
|
||||
onClick={() => updateFolder(folder)}
|
||||
>
|
||||
<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(--frontmatter-text)]`}>
|
||||
C
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
|
||||
{basename(relFolderPath)}
|
||||
</p>
|
||||
</button>
|
||||
|
||||
{!isContentFolder && (
|
||||
<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={localize(LocalizationKey.commonEdit)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={updateFolderName}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={localize(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>
|
||||
)}
|
||||
</li>
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${localize(LocalizationKey.commonDelete)}: ${basename(parseWinPath(folder) || '')}`}
|
||||
description={localize(LocalizationKey.dashboardMediaFolderItemDeleteDescription, folder)}
|
||||
okBtnText={localize(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={localize(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
trigger={confirmDeletion}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { ClipboardIcon, CodeBracketIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
@@ -36,25 +35,25 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
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)}
|
||||
title={localize(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>
|
||||
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
|
||||
title={localize(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>
|
||||
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
@@ -62,8 +61,8 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
? localize(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: localize(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={insertIntoArticle}
|
||||
@@ -73,7 +72,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonInsertSnippet)}
|
||||
title={localize(LocalizationKey.commonInsertSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
@@ -85,7 +84,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
|
||||
title={localize(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => copyToClipboard(parseWinPath(relPath) || '')}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
@@ -101,7 +100,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
showTrigger />
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
title={localize(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" />
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PlusIcon,
|
||||
VideoCameraIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { basename } from 'path';
|
||||
import { basename, parse } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
@@ -55,8 +55,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
const { mediaFolder, mediaDetails, isAudio, isImage, isVideo } = useMediaInfo(media);
|
||||
|
||||
const relPath = useMemo(() => {
|
||||
if (viewData?.data?.pageBundle && viewData?.data?.filePath) {
|
||||
const articlePath = viewData?.data?.filePath;
|
||||
const articleDir = parse(parseWinPath(articlePath)).dir;
|
||||
|
||||
const mediaPath = parseWinPath(media.fsPath);
|
||||
if (mediaPath.startsWith(articleDir)) {
|
||||
return getRelPath(media.fsPath, undefined, articleDir);
|
||||
}
|
||||
}
|
||||
return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder);
|
||||
}, [media.fsPath, settings?.staticFolder, settings?.wsFolder]);
|
||||
}, [media.fsPath, settings?.staticFolder, settings?.wsFolder, viewData?.data?.pageBundle, viewData?.data?.filePath]);
|
||||
|
||||
const hasViewData = useMemo(() => {
|
||||
return viewData?.data?.filePath !== undefined;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ArrowUpTrayIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import {
|
||||
LoadingAtom,
|
||||
MediaFoldersAtom,
|
||||
PagedItems,
|
||||
SelectedMediaFolderAtom,
|
||||
SettingsSelector,
|
||||
SortingAtom,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
@@ -20,7 +22,7 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { FolderItem } from './FolderItem';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import { GeneralCommands, STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
|
||||
import { STATIC_FOLDER_PLACEHOLDER, } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { basename, extname, join } from 'path';
|
||||
@@ -29,18 +31,19 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { MediaItemPanel } from './MediaItemPanel';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
import { SortOption } from '../../constants/SortOption';
|
||||
|
||||
export interface IMediaProps { }
|
||||
|
||||
export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
_: React.PropsWithChildren<IMediaProps>
|
||||
) => {
|
||||
export const Media: React.FunctionComponent<IMediaProps> = () => {
|
||||
const { media } = useMedia();
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
|
||||
const folders = useRecoilValue(MediaFoldersAtom);
|
||||
const loading = useRecoilValue(LoadingAtom);
|
||||
const crntSorting = useRecoilValue(SortingAtom);
|
||||
const [, setPagedItems] = useRecoilState(PagedItems);
|
||||
|
||||
const currentStaticFolder = useMemo(() => {
|
||||
if (settings?.staticFolder) {
|
||||
@@ -65,7 +68,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
return [];
|
||||
}
|
||||
|
||||
let groupedFolders = [];
|
||||
const groupedFolders = [];
|
||||
|
||||
for (const cFolder of settings?.contentFolders || []) {
|
||||
const foldersPath = parseWinPath(cFolder.path);
|
||||
@@ -83,11 +86,18 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
currentStaticFolder &&
|
||||
settings?.staticFolder !== STATIC_FOLDER_PLACEHOLDER.hexo.placeholder
|
||||
) {
|
||||
return folders.filter((f) => parseWinPath(f).includes(currentStaticFolder));
|
||||
const allFolders = folders.filter((f) => parseWinPath(f).includes(currentStaticFolder));
|
||||
if (crntSorting && crntSorting.id === SortOption.FileNameAsc) {
|
||||
return allFolders.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
||||
} else if (crntSorting && crntSorting.id === SortOption.FileNameDesc) {
|
||||
return allFolders.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
||||
} else {
|
||||
return allFolders;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [folders, viewData, currentStaticFolder, settings?.staticFolder]);
|
||||
}, [folders, viewData, currentStaticFolder, settings?.staticFolder, crntSorting]);
|
||||
|
||||
const allMedia = useMemo(() => {
|
||||
let mediaFiles: MediaInfo[] = Object.assign([], media);
|
||||
@@ -126,6 +136,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
});
|
||||
}
|
||||
|
||||
setPagedItems(mediaFiles.map((m) => m.fsPath));
|
||||
return mediaFiles;
|
||||
}, [media, viewData, currentStaticFolder, settings?.staticFolder]);
|
||||
|
||||
@@ -151,15 +162,6 @@ 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({
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface IMediaHeaderTopProps { }
|
||||
|
||||
export const MediaHeaderTop: React.FunctionComponent<
|
||||
IMediaHeaderTopProps
|
||||
> = ({ }: React.PropsWithChildren<IMediaHeaderTopProps>) => {
|
||||
> = () => {
|
||||
const [lastUpdated, setLastUpdated] = React.useState<string | null>(null);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const crntSorting = useRecoilValue(SortingSelector);
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IMenuButtonProps {
|
||||
label: string | JSX.Element;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
labelClass?: string;
|
||||
buttonClass?: string;
|
||||
}
|
||||
|
||||
export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
label,
|
||||
title,
|
||||
disabled
|
||||
disabled,
|
||||
className,
|
||||
labelClass,
|
||||
buttonClass,
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>
|
||||
<div className={cn(`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''} ${className || ""}`)}>
|
||||
<div className={cn(`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)] ${labelClass || ""}`)}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
<DropdownMenuTrigger
|
||||
className='text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)] flex items-center focus:outline-none'
|
||||
className={cn(`text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)] flex items-center focus:outline-none ${buttonClass || ""}`)}
|
||||
disabled={disabled}>
|
||||
<span>{title}</span>
|
||||
<ChevronDownIcon className={`-mr-1 ml-1 h-4 w-4`} aria-hidden="true" />
|
||||
|
||||
@@ -20,7 +20,8 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
|
||||
|
||||
const onRefresh = () => {
|
||||
if (iframeRef.current?.src) {
|
||||
iframeRef.current.src = iframeRef.current.src;
|
||||
const url = iframeRef.current.src;
|
||||
iframeRef.current.src = url;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,10 +35,12 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
|
||||
navUrl = `https://${navUrl}`;
|
||||
setCrntUrl(navUrl);
|
||||
}
|
||||
iframeRef.current!.src = navUrl;
|
||||
if (iframeRef.current) {
|
||||
iframeRef.current.src = navUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const msgListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const msgListener = (message: MessageEvent<EventData<string>>) => {
|
||||
if (message.data.command === PreviewCommands.toWebview.updateUrl) {
|
||||
setCrntUrl(message.data.payload);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Startup } from '../Header/Startup';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { SettingsInput } from './SettingsInput';
|
||||
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
|
||||
import { DOCS_SUBMODULES, FrameworkDetectors, GIT_CONFIG, SETTING_FRAMEWORK_START, SETTING_GIT_COMMIT_MSG, SETTING_GIT_ENABLED, SETTING_PREVIEW_HOST, SETTING_WEBSITE_URL } from '../../../constants';
|
||||
import { Button as VSCodeButton } from 'vscrui';
|
||||
import { DOCS_SUBMODULES, FrameworkDetectors, GIT_CONFIG, SETTING_FRAMEWORK_START, SETTING_GIT_COMMIT_MSG, SETTING_GIT_ENABLED, SETTING_PANEL_OPEN_ON_SUPPORTED_FILE, SETTING_PREVIEW_HOST, SETTING_WEBSITE_URL } from '../../../constants';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsCheckbox } from './SettingsCheckbox';
|
||||
import { ChevronRightIcon } from '@heroicons/react/24/outline';
|
||||
import { BooleanOption } from '../Header/BooleanOption';
|
||||
|
||||
export interface ICommonSettingsProps { }
|
||||
|
||||
@@ -73,6 +74,15 @@ export const CommonSettings: React.FunctionComponent<ICommonSettingsProps> = (pr
|
||||
<Startup settings={settings} />
|
||||
</div>
|
||||
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsOpenPanelForSupportedFiles)}</h2>
|
||||
|
||||
<BooleanOption
|
||||
label={l10n.t(LocalizationKey.settingsOpenPanelForSupportedFilesLabel)}
|
||||
name={SETTING_PANEL_OPEN_ON_SUPPORTED_FILE}
|
||||
value={settings?.openPanelForSupportedFiles} />
|
||||
</div>
|
||||
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsGit)}</h2>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { GeneralCommands, ExtensionState } from '../../../constants';
|
||||
import { SettingsInput } from './SettingsInput';
|
||||
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Button as VSCodeButton } from 'vscrui';
|
||||
|
||||
export interface IIntegrationsViewProps { }
|
||||
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = ({ }: React.PropsWithChildren<IIntegrationsViewProps>) => {
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = () => {
|
||||
const [deeplApiKey, setDeeplApiKey] = React.useState<string>('');
|
||||
const [azureApiKey, setAzureApiKey] = React.useState<string>('');
|
||||
const [azureRegion, setAzureRegion] = React.useState<string>('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface ISettingsCheckboxProps {
|
||||
label: string;
|
||||
@@ -27,7 +27,7 @@ export const SettingsCheckbox: React.FunctionComponent<ISettingsCheckboxProps> =
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateValue(e.target.checked)}
|
||||
onChange={updateValue}
|
||||
checked={isEnabled}>
|
||||
{label}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { TextField as VSCodeTextField } from 'vscrui';
|
||||
|
||||
export interface ISettingsInputProps {
|
||||
label: string;
|
||||
@@ -21,13 +21,10 @@ export const SettingsInput: React.FunctionComponent<ISettingsInputProps> = ({
|
||||
|
||||
return (
|
||||
<VSCodeTextField
|
||||
className='w-full p-0 m-0 bg-inherit border-0 focus:border-0 outline-none focus:outline-none shadow-none'
|
||||
style={{
|
||||
boxShadow: 'none'
|
||||
}}
|
||||
className='w-full p-0 m-0 bg-inherit'
|
||||
value={value || fallback || ""}
|
||||
placeholder={placeholder}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) => onChange(name, e.target.value)}>
|
||||
onChange={(value: string) => onChange(name, value)}>
|
||||
{label}
|
||||
</VSCodeTextField>
|
||||
);
|
||||
|
||||
@@ -8,14 +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, TelemetryEvent } from '../../../constants';
|
||||
import { COMMAND_NAME } 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';
|
||||
import { ITab, IView, Panels as VSCodePanels } from 'vscrui';
|
||||
|
||||
export interface ISettingsViewProps { }
|
||||
|
||||
@@ -23,12 +23,76 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const tabs: ITab[] = React.useMemo(() => {
|
||||
const temp = [
|
||||
{ id: "view-1", label: l10n.t(LocalizationKey.settingsViewCommon) },
|
||||
{ id: "view-2", label: l10n.t(LocalizationKey.settingsViewContentFolders) }
|
||||
];
|
||||
|
||||
if (settings?.crntFramework === 'astro') {
|
||||
temp.push({ id: "view-3", label: l10n.t(LocalizationKey.settingsViewAstro) });
|
||||
}
|
||||
|
||||
return [
|
||||
...temp,
|
||||
{ id: "view-4", label: l10n.t(LocalizationKey.settingsViewIntegration) }
|
||||
];
|
||||
}, [settings]);
|
||||
|
||||
const views: IView[] = React.useMemo(() => {
|
||||
if (!settings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const temp = [
|
||||
{
|
||||
id: "view-1",
|
||||
content: <CommonSettings />
|
||||
},
|
||||
{
|
||||
id: "view-2",
|
||||
content: (
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentFolders)}</h2>
|
||||
|
||||
<ContentFolders
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
if (settings?.crntFramework === 'astro') {
|
||||
temp.push({
|
||||
id: "view-3",
|
||||
content: (
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentTypes)}</h2>
|
||||
|
||||
<AstroContentTypes
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)}
|
||||
setStatus={_ => null} />
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
temp.push({
|
||||
id: "view-4",
|
||||
content: (
|
||||
<IntegrationsView />
|
||||
)
|
||||
});
|
||||
|
||||
return [
|
||||
...temp
|
||||
];
|
||||
}, [settings]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.commonSettings));
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewSettings
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -56,51 +120,11 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<VSCodePanels className={`mt-4`}>
|
||||
<VSCodePanelTab id="view-1">{l10n.t(LocalizationKey.settingsViewCommon)}</VSCodePanelTab>
|
||||
<VSCodePanelTab id="view-2">{l10n.t(LocalizationKey.settingsViewContentFolders)}</VSCodePanelTab>
|
||||
|
||||
{
|
||||
settings?.crntFramework === 'astro' && (
|
||||
<VSCodePanelTab id="view-3">{l10n.t(LocalizationKey.settingsViewAstro)}</VSCodePanelTab>
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelTab id="view-4">{l10n.t(LocalizationKey.settingsViewIntegration)}</VSCodePanelTab>
|
||||
|
||||
<VSCodePanelView id="view-1">
|
||||
<CommonSettings />
|
||||
</VSCodePanelView>
|
||||
|
||||
<VSCodePanelView id="view-2">
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentFolders)}</h2>
|
||||
|
||||
<ContentFolders
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)} />
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
|
||||
{
|
||||
settings?.crntFramework === 'astro' && (
|
||||
<VSCodePanelView id="view-3">
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentTypes)}</h2>
|
||||
|
||||
<AstroContentTypes
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)}
|
||||
setStatus={_ => null} />
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelView id="view-4">
|
||||
<IntegrationsView />
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
<VSCodePanels
|
||||
className={`mt-4`}
|
||||
tabs={tabs}
|
||||
views={views}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
let snippets: Snippets = Object.assign({}, settings?.snippets || {});
|
||||
const snippets: Snippets = Object.assign({}, settings?.snippets || {});
|
||||
const snippetLines = snippetOriginalBody.split('\n');
|
||||
|
||||
const crntSnippet = Object.assign({}, snippets[snippetKey]);
|
||||
@@ -183,7 +183,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
<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)
|
||||
snippet.isMediaSnippet ? l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeMedia) : l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeContent)
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
|
||||
);
|
||||
|
||||
const snippetBody = useMemo(() => {
|
||||
let body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
||||
const body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
||||
|
||||
const obj: any = {};
|
||||
const obj: { [key: string]: string } = {};
|
||||
for (const field of fields) {
|
||||
obj[field.name] = field.value;
|
||||
obj[field.name] = field.value as string;
|
||||
}
|
||||
|
||||
return SnippetParser.render(body, obj, snippet.openingTags, snippet.closingTags);
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
<div className="relative">
|
||||
<select
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
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"
|
||||
@@ -69,7 +69,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
return (
|
||||
<TextField
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
description={field.description}
|
||||
onChange={(e) => onValueChange(field, e)}
|
||||
rows={4}
|
||||
@@ -81,7 +81,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
return (
|
||||
<TextField
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
description={field.description}
|
||||
onChange={(e) => onValueChange(field, e)}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
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';
|
||||
@@ -84,10 +83,6 @@ 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'
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TemplateItem } from './TemplateItem';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
|
||||
import { ContentFolders } from '../Configuration/Common/ContentFolders';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IStepsToGetStartedProps {
|
||||
@@ -239,7 +239,7 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
|
||||
description: (
|
||||
<div className='mt-1'>
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateSetting(SETTING_GIT_ENABLED, e.target.checked)}
|
||||
onChange={(value) => updateSetting(SETTING_GIT_ENABLED, value)}
|
||||
checked={isGitEnabled}>
|
||||
{l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedGitDescription)}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TaxonomyLookup: React.FunctionComponent<ITaxonomyLookupProps> = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
return fieldName && page[fieldName] ? page[fieldName].includes(value) : false;
|
||||
}).length;
|
||||
|
||||
@@ -80,7 +80,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
}, [data, taxonomy, debounceFilterValue]);
|
||||
|
||||
const unmappedItems = useMemo(() => {
|
||||
let unmapped: string[] = [];
|
||||
const unmapped: string[] = [];
|
||||
|
||||
if (!pages || !settings?.contentTypes || !taxonomy) {
|
||||
return unmapped;
|
||||
@@ -100,7 +100,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
return false;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
if (fieldName && page[fieldName]) {
|
||||
values = page[fieldName];
|
||||
|
||||
@@ -5,18 +5,18 @@ import { SettingsSelector } from '../../state';
|
||||
import { getTaxonomyField } from '../../../helpers/getTaxonomyField';
|
||||
import { Sorting } from '../../../helpers/Sorting';
|
||||
import { ArrowLeftIcon, EyeIcon } from '@heroicons/react/24/outline';
|
||||
import { Button } from '../Common/Button';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Button } from 'vscrui';
|
||||
import { FilterInput } from './FilterInput';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { sortPages } from '../../../utils/sortPages';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ExtensionState } from '../../../constants';
|
||||
import { LinkButton } from '../Common/LinkButton';
|
||||
import { openFile } from '../../utils';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface ITaxonomyTaggingProps {
|
||||
taxonomy: string | null;
|
||||
@@ -70,7 +70,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
continue;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
if (fieldName && (!page[fieldName] || page[fieldName].indexOf(value) === -1)) {
|
||||
untagged.push(page);
|
||||
@@ -78,7 +78,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
}
|
||||
}
|
||||
|
||||
untagged = untagged.sort(Sorting.number('fmPublished')).reverse();
|
||||
untagged = untagged.sort(Sorting.numerically('fmPublished')).reverse();
|
||||
|
||||
if (debounceFilterValue) {
|
||||
return untagged.filter((p) => p.title.toLowerCase().includes(debounceFilterValue.toLowerCase()));
|
||||
@@ -122,8 +122,12 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
}, [pageMappings, untaggedPages]);
|
||||
|
||||
const checkIfChecked = React.useCallback((page: Page) => {
|
||||
return (!untaggedPages.find((p: Page) => p.fmFilePath === page.fmFilePath) && !pageMappings.untagged.find((p: Page) => p.fmFilePath === page.fmFilePath)) || pageMappings.tagged.find((p: Page) => p.fmFilePath === page.fmFilePath);
|
||||
}, [untaggedPages, pageMappings.tagged]);
|
||||
const isUntagged = untaggedPages.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
const isTagged = pageMappings.tagged.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
const isInUntagged = pageMappings.untagged.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
|
||||
return (!isUntagged && !isInUntagged) || isTagged;
|
||||
}, [untaggedPages, pageMappings.tagged, pageMappings.untagged]);
|
||||
|
||||
const onFileView = (filePath: string) => {
|
||||
openFile(filePath);
|
||||
@@ -193,7 +197,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
<td className={`pl-6 py-2 w-[25px]`}>
|
||||
<VSCodeCheckbox
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
|
||||
onClick={() => onCheckboxClick(page)}
|
||||
onChange={() => onCheckboxClick(page)}
|
||||
checked={checkIfChecked(page)}>
|
||||
<span className='sr-only'>
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
|
||||
@@ -225,8 +229,8 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end space-x-2'>
|
||||
<Button onClick={onDismiss} secondary>{l10n.t(LocalizationKey.commonCancel)}</Button>
|
||||
<Button onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
|
||||
<Button className='!py-2' onClick={onDismiss} appearance='secondary'>{l10n.t(LocalizationKey.commonCancel)}</Button>
|
||||
<Button className='!py-2' onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 { GeneralCommands, TelemetryEvent } from '../../../constants';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
import { TaxonomyData } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page, PageMappings } from '../../models';
|
||||
@@ -55,10 +55,6 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
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'
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 { }
|
||||
|
||||
@@ -14,10 +13,6 @@ export const UnknownView: React.FunctionComponent<IUnknownViewProps> = (
|
||||
) => {
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.setTitle, "Unknown View");
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewUnknown
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BookOpenIcon, HeartIcon, StarIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { DOCUMENTATION_LINK, GITHUB_LINK, GeneralCommands, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../../constants';
|
||||
import { DOCUMENTATION_LINK, GITHUB_LINK, GeneralCommands, REVIEW_LINK, SPONSOR_LINK } from '../../../constants';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { GitHubIcon } from '../../../panelWebView/components/Icons/GitHubIcon';
|
||||
@@ -25,9 +25,6 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({
|
||||
|
||||
React.useEffect(() => {
|
||||
setView(NavigationType.Welcome);
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewWelcomeScreen
|
||||
});
|
||||
|
||||
Messenger.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: 'Welcome screen loaded',
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function useMediaInfo(media?: MediaInfo) {
|
||||
}, [media]);
|
||||
|
||||
const mediaDetails = useMemo(() => {
|
||||
let sizeDetails = [];
|
||||
const sizeDetails = [];
|
||||
|
||||
if (mediaDimensions) {
|
||||
sizeDetails.push(mediaDimensions);
|
||||
|
||||
@@ -107,7 +107,14 @@ export default function usePages(pages: Page[]) {
|
||||
for (const filter of filterNames) {
|
||||
const filterValue = filters[filter];
|
||||
if (filterValue) {
|
||||
pagesSorted = pagesSorted.filter((page) => page[filter] === filterValue);
|
||||
pagesSorted = pagesSorted.filter((page) => {
|
||||
const value = page[filter];
|
||||
if (Array.isArray(value)) {
|
||||
return value.includes(filterValue);
|
||||
} else {
|
||||
return value === filterValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,8 +166,6 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
if (tab !== Tab.All) {
|
||||
crntPages = crntPages.filter((page) => page.fmDraft === tab);
|
||||
} else {
|
||||
crntPages = crntPages;
|
||||
}
|
||||
} else {
|
||||
// Draft field is a boolean field
|
||||
@@ -194,8 +199,6 @@ export default function usePages(pages: Page[]) {
|
||||
crntPages = drafts;
|
||||
} else if (tab === Tab.Scheduled) {
|
||||
crntPages = scheduled;
|
||||
} else {
|
||||
crntPages = crntPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,11 +243,11 @@ export default function usePages(pages: Page[]) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let usedSorting = sorting;
|
||||
const usedSorting = sorting;
|
||||
|
||||
const startPageProcessing = () => {
|
||||
// Check if search needs to be performed
|
||||
let searchedPages = pages;
|
||||
const searchedPages = pages;
|
||||
if (search) {
|
||||
Messenger.send(DashboardMessage.searchPages, { query: search });
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { I10nProvider } from './providers/I10nProvider';
|
||||
import { SentryInit } from '../utils/sentryInit';
|
||||
import { WEBSITE_LINKS } from '../constants';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
setState: (data: T) => void;
|
||||
@@ -93,7 +94,7 @@ if (elm) {
|
||||
render(
|
||||
<I10nProvider>
|
||||
<SettingsProvider
|
||||
aiUrl={WEBSITE_LINKS.root}
|
||||
aiUrl={WEBSITE_LINKS.api.baseUrl}
|
||||
experimental={experimental === 'true'}
|
||||
version={version || ""}>
|
||||
<Chatbot />
|
||||
@@ -119,4 +120,8 @@ if (elm) {
|
||||
}
|
||||
|
||||
// Webpack HMR
|
||||
if ((module as any).hot) (module as any).hot.accept();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((module as any).hot) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(module as any).hot.accept();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ContentType,
|
||||
CustomScript,
|
||||
CustomTaxonomy,
|
||||
DataFolder,
|
||||
DraftField,
|
||||
FilterType,
|
||||
Framework,
|
||||
@@ -30,6 +31,7 @@ export interface Settings {
|
||||
categories: string[];
|
||||
customTaxonomy: CustomTaxonomy[];
|
||||
openOnStart: boolean | null;
|
||||
openPanelForSupportedFiles: boolean | null;
|
||||
versionInfo: VersionInfo;
|
||||
pageViewType: DashboardViewType | undefined;
|
||||
contentTypes: ContentType[];
|
||||
@@ -40,9 +42,11 @@ export interface Settings {
|
||||
draftField: DraftField | null | undefined;
|
||||
customSorting: SortingSetting[] | undefined;
|
||||
filters: (FilterType | { title: string; name: string })[] | undefined;
|
||||
grouping: { title: string; name: string }[] | undefined;
|
||||
dashboardState: DashboardState;
|
||||
scripts: CustomScript[];
|
||||
dataFiles: DataFile[] | undefined;
|
||||
dataFolders: DataFolder[];
|
||||
dataTypes: DataType[] | undefined;
|
||||
isBacker: boolean | undefined;
|
||||
snippets: Snippets | undefined;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const FilterValuesAtom = atom<{ [filter: string]: string[] }>({
|
||||
export const FilterValuesAtom = atom<{ [filter: string]: string[] | string[][] }>({
|
||||
key: 'FilterValuesAtom',
|
||||
default: {}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user