Compare commits

..

93 Commits

Author SHA1 Message Date
Elio Struyf 88cad8caa2 Merge pull request #897 from estruyf/beta
v10.7.0 release
2024-12-31 15:28:43 +01:00
Elio Struyf a04d56fbde 10.7.0 2024-12-31 15:24:47 +01:00
Elio Struyf ec86b079a6 Update changelog 2024-12-31 15:23:28 +01:00
Elio Struyf 838ced0560 Remove logging 2024-12-30 11:40:12 +01:00
Elio Struyf 1c269db91d Issue: [BUG] Filtering on field with multiple values does not work as expected #895 2024-12-30 11:39:12 +01:00
Elio Struyf 1ed5131abe Change logging output 2024-12-29 13:18:30 +01:00
Elio Struyf d944319d53 #896 - Fix glob node_modules ignore 2024-12-29 13:08:43 +01:00
Elio Struyf 146bbbf6a1 Added some verbose logging 2024-12-27 18:07:51 +01:00
Elio Struyf 7bfc72469d Merge pull request #893 from estruyf/issue/892
Add media folder actions and localization updates #892
2024-11-29 15:43:31 +01:00
Elio Struyf 324184964b Update media 2024-11-29 15:42:47 +01:00
Elio Struyf 1f6ea6ac20 Sorting on folders 2024-11-29 15:41:50 +01:00
Elio Struyf 5a45fdc94f Added progress 2024-11-29 15:19:22 +01:00
Elio Struyf b043c22437 Add media folder actions and localization updates #892 2024-11-29 12:05:08 +01:00
Elio Struyf b48e34ecb0 Remove CenterIcon ref 2024-11-28 18:24:54 +01:00
Elio Struyf 3bdae40ff0 Updated center layout icon 2024-11-28 18:19:42 +01:00
Elio Struyf 94df672f4c Updated yellow to warning 2024-11-28 18:18:55 +01:00
Elio Struyf 98c5b56310 Fix errors 2024-11-28 09:19:48 +01:00
Elio Struyf f38144b8a7 Merge branch 'T3sT3ro-beta' into beta 2024-11-28 09:05:03 +01:00
Elio Struyf 231bd89619 Added density tooltip 2024-11-28 09:04:25 +01:00
Elio Struyf 4907a7aaa9 Merge branch 'beta' of github.com:T3sT3ro/vscode-front-matter into T3sT3ro-beta 2024-11-28 08:46:12 +01:00
Tooster 2dc4865581 #705 - fix styles - align checks badge padding and margin 2024-11-28 00:30:45 +01:00
Tooster f1ae0d60cc #705 - UI tweaks and accessibility changes 2024-11-28 00:10:27 +01:00
Elio Struyf 2f76de2a28 #405 - Implement custom grouping option 2024-11-27 16:00:34 +01:00
Elio Struyf d7282b18eb Update key 2024-11-27 15:58:57 +01:00
Elio Struyf eb22a97198 Optimizations 2024-11-27 15:58:38 +01:00
Elio Struyf 42fe1c2887 #705 - Style changes 2024-11-25 16:49:47 +01:00
Elio Struyf c02275d20b #705 - Style fixes 2024-11-25 13:41:43 +01:00
Elio Struyf 147823bfd0 #705 - update density 2024-11-25 12:00:07 +01:00
Elio Struyf a7f183b6cc #705 - further improve keywords section 2024-11-25 11:59:22 +01:00
Elio Struyf e10ee11f0e Fix title field on keywords section #705 2024-11-24 18:03:39 +01:00
Elio Struyf fca8d260d5 Enhance SEO components with keyword management and styling updates #705 2024-11-22 09:27:46 +01:00
Elio Struyf 8cecf8d8be #705 - change order of metadata view 2024-11-21 16:40:24 +01:00
Elio Struyf 22ce41c3eb #705 - UX improvements for SEO panel 2024-11-21 16:14:25 +01:00
Elio Struyf cb649a9a97 Add timezone formatting support and related settings #887 2024-11-14 16:30:46 +01:00
Elio Struyf 65d430b7cf Add GitHub Copilot prompt functionality #888 2024-11-13 10:24:24 +01:00
Elio Struyf f1f0e0ab58 Merge pull request #886 from estruyf/beta
PR for v10.6.0
2024-11-06 11:06:01 +01:00
Elio Struyf 5f7f847ff8 Merge branch 'beta' of github.com:estruyf/vscode-front-matter into beta 2024-11-06 10:54:36 +01:00
Elio Struyf 2c4dbeb1eb update changelog 2024-11-06 10:54:32 +01:00
Elio Struyf 17164df11f Update fuse options 2024-11-04 19:37:00 +01:00
Elio Struyf 228c46084d Update categories 2024-11-04 18:39:47 +01:00
Elio Struyf e838f18abc Remove unused ref 2024-11-04 13:42:36 +01:00
Elio Struyf 3d6359bc2e Issue: Content relationship field type is fetching forever #885 2024-11-04 13:20:18 +01:00
Elio Struyf 57b710cc61 Enhancement: hide WYSIWYG actions in git diff mode #884 2024-11-04 11:23:19 +01:00
Elio Struyf 7796d52ff9 Fix beta version 2024-10-28 16:25:49 +01:00
Elio Struyf f8f539be0d #862 - evaluate the node command 2024-10-28 15:07:35 +01:00
Elio Struyf fc96c8922c Feedback: Enum/Select in schema fields #859 2024-10-28 14:41:50 +01:00
Elio Struyf c84af8493b Enhancement: dynamic evaluation of commands #882 2024-10-28 10:40:49 +01:00
Elio Struyf 6f288ff757 #879 Fix for auto updating last modified date on save 2024-10-24 14:25:34 +02:00
Elio Struyf 0e04e687fa #878 - Enhanced select all logic 2024-10-24 12:05:07 +02:00
Elio Struyf cec3cbee3a Update changelog 2024-10-23 17:54:43 +02:00
Elio Struyf c6f40194b4 Refactor snippet type display logic #867 2024-10-23 17:53:27 +02:00
Elio Struyf 6c591a90bd Merge pull request #877 from estruyf/beta
v10.5.1 changes
2024-10-23 17:37:17 +02:00
Elio Struyf bece544934 Update changelog 2024-10-23 17:33:35 +02:00
Elio Struyf c40fcba088 #872 - set default values in fields field type 2024-10-23 17:24:08 +02:00
Elio Struyf ea9f8a2651 Remove debugger 2024-10-23 13:23:49 +02:00
Elio Struyf b9b927c800 Retrieve correct default value #872 2024-10-23 13:23:31 +02:00
Elio Struyf bc0f2e7bf7 Update changelog + version 2024-10-23 13:14:28 +02:00
Elio Struyf b18f5e1e36 Removal of all punctuations from filename #875 2024-10-23 13:10:33 +02:00
Elio Struyf 8b05da5a76 Fix issue with default field value in field's 'when' clause #872 2024-10-23 12:29:12 +02:00
Elio Struyf 0062117c3b Fix media snippet markup insertion and clean up exclamation marks in file names #875 2024-10-23 12:17:01 +02:00
Elio Struyf 1d485adbca Fix media snippet markup insertion to article content's #874 2024-10-23 10:30:43 +02:00
Elio Struyf 9f2aa34aac 10.6.0 2024-10-23 09:44:36 +02:00
Elio Struyf 46a7a49e7c Optimize Copilot chat model retrieval #873 2024-10-23 09:44:29 +02:00
Elio Struyf 61ae29c37a Style changes 2024-10-23 09:28:03 +02:00
Elio Struyf 9d51531d59 Merge pull request #871 from estruyf/beta
Prep v10.5.0 release
2024-10-21 16:34:04 +02:00
Elio Struyf 0cb7d2463b Update workflows 2024-10-21 16:14:40 +02:00
Elio Struyf ceeb1bf9a7 Updated changelog 2024-10-21 16:12:34 +02:00
Elio Struyf c11efa56f1 #870 NumField component styles 2024-10-17 15:27:28 +02:00
Elio Struyf fa3215fa64 Enhancement: Support Markdown in the WYSIWYG field #866 2024-10-10 09:21:09 +02:00
Elio Struyf 305c95fa86 Merge branch 'issue/865' into beta 2024-10-10 09:19:59 +02:00
Elio Struyf 3b7671afc9 Update schema 2024-10-10 09:19:47 +02:00
Elio Struyf 8660f5f680 Update type #840 2024-10-09 18:05:08 +02:00
Elio Struyf 2269994b43 separate the wysiwyg field 2024-10-09 14:13:55 +02:00
Elio Struyf bdcd901e51 Merge branch 'beta' into issue/865 2024-10-09 10:07:38 +02:00
Elio Struyf 6d7df4266d #840 - Fix win path 2024-10-08 19:55:45 +02:00
Elio Struyf 8c2d243777 WIP 2024-10-08 19:40:58 +02:00
Elio Struyf 4282ec83e5 Add excludePaths option #840 2024-10-08 12:16:59 +02:00
Elio Struyf 0f3c43e0fc Fix button styling on the data screen #858 2024-10-08 09:31:40 +02:00
Elio Struyf a377f27765 Update changelog 2024-10-07 22:00:12 +02:00
Elio Struyf 17860a18f4 Merge branch 'issue/851' of github.com:wottpal/vscode-front-matter into wottpal-issue/851 2024-10-07 21:58:08 +02:00
Elio Struyf 73609ca346 Update changelog 2024-10-07 21:32:47 +02:00
Elio Struyf d6dfa8c9cf Merge branch 'wottpal-issue/850' into beta 2024-10-07 21:18:40 +02:00
Elio Struyf 1c00362b1c Updated localization 2024-10-07 21:17:56 +02:00
Elio Struyf 63ea564734 #863 fix empty page folder cache 2024-10-07 21:14:58 +02:00
Elio Struyf 38f128e1b6 Merge branch 'issue/850' of github.com:wottpal/vscode-front-matter into wottpal-issue/850 2024-10-07 20:59:59 +02:00
Dennis Zoma 39704f3a55 feat: Add option to filter content relationship options by active locale 2024-10-07 10:54:20 +02:00
Dennis Zoma 2020198e90 fix: Fix issue of filtering incorrect content types 2024-10-07 09:13:42 +02:00
Dennis Zoma ba1cf95ffd feat: Show slug in content relationship combobox option 2024-10-07 08:38:22 +02:00
Dennis Zoma aea87a6168 feat: Add action to create or open translation file dynamically 2024-10-07 08:04:29 +02:00
Elio Struyf 179a71db39 Merge pull request #861 from davidsneighbour/patch-1
fix: typo
2024-10-05 10:00:35 +02:00
Patrick Kollitsch 8d8e3fe3cc fix: typo 2024-10-02 13:15:27 +07:00
Elio Struyf 3d8c550f60 10.5.0 2024-09-27 11:14:43 +02:00
Elio Struyf 6fd526e962 Added eslint to webpack config 2024-09-27 11:14:34 +02:00
183 changed files with 8095 additions and 1430 deletions
+3 -1
View File
@@ -13,6 +13,8 @@
"no-unused-expressions": "error",
"curly": "error",
"class-methods-use-this": "warn",
"no-console": "warn"
"no-console": "warn",
"@typescript-eslint/no-empty-interface": "off",
"no-extra-boolean-cast": "off"
}
}
+1 -1
View File
@@ -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'
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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
View File
@@ -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
},
+56
View File
@@ -1,5 +1,61 @@
# Change Log
## [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
+2 -6
View File
@@ -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 {
+1 -7
View File
@@ -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",
+1 -7
View File
@@ -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",
+1 -7
View File
@@ -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",
+1 -7
View File
@@ -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} 語",
+18 -10
View File
@@ -144,7 +144,7 @@
"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}\"",
@@ -187,7 +187,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",
@@ -247,6 +247,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",
@@ -448,9 +449,6 @@
"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",
@@ -499,18 +497,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",
@@ -583,6 +583,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}",
@@ -771,6 +776,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.",
+5774 -180
View File
File diff suppressed because it is too large Load Diff
+94 -20
View File
@@ -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.4.1",
"version": "10.7.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": [],
@@ -1060,7 +1096,7 @@
"error"
],
"markdownDescription": "%setting.frontMatter.global.notifications.markdownDescription%",
"scope": "Templates"
"scope": "Global"
},
"frontMatter.global.disabledNotifications": {
"type": "array",
@@ -1070,6 +1106,12 @@
"requiredFieldValidation"
]
},
"frontMatter.global.timezone": {
"default": "UTC",
"type": "string",
"markdownDescription": "%setting.frontMatter.global.timezone.markdownDescription%",
"scope": "Global"
},
"frontMatter.media.defaultSorting": {
"type": "string",
"default": "",
@@ -1374,7 +1416,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%"
},
@@ -1543,6 +1592,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%",
@@ -2393,6 +2447,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%",
@@ -2420,72 +2483,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",
@@ -2865,10 +2928,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",
@@ -2902,8 +2967,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",
@@ -2913,6 +2986,7 @@
"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",
+9 -1
View File
@@ -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",
@@ -164,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)",
@@ -202,7 +209,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?",
@@ -228,6 +235,7 @@
"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",
+15 -7
View File
@@ -23,7 +23,6 @@ import {
SETTING_SLUG_TEMPLATE
} from './../constants';
import { CustomPlaceholder, Field } from '../models';
import { format } from 'date-fns';
import {
ArticleHelper,
Logger,
@@ -44,7 +43,7 @@ import { NavigationType } from '../dashboardWebView/models';
import { SNIPPET } from '../constants/Snippet';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { getTitleField } from '../utils';
import { formatInTimezone, getTitleField } from '../utils';
export class Article {
/**
@@ -52,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)
);
@@ -66,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
*/
@@ -369,7 +377,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);
@@ -377,7 +385,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;
}
@@ -398,10 +406,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'
+21 -16
View File
@@ -33,26 +33,31 @@ 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;
}
});
+46 -15
View File
@@ -23,7 +23,6 @@ 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';
@@ -31,7 +30,7 @@ import { MediaListener, PagesListener, SettingsListener } from '../listeners/das
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
import { glob } from 'glob';
import { mkdirAsync } from '../utils/mkdirAsync';
import { existsAsync, isWindows, lstatAsync } from '../utils';
import { existsAsync, formatInTimezone, isWindows, lstatAsync } from '../utils';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { Preview } from './Preview';
@@ -39,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();
@@ -50,7 +49,7 @@ export class Folders {
public static clearCached() {
Logger.verbose(`Folders:clearCached`);
Folders._folders = [];
Folders._folders = undefined;
}
/**
@@ -85,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) {
@@ -327,7 +326,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;
}
@@ -401,8 +400,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) {
@@ -452,10 +451,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
@@ -688,7 +700,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))
@@ -719,7 +731,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
@@ -788,7 +800,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) =>
@@ -844,7 +860,7 @@ export class Folders {
try {
pattern = isWindows() ? parseWinPath(pattern) : pattern;
const folders = await glob(pattern, {
ignore: 'node_modules/**',
ignore: '**/node_modules/**',
dot: true
});
@@ -876,12 +892,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;
+22 -17
View File
@@ -110,27 +110,32 @@ 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;
}
});
+1 -1
View File
@@ -42,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) {
+208 -18
View File
@@ -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,
@@ -16,8 +25,7 @@ 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,11 +593,11 @@ 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 () => {
+26
View 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>
)}
+2 -1
View File
@@ -70,7 +70,8 @@ export const COMMAND_NAME = {
// i18n
i18n: {
create: getCommandName('i18n.create')
create: getCommandName('i18n.create'),
createOrOpen: getCommandName('i18n.createOrOpen')
},
// Project
+2
View File
@@ -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';
@@ -61,6 +62,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';
+2
View File
@@ -42,6 +42,8 @@ export enum DashboardMessage {
insertMedia = 'insertMedia',
updateMediaMetadata = 'updateMediaMetadata',
createMediaFolder = 'createMediaFolder',
updateMediaFolder = 'updateMediaFolder',
deleteMediaFolder = 'deleteMediaFolder',
insertFile = 'insertFile',
createHexoAssetFolder = 'createHexoAssetFolder',
getUnmappedMedia = 'getUnmappedMedia',
+1 -1
View File
@@ -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);
@@ -11,10 +11,11 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { messageHandler } from '@estruyf/vscode/dist/client';
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
@@ -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>
);
};
@@ -25,6 +25,9 @@ export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
<div className={`${cssNames} group-hover:block`}>
<VSCodeCheckbox
className={show ? "" : " shadow-[0_0_3px_var(--frontmatter-border-preserve)]"}
onClick={(e) => {
e.stopPropagation();
}}
onChange={() => {
onMultiSelect(filePath);
}}
@@ -10,8 +10,7 @@ import { GroupingSelector, PageAtom, PagedItems, ViewSelector } from '../../stat
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';
@@ -54,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(() => {
@@ -68,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) {
@@ -96,6 +111,8 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
...groupedPages,
}
}
} else {
groupKeys = groupKeys.sort();
}
return { groupKeys, groupedPages };
@@ -127,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>
@@ -176,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();
@@ -10,7 +10,6 @@ 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';
@@ -27,12 +26,11 @@ 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);
@@ -68,15 +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;
}
debugger
const dataClone: any[] = Object.assign([], dataEntries);
const dataClone: unknown[] = Object.assign([], dataEntries);
if (selectedIndex !== null && selectedIndex !== undefined) {
dataClone[selectedIndex] = data;
} else {
@@ -140,7 +137,7 @@ 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(() => {
@@ -189,8 +186,6 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
};
}, []);
console.log('DataView render', settings?.dataFolders);
return (
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
@@ -339,7 +334,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
/>
))}
</Container>
<Button className="mt-4" onClick={() => setSelectedIndex(null)}>
<Button className="mt-4 !py-2" onClick={() => setSelectedIndex(null)}>
{localize(LocalizationKey.dashboardDataViewDataViewAdd)}
</Button>
</>
@@ -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);
@@ -4,8 +4,7 @@ import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, Eye
import { useRecoilState, useRecoilValue } from 'recoil';
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';
@@ -68,8 +67,14 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
}, [selectedFiles]);
const selectAllItems = React.useCallback(() => {
setSelectedFiles([...pagedItems]);
}, [pagedItems]);
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[] = [];
@@ -92,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>
)
@@ -107,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>
@@ -163,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>
@@ -197,10 +202,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
<ActionsBarItem
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
onClick={viewFile}
title={l10n.t(LocalizationKey.commonView)}
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>
{
@@ -211,10 +216,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
messageHandler.send(DashboardMessage.rename, selectedFiles[0]);
setSelectedFiles([]);
}}
title={l10n.t(LocalizationKey.commonRename)}
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>
)
}
@@ -228,10 +233,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
path: selectedFiles[0],
action: 'edit'
})}
title={l10n.t(LocalizationKey.commonEdit)}
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>
</>
)
@@ -245,10 +250,10 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
disabled={selectedFiles.length === 0}
onClick={() => setShowAlert(true)}
title={l10n.t(LocalizationKey.commonDelete)}
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>
@@ -258,33 +263,33 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
<ActionsBarItem
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
onClick={() => setSelectedFiles([])}
title={l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}
title={localize(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}
>
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
<span>{localize(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
</ActionsBarItem>
)
}
<ActionsBarItem
disabled={selectedFiles.length === pagedItems.length}
disabled={hasAllItemsSelectedOnPage}
onClick={selectAllItems}
title={l10n.t(LocalizationKey.dashboardHeaderActionsBarSelectAll)}
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>{l10n.t(LocalizationKey.dashboardHeaderActionsBarSelectAll)}</span>
<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}
/>
@@ -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);
};
@@ -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" />
@@ -8,6 +8,7 @@ import {
PagedItems,
SelectedMediaFolderAtom,
SettingsSelector,
SortingAtom,
ViewDataSelector
} from '../../state';
import { Spinner } from '../Common/Spinner';
@@ -30,18 +31,18 @@ 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(() => {
@@ -67,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);
@@ -85,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);
@@ -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);
@@ -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);
}
@@ -8,7 +8,7 @@ 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>('');
@@ -24,7 +24,7 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
const settings = useRecoilValue(SettingsSelector);
const tabs: ITab[] = React.useMemo(() => {
let temp = [
const temp = [
{ id: "view-1", label: l10n.t(LocalizationKey.settingsViewCommon) },
{ id: "view-2", label: l10n.t(LocalizationKey.settingsViewContentFolders) }
];
@@ -44,7 +44,7 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
return [];
}
let temp = [
const temp = [
{
id: "view-1",
content: <CommonSettings />
@@ -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)}
/>
@@ -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,7 +5,7 @@ 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 { Button } from 'vscrui';
import { FilterInput } from './FilterInput';
import { useDebounce } from '../../../hooks/useDebounce';
import * as l10n from '@vscode/l10n';
@@ -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()));
@@ -229,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>
);
+1 -1
View File
@@ -42,7 +42,7 @@ export default function useMediaInfo(media?: MediaInfo) {
}, [media]);
const mediaDetails = useMemo(() => {
let sizeDetails = [];
const sizeDetails = [];
if (mediaDimensions) {
sizeDetails.push(mediaDimensions);
+10 -7
View File
@@ -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 {
+6 -1
View File
@@ -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;
@@ -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();
}
+1
View File
@@ -41,6 +41,7 @@ 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;
@@ -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: {}
});
+4 -2
View File
@@ -115,7 +115,9 @@
}
input[type='submit'] {
@apply mt-4 inline-flex w-auto items-center rounded border border-transparent bg-[var(--frontmatter-button-background)] px-3 py-2 text-sm font-medium leading-4 text-[var(--vscode-button-foreground)];
@apply mt-4 inline-flex w-auto items-center rounded-[2px] border border-transparent bg-[var(--frontmatter-button-background)] px-[11px] py-[4px] text-[var(--vscode-button-foreground)];
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size, 13px);
&:hover {
@apply bg-[var(--frontmatter-button-hoverBackground)];
@@ -310,7 +312,7 @@
}
input[type='submit'] {
@apply rounded text-vulcan-500;
@apply rounded-[2px] text-vulcan-500;
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ export const darkenColor = (color: string | undefined, percentage: number) => {
// Check if the color is in rgba format
if (color.startsWith('rgba')) {
// Extract the alpha value
const alphaMatch = color.match(/[\d\.]+(?=\))/);
const alphaMatch = color.match(/[\d.]+(?=\))/);
const alpha = alphaMatch ? Number(alphaMatch[0]) : 1;
return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`;
@@ -1,6 +1,6 @@
import { darkenColor, opacityColor, preserveColor } from '.';
export const updateCssVariables = (isDarkTheme: boolean = true) => {
export const updateCssVariables = (isDarkTheme = true) => {
const styles = getComputedStyle(document.documentElement);
// Lightbox
@@ -98,4 +98,17 @@ export const updateCssVariables = (isDarkTheme: boolean = true) => {
'--frontmatter-border-active',
darkenColor(borderColor, isDarkTheme ? -30 : 30) || 'var(--vscode-activityBar-activeBorder)'
);
// SEO - Success/Warning colors
const successColor = styles.getPropertyValue('--vscode-charts-green');
document.documentElement.style.setProperty(
'--frontmatter-success-background',
opacityColor(successColor, 0.05) || 'var(--vscode-charts-green)'
);
const warningColor = styles.getPropertyValue('--vscode-statusBarItem-warningBackground');
document.documentElement.style.setProperty(
'--frontmatter-warning-background',
opacityColor(warningColor, 0.05) || 'var(--vscode-statusBarItem-warningBackground)'
);
};
+8 -12
View File
@@ -14,7 +14,7 @@ import ContentProvider from './providers/ContentProvider';
import { PagesListener } from './listeners/dashboard';
import { ModeSwitch } from './services/ModeSwitch';
import { PagesParser } from './services/PagesParser';
import { ContentType, Telemetry, Extension } from './helpers';
import { ContentType, Extension } from './helpers';
import * as l10n from '@vscode/l10n';
import {
Backers,
@@ -39,6 +39,7 @@ import { i18n } from './commands/i18n';
import { UriHandler } from './providers/UriHandler';
let pageUpdateDebouncer: { (fnc: any, time: number): void };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let editDebounce: { (fnc: any, time: number): void };
let collection: vscode.DiagnosticCollection;
@@ -48,7 +49,6 @@ export async function activate(context: vscode.ExtensionContext) {
const extension = Extension.getInstance(context);
Logger.info(`Activating ${EXTENSION_NAME} version ${Extension.getInstance().version}...`);
Logger.info(`Logging level: ${Logger.getLevel()}`);
// Set development context
if (!Extension.getInstance().isProductionMode) {
@@ -56,7 +56,7 @@ export async function activate(context: vscode.ExtensionContext) {
}
// Sponsor check
Backers.init(context).then(() => {});
Backers.init(context);
// Make sure the EN language file is loaded
if (!vscode.l10n.uri) {
@@ -120,8 +120,9 @@ export async function activate(context: vscode.ExtensionContext) {
// Register the taxonomy commands
Taxonomy.registerCommands(subscriptions);
// Register all the article commands
// Register all the article commands and listeners
Article.registerCommands(subscriptions);
Article.registerListeners(subscriptions);
// Template creation
Template.registerCommands();
@@ -143,7 +144,7 @@ export async function activate(context: vscode.ExtensionContext) {
SettingsHelper.startListening();
// Create the status bar
let fmStatusBarItem = vscode.window.createStatusBarItem(
const fmStatusBarItem = vscode.window.createStatusBarItem(
'fm-statusBarItem',
vscode.StatusBarAlignment.Right,
-100
@@ -180,9 +181,6 @@ export async function activate(context: vscode.ExtensionContext) {
// Automatically run the command
triggerPageUpdate(`main`);
// Listener for file edit changes
subscriptions.push(vscode.workspace.onWillSaveTextDocument(handleAutoDateUpdate));
// Listener for file saves
subscriptions.push(PagesListener.saveFileWatcher());
@@ -240,15 +238,13 @@ export async function activate(context: vscode.ExtensionContext) {
// Subscribe all commands
subscriptions.push(PanelView, collapseAll, fmStatusBarItem);
// eslint-disable-next-line no-console
console.log(`𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲 𝖺𝖼𝗍𝗂𝗏𝖺𝗍𝖾𝖽! 𝖱𝖾𝖺𝖽𝗒 𝗍𝗈 𝗌𝗍𝖺𝗋𝗍 𝗐𝗋𝗂𝗍𝗂𝗇𝗀... 👩‍💻🧑‍💻👨‍💻`);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function deactivate() {}
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
Article.autoUpdate(e);
};
const triggerPageUpdate = (location: string) => {
Logger.verbose(`Trigger page update: ${location}`);
pageUpdateDebouncer(() => {
+2 -2
View File
@@ -356,7 +356,7 @@ export class ArticleHelper {
* @returns A boolean indicating whether the file is a page bundle or not.
*/
public static async isPageBundle(filePath: string) {
let article = await ArticleHelper.getFrontMatterByPath(filePath);
const article = await ArticleHelper.getFrontMatterByPath(filePath);
if (!article) {
return false;
}
@@ -913,7 +913,7 @@ export class ArticleHelper {
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
if (fileContents) {
let article = FrontMatterParser.fromFile(fileContents);
const article = FrontMatterParser.fromFile(fileContents);
if (article?.data) {
if (commaSeparated) {
+6 -5
View File
@@ -564,7 +564,7 @@ export class ContentType {
const allRequiredFields = ContentType.findRequiredFieldsDeep(contentType.fields);
let emptyFields: Field[][] = [];
const emptyFields: Field[][] = [];
for (const fields of allRequiredFields) {
const fieldValue = this.getFieldValue(
@@ -656,7 +656,7 @@ export class ContentType {
return [];
}
let foundBlocks = [];
const foundBlocks = [];
for (const group of groups) {
const block = blocks.find((block) => block.id === group);
if (!block) {
@@ -960,11 +960,12 @@ export class ContentType {
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
}
let newFilePath: string | undefined = await ArticleHelper.createContent(
const newFilePath: string | undefined = await ArticleHelper.createContent(
contentType,
folderPath,
titleValue
);
if (!newFilePath) {
return;
}
@@ -1045,7 +1046,7 @@ export class ContentType {
filePath: string,
clearEmpty: boolean,
contentType: IContentType,
isRoot: boolean = true
isRoot = true
): Promise<any> {
if (obj.fields) {
const titleField = getTitleField();
@@ -1106,7 +1107,7 @@ export class ContentType {
filePath
);
} else if (defaultValue && Array.isArray(defaultValue)) {
let defaultValues = [];
const defaultValues = [];
for (let value of defaultValue as string[]) {
if (typeof value === 'string') {
value = await ContentType.processFieldPlaceholders(
+71 -40
View File
@@ -1,5 +1,5 @@
import { Settings } from './SettingsHelper';
import { CommandType, EnvironmentType } from './../models/PanelSettings';
import { CommandType } from './../models/PanelSettings';
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
import { window, env as vscodeEnv, ProgressLocation, Uri, commands } from 'vscode';
import { ArticleHelper, Logger, MediaHelpers } from '.';
@@ -13,9 +13,10 @@ import { Dashboard } from '../commands/Dashboard';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { ParsedFrontMatter } from '../parsers';
import { SETTING_CUSTOM_SCRIPTS } from '../constants';
import { existsAsync } from '../utils';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { evaluateCommand, existsAsync, getPlatform } from '../utils';
import { LocalizationKey, localize } from '../localization';
import { ScriptAction } from '../models';
import { Copilot } from '../services/Copilot';
export class CustomScript {
/**
@@ -72,7 +73,9 @@ export class CustomScript {
if (!path) {
const editor = window.activeTextEditor;
if (!editor) return;
if (!editor) {
return;
}
articlePath = editor.document.uri.fsPath;
article = ArticleHelper.getFrontMatter(editor);
@@ -99,7 +102,7 @@ export class CustomScript {
);
} else {
Notifications.warning(
l10n.t(LocalizationKey.helpersCustomScriptSingleRunArticleWarning, script.title)
localize(LocalizationKey.helpersCustomScriptSingleRunArticleWarning, script.title)
);
}
}
@@ -115,17 +118,17 @@ export class CustomScript {
if (!folders || folders.length === 0) {
Notifications.warning(
l10n.t(LocalizationKey.helpersCustomScriptBulkRunNoFilesWarning, script.title)
localize(LocalizationKey.helpersCustomScriptBulkRunNoFilesWarning, script.title)
);
return;
}
let output: string[] = [];
const output: string[] = [];
window.withProgress(
{
location: ProgressLocation.Notification,
title: l10n.t(LocalizationKey.helpersCustomScriptExecuting, script.title),
title: localize(LocalizationKey.helpersCustomScriptExecuting, script.title),
cancellable: false
},
async (_, __) => {
@@ -171,7 +174,7 @@ export class CustomScript {
): Promise<void> {
if (!path) {
Notifications.error(
l10n.t(LocalizationKey.helpersCustomScriptRunMediaScriptNoFolderWarning, script.title)
localize(LocalizationKey.helpersCustomScriptRunMediaScriptNoFolderWarning, script.title)
);
return;
}
@@ -180,7 +183,7 @@ export class CustomScript {
window.withProgress(
{
location: ProgressLocation.Notification,
title: l10n.t(LocalizationKey.helpersCustomScriptExecuting, script.title),
title: localize(LocalizationKey.helpersCustomScriptExecuting, script.title),
cancellable: false
},
async () => {
@@ -266,12 +269,7 @@ export class CustomScript {
try {
const data: {
frontmatter?: { [key: string]: any };
fmAction?:
| 'open'
| 'copyMediaMetadata'
| 'copyMediaMetadataAndDelete'
| 'deleteMedia'
| 'fieldAction';
fmAction?: ScriptAction;
fmPath?: string;
fmSourcePath?: string;
fmDestinationPath?: string;
@@ -283,7 +281,9 @@ export class CustomScript {
const editor = window.activeTextEditor;
if (!articlePath) {
if (!editor) return;
if (!editor) {
return;
}
articlePath = editor.document.uri.fsPath;
article = ArticleHelper.getFrontMatter(editor);
@@ -305,7 +305,10 @@ export class CustomScript {
throw new Error(`Couldn't update article.`);
}
Notifications.info(
l10n.t(LocalizationKey.helpersCustomScriptShowOutputFrontMatterSuccess, script.title)
localize(
LocalizationKey.helpersCustomScriptShowOutputFrontMatterSuccess,
script.title
)
);
}
} else if (data.fmAction) {
@@ -341,10 +344,12 @@ export class CustomScript {
window
.showInformationMessage(
`${script.title}: ${output}`,
l10n.t(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction)
localize(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction)
)
.then((value) => {
if (value === l10n.t(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction)) {
if (
value === localize(LocalizationKey.helpersCustomScriptShowOutputCopyOutputAction)
) {
vscodeEnv.clipboard.writeText(output);
}
});
@@ -352,7 +357,7 @@ export class CustomScript {
}
} else {
Notifications.info(
l10n.t(LocalizationKey.helpersCustomScriptShowOutputSuccess, script.title)
localize(LocalizationKey.helpersCustomScriptShowOutputSuccess, script.title)
);
}
}
@@ -369,7 +374,7 @@ export class CustomScript {
wsPath: string,
args: string
): Promise<string> {
const osType = os.type();
const platform = getPlatform();
// Check the command to use
let command = script.nodeBin || 'node';
@@ -377,6 +382,10 @@ export class CustomScript {
command = script.command;
}
if (script.command === CommandType.Node && platform !== 'windows') {
command = await evaluateCommand(CommandType.Node);
}
let scriptPath = join(wsPath, script.script);
if (script.script.includes(WORKSPACE_PLACEHOLDER)) {
scriptPath = Folders.getAbsFilePath(script.script);
@@ -384,19 +393,15 @@ export class CustomScript {
// Check if there is an environments overwrite required
if (script.environments) {
let crntType: EnvironmentType | null = null;
if (osType === 'Windows_NT') {
crntType = 'windows';
} else if (osType === 'Darwin') {
crntType = 'macos';
} else {
crntType = 'linux';
}
const environment = script.environments.find((e) => e.type === crntType);
const environment = script.environments.find((e) => e.type === platform);
if (environment && environment.script && environment.command) {
if (await CustomScript.validateCommand(environment.command)) {
command = environment.command;
if (command === CommandType.Node && platform !== 'windows') {
command = await evaluateCommand(CommandType.Node);
}
scriptPath = join(wsPath, environment.script);
if (environment.script.includes(WORKSPACE_PLACEHOLDER)) {
scriptPath = Folders.getAbsFilePath(environment.script);
@@ -410,21 +415,38 @@ export class CustomScript {
throw new Error(`Script not found: ${scriptPath}`);
}
if (osType === 'Windows_NT' && command.toLowerCase() === 'powershell') {
if (platform === 'windows' && command.toLowerCase() === 'powershell') {
command = `${command} -File`;
}
const fullScript = `${command} "${scriptPath}" ${args}`;
Logger.info(l10n.t(LocalizationKey.helpersCustomScriptExecuting, fullScript));
Logger.info(localize(LocalizationKey.helpersCustomScriptExecuting, fullScript));
const output = await CustomScript.processExecution(fullScript, wsPath);
return output;
}
// Recursive function to process the execution of the script
private static async processExecution(fullScript: string, wsPath: string): Promise<string> {
const output: string = await CustomScript.executeScriptAsync(fullScript, wsPath);
try {
const data = JSON.parse(output);
if (data.questions) {
const { questions, fmAction, fmPrompt } = data as {
questions?: {
name: string;
message: string;
default?: string;
options?: string[];
}[];
fmAction?: ScriptAction;
fmPrompt?: any; // When the 'promptCopilot' action is used
};
if (questions) {
const answers: string[] = [];
for (const question of data.questions) {
for (const question of questions) {
if (question.name && question.message) {
let answer;
if (question.options) {
@@ -452,7 +474,16 @@ export class CustomScript {
if (answers.length > 0) {
const newScript = `${fullScript} ${answers.join(' ')}`;
return await CustomScript.executeScriptAsync(newScript, wsPath);
return await CustomScript.processExecution(newScript, wsPath);
}
} else if (fmAction) {
if (fmAction === 'promptCopilot' && fmPrompt) {
const response = await Copilot.promptCopilot(fmPrompt);
if (response) {
const promptResponse = `promptResponse="${response}"`;
const newScript = `${fullScript} ${promptResponse}`;
return await CustomScript.processExecution(newScript, wsPath);
}
}
}
} catch (error) {
@@ -469,7 +500,7 @@ export class CustomScript {
* @returns
*/
private static async executeScriptAsync(fullScript: string, wsPath: string): Promise<string> {
return new Promise(async (resolve, reject) => {
return new Promise((resolve, reject) => {
exec(fullScript, { cwd: wsPath }, (error, stdout) => {
if (error) {
Logger.error(error.message);
@@ -498,7 +529,7 @@ export class CustomScript {
return true;
} catch (e) {
Logger.error(l10n.t(LocalizationKey.helpersCustomScriptValidateCommandError, command));
Logger.error(localize(LocalizationKey.helpersCustomScriptValidateCommandError, command));
return false;
}
}
+8 -6
View File
@@ -1,5 +1,5 @@
import { GitListener } from './../listeners/general/GitListener';
import { basename, join } from 'path';
import { join } from 'path';
import { workspace } from 'vscode';
import { Folders } from '../commands/Folders';
import { Project } from '../commands/Project';
@@ -8,6 +8,7 @@ import {
ExtensionState,
SETTING_CONTENT_DRAFT_FIELD,
SETTING_CONTENT_FILTERS,
SETTING_CONTENT_GROUPING,
SETTING_CONTENT_SORTING,
SETTING_CONTENT_SORTING_DEFAULT,
SETTING_DASHBOARD_OPENONSTART,
@@ -23,7 +24,6 @@ import {
SETTING_MEDIA_SUPPORTED_MIMETYPES,
SETTING_TAXONOMY_CUSTOM,
SETTING_TEMPLATES_ENABLED,
SETTING_GIT_ENABLED,
SETTING_DASHBOARD_CONTENT_PAGINATION,
SETTING_SNIPPETS_WRAPPER,
SETTING_DASHBOARD_CONTENT_CARD_DATE,
@@ -63,7 +63,7 @@ import { DataListener } from '../listeners/dashboard';
export class DashboardSettings {
private static cachedSettings: ISettings | undefined = undefined;
public static async get(clear: boolean = false) {
public static async get(clear = false) {
if (!this.cachedSettings || clear) {
this.cachedSettings = await this.getSettings();
}
@@ -119,6 +119,8 @@ export class DashboardSettings {
contentFolders: await Folders.get(),
filters:
Settings.get<(FilterType | { title: string; name: string })[]>(SETTING_CONTENT_FILTERS),
grouping:
Settings.get<{ title: string; name: string }[]>(SETTING_CONTENT_GROUPING),
crntFramework: Settings.get<string>(SETTING_FRAMEWORK_ID),
framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [],
@@ -192,9 +194,9 @@ export class DashboardSettings {
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
const folders = Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS);
let clonedFiles = Object.assign([], files);
const clonedFiles = Object.assign([], files);
if (folders) {
for (let folder of folders) {
for (const folder of folders) {
if (!folder.path) {
continue;
}
@@ -218,7 +220,7 @@ export class DashboardSettings {
);
const dataFiles = [...dataJsonFiles, ...dataYmlFiles, ...dataYamlFiles];
for (let dataFile of dataFiles) {
for (const dataFile of dataFiles) {
clonedFiles.push(DataListener.createDataFileObject(dataFile.fsPath, folder));
}
}
+1 -1
View File
@@ -293,7 +293,7 @@ export class Extension {
propKey: string,
propValue: T,
type: 'workspace' | 'global' = 'global',
setState: boolean = false
setState = false
): Promise<void> {
if (this.isFileStorageNeeded(propKey)) {
let storageUri: Uri | undefined = undefined;
+1 -1
View File
@@ -38,7 +38,7 @@ export class FilesHelper {
*/
public static relToAbsPath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
let absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
const absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
return parseWinPath(absPath);
}
+2 -2
View File
@@ -17,7 +17,7 @@ export class ImageHelper {
* @returns
*/
public static allRelToAbs(field: Field, value: string | string[] | undefined) {
let filePath =
const filePath =
window.activeTextEditor?.document.uri.fsPath ||
Preview.filePath ||
ArticleHelper.getActiveFile();
@@ -54,7 +54,7 @@ export class ImageHelper {
*/
public static relToAbs(filePath: string, value: string) {
const wsFolder = Folders.getWorkspaceFolder();
let staticFolder = Folders.getStaticFolderRelativePath();
const staticFolder = Folders.getStaticFolderRelativePath();
if (staticFolder === STATIC_FOLDER_PLACEHOLDER.hexo.placeholder) {
const editor = window.activeTextEditor;
+17 -5
View File
@@ -18,7 +18,7 @@ import {
SETTING_MEDIA_SUPPORTED_MIMETYPES
} from '../constants';
import { SortingOption } from '../dashboardWebView/models';
import { MediaInfo, MediaPaths, SortOrder, SortType } from '../models';
import { BlockFieldData, MediaInfo, MediaPaths, SortOrder, SortType } from '../models';
import { basename, join, parse, dirname, relative } from 'path';
import { statSync } from 'fs';
import { Uri, workspace, window, Position } from 'vscode';
@@ -45,8 +45,9 @@ export class MediaHelpers {
* @returns
*/
public static async getMedia(
page: number = 0,
requestedFolder: string = '',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
page = 0,
requestedFolder = '',
sort: SortingOption | null = null
) {
const wsFolder = Folders.getWorkspaceFolder();
@@ -375,7 +376,18 @@ export class MediaHelpers {
* Insert an image into the front matter or contents
* @param data
*/
public static async insertMediaToMarkdown(data: any) {
public static async insertMediaToMarkdown(data: {
file: string;
relPath: string;
snippet: string;
position: Position;
title?: string;
alt?: string;
caption?: string;
fieldName: string;
parents: string[];
blockData: BlockFieldData;
}) {
if (data?.file && data?.relPath) {
await EditorHelper.showFile(data.file);
Dashboard.resetViewData();
@@ -443,7 +455,7 @@ export class MediaHelpers {
const docType = Wysiwyg.getDocType(filePath);
let snippet = data.snippet || '';
if (!data.Snippet) {
if (!snippet) {
if (docType === 'markdown') {
snippet = `${isFile ? '' : '!'}[${caption}](${FrameworkDetector.relAssetPathUpdate(
relPath,
+9
View File
@@ -132,6 +132,15 @@ export class MediaLibrary {
}
}
public async getAllByPath(path: string) {
try {
const data = await this.db?.getData(path);
return data;
} catch {
return undefined;
}
}
public set(id: string, metadata: any): void {
const fileId = this.parsePath(id);
this.db?.push(fileId, metadata, true);
+1 -1
View File
@@ -162,7 +162,7 @@ export class Notifications {
* @returns
*/
private static shouldShow(level: NotificationType): boolean {
let levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
const levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
if (!levels) {
return true;
+4 -4
View File
@@ -39,7 +39,7 @@ export class Questions {
* @param showWarning
* @returns
*/
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
public static async ContentTitle(showWarning = true): Promise<string | undefined> {
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
let title: string | undefined = '';
const isCopilotInstalled = await Copilot.isInstalled();
@@ -122,7 +122,7 @@ export class Questions {
title: string | undefined,
aiTitles: string[],
isCopilotInstalled: boolean,
showWarning: boolean = true
showWarning = true
): Promise<string | undefined> {
if (title && aiTitles && aiTitles.length > 0) {
const options: QuickPickItem[] = [
@@ -174,7 +174,7 @@ export class Questions {
* @returns
*/
public static async SelectContentFolder(
showWarning: boolean = true
showWarning = true
): Promise<FolderQuickPickItem | undefined> {
let folders = await Folders.get();
folders = folders.filter((f) => !f.disableCreation);
@@ -230,7 +230,7 @@ export class Questions {
*/
public static async SelectContentType(
allowedCts: string[],
showWarning: boolean = true
showWarning = true
): Promise<string | undefined> {
let contentTypes = ContentType.getAll();
if (!contentTypes || contentTypes.length === 0) {
+10 -8
View File
@@ -1,14 +1,16 @@
var illegalRe = /[\/\?<>\\:\*\|"]/g;
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
var reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[\. ]+$/;
const illegalRe = /[/?<>\\:*|"!.,;{}[\]()_+=~`@#$%^&]/g;
// eslint-disable-next-line no-control-regex
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
const reservedRe = /^\.+$/;
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
const windowsTrailingRe = /[. ]+$/;
function sanitize(input: string, replacement: string) {
if (typeof input !== 'string') {
throw new Error('Input must be string');
}
var sanitized = input
const sanitized = input
.replace(illegalRe, replacement)
.replace(controlRe, replacement)
.replace(reservedRe, replacement)
@@ -18,8 +20,8 @@ function sanitize(input: string, replacement: string) {
}
export default function (input: string, options?: any) {
var replacement = (options && options.replacement) || '';
var output = sanitize(input, replacement);
const replacement = (options && options.replacement) || '';
const output = sanitize(input, replacement);
if (replacement === '') {
return output;
}
+15 -19
View File
@@ -43,7 +43,8 @@ import {
SETTING_TAXONOMY_TAGS,
SETTING_TAXONOMY_CATEGORIES,
SETTING_CONTENT_FILTERS,
SETTING_CONTENT_I18N
SETTING_CONTENT_I18N,
SETTING_CONTENT_GROUPING
} from '../constants';
import { Folders } from '../commands/Folders';
import { join, basename, dirname, parse } from 'path';
@@ -67,7 +68,7 @@ export class Settings {
public static globalConfigPath: string | undefined = undefined;
public static globalConfig: any;
private static config: WorkspaceConfiguration;
private static isInitialized: boolean = false;
private static isInitialized = false;
private static listeners: { id: string; callback: (global?: any) => void }[] = [];
private static fileCreationWatcher: FileSystemWatcher | undefined;
private static fileChangeWatcher: FileSystemWatcher | undefined;
@@ -76,7 +77,7 @@ export class Settings {
private static readConfigPromise: Promise<void> | undefined = undefined;
private static project: Project | undefined = undefined;
private static configDebouncer = debounceCallback();
private static hasExtendedConfig: boolean = false;
private static hasExtendedConfig = false;
private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any;
public static async registerCommands() {
@@ -131,6 +132,8 @@ export class Settings {
Settings.config = workspace.getConfiguration(CONFIG_KEY);
});
Logger.info(`Logging level: ${Logger.getLevel()}`);
Settings.onConfigChange();
}
@@ -335,7 +338,7 @@ export class Settings {
/**
* Retrieve a setting from global and local config
*/
public static get<T>(name: string, merging: boolean = false): T | undefined {
public static get<T>(name: string, merging = false): T | undefined {
if (!Settings.config) {
return;
}
@@ -383,11 +386,7 @@ export class Settings {
* @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`.
* @returns A promise that resolves when the setting is updated.
*/
public static async safeUpdate<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
public static async safeUpdate<T>(name: string, value: T, updateGlobal = false): Promise<void> {
if (Settings.hasExtendedConfig) {
const configKey = `${CONFIG_KEY}.${name}`;
@@ -418,11 +417,7 @@ ${JSON.stringify(value, null, 2)}`,
* @param name
* @param value
*/
public static async update<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
public static async update<T>(name: string, value: T, updateGlobal = false): Promise<void> {
const fmConfig = await Settings.projectConfigPath();
if (updateGlobal) {
@@ -571,7 +566,7 @@ ${JSON.stringify(value, null, 2)}`,
*/
public static async updateCustomTaxonomyOptions(id: string, options: string[]) {
const customTaxonomies = Settings.get<CustomTaxonomy[]>(SETTING_TAXONOMY_CUSTOM, true) || [];
let taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
const taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
if (taxIdx !== -1) {
customTaxonomies[taxIdx].options = options;
@@ -753,7 +748,7 @@ ${JSON.stringify(value, null, 2)}`,
Logger.info(`Reading dynamic config file: ${absFilePath}`);
if (absFilePath) {
if (await existsAsync(absFilePath)) {
const configFunction = require(absFilePath);
const configFunction = await import(absFilePath);
const dynamicConfig = await configFunction(
Object.assign({}, Settings.globalConfig)
);
@@ -858,7 +853,7 @@ ${JSON.stringify(value, null, 2)}`,
// We need to loop through the config to make sure the objects and arrays are merged
for (const key in config) {
if (config.hasOwnProperty(key)) {
if (Object.prototype.hasOwnProperty.call(config, key)) {
const value = config[key];
const settingName = key.replace(`${CONFIG_KEY}.`, '');
@@ -875,7 +870,8 @@ ${JSON.stringify(value, null, 2)}`,
settingName === SETTING_GLOBAL_NOTIFICATIONS_DISABLED ||
settingName === SETTING_MEDIA_SUPPORTED_MIMETYPES ||
settingName === SETTING_COMMA_SEPARATED_FIELDS ||
settingName === SETTING_CONTENT_FILTERS
settingName === SETTING_CONTENT_FILTERS ||
settingName === SETTING_CONTENT_GROUPING
) {
if (typeof originalConfig[key] === 'undefined') {
Settings.globalConfig[key] = value;
@@ -1209,7 +1205,7 @@ ${JSON.stringify(value, null, 2)}`,
* Reload the config
* @param debounced
*/
private static async reloadConfig(debounced: boolean = true) {
private static async reloadConfig(debounced = true) {
Logger.info(`Reloading config...`);
// Clear the folder cache as we need to see the latest folders
Folders.clearCached();
+1 -1
View File
@@ -69,7 +69,7 @@ export class SlugHelper {
return '';
}
const punctuationless = value?.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, ' ');
const punctuationless = value?.replace(/[.,-/#!$@%^&*;:{}=\-_`'"~()+?<>]/g, ' ');
// Remove double spaces
return punctuationless?.replace(/\s{2,}/g, ' ');
}
+7 -7
View File
@@ -4,8 +4,8 @@ import { SnippetField } from '../models';
export class SnippetParser {
public static getPlaceholders(
value: string[] | string,
openingTags: string = '[[',
closingTags: string = ']]'
openingTags = '[[',
closingTags = ']]'
): string[] {
const template = SnippetParser.template(value);
const parseTree = Mustache.parse(template, [openingTags, closingTags]);
@@ -29,9 +29,9 @@ export class SnippetParser {
public static render(
value: string[] | string,
data: any,
openingTags: string = '[[',
closingTags: string = ']]'
data: unknown,
openingTags = '[[',
closingTags = ']]'
): string {
const template = SnippetParser.template(value);
return Mustache.render(template, data, undefined, [openingTags, closingTags]);
@@ -40,8 +40,8 @@ export class SnippetParser {
public static getFields(
value: string[] | string,
fields: SnippetField[],
openingTags: string = '[[',
closingTags: string = ']]'
openingTags = '[[',
closingTags = ']]'
) {
const placeholders = SnippetParser.getPlaceholders(value, openingTags, closingTags);
+9 -14
View File
@@ -7,6 +7,7 @@ export class Sorting {
* @returns
*/
public static alphabetically = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
if (a[property] < b[property]) {
return -1;
@@ -24,6 +25,7 @@ export class Sorting {
* @returns
*/
public static numerically = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
return a[property] - b[property];
};
@@ -35,6 +37,7 @@ export class Sorting {
* @returns
*/
public static date = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
@@ -49,13 +52,16 @@ export class Sorting {
* @returns
*/
public static dateWithFallback = (property: string, fallback: string) => {
return (a: any, b: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any): number => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
// Sort by date
var dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
if (dCount) return dCount;
const dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
if (dCount) {
return dCount;
}
// If there is a tie, sort by fallback property
if (a[fallback] < b[fallback]) {
@@ -67,15 +73,4 @@ export class Sorting {
return 0;
};
};
/**
* Sort by number
* @param property
* @returns
*/
public static number = (property: string) => {
return (a: any, b: any) => {
return a[property] - b[property];
};
};
}
+5 -5
View File
@@ -273,7 +273,7 @@ export class TaxonomyHelper {
oldValue: string,
newValue?: string,
pages?: Page[],
needsSettingsUpdate: boolean = true
needsSettingsUpdate = true
) {
// Retrieve all the markdown files
const allFiles = pages
@@ -349,7 +349,7 @@ export class TaxonomyHelper {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = await ArticleHelper.getContentType(article);
let fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
const fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
if (fieldNames.length > 0 && article && article.data) {
const { data } = article;
@@ -484,8 +484,8 @@ export class TaxonomyHelper {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = await ArticleHelper.getContentType(article);
let oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
let newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
const oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
const newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
if (oldFieldNames.length > 0 && newFieldNames.length > 0 && article && article.data) {
const { data } = article;
@@ -559,7 +559,7 @@ export class TaxonomyHelper {
private static getFieldsHierarchy(
taxonomyType: TaxonomyType | string,
contentType: IContentType,
fallback: boolean = false
fallback = false
): string[] {
let fieldNames: string[] = [];
if (taxonomyType === TaxonomyType.Tag) {
+1 -1
View File
@@ -8,7 +8,7 @@ export const decodeBase64 = (dataString: string) => {
const typePart = dataParts[0].split(':').pop() as string;
const dataPart = dataParts.pop() as string;
let response: any = {};
const response: any = {};
response.type = typePart;
response.data = Buffer.from(dataPart, 'base64');
+4 -7
View File
@@ -1,5 +1,4 @@
import { format } from 'date-fns';
import { DateHelper } from './DateHelper';
import { formatInTimezone } from '../utils';
/**
* Replace the datetime placeholders
@@ -17,14 +16,12 @@ export const processDateTimePlaceholders = (value: string, articleDate?: Date) =
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let dateFormat = placeholderParts[1].trim().replace('}}', '');
const dateFormat = placeholderParts[1].trim().replace('}}', '');
if (dateFormat) {
if (dateFormat && typeof dateFormat === 'string') {
value = value.replace(
match,
format(articleDate || new Date(), DateHelper.formatUpdate(dateFormat) as string)
);
const formattedDate = formatInTimezone(articleDate || new Date(), dateFormat);
value = value.replace(match, formattedDate);
} else {
value = value.replace(match, (articleDate || new Date()).toISOString());
}
+2 -2
View File
@@ -25,7 +25,7 @@ export const processFilePrefixPlaceholders = async (value: string, folderPath?:
);
let chars = 3;
let idxValue = files.length + 1;
const idxValue = files.length + 1;
if (value.includes('{{filePrefix.index}}')) {
const regex = new RegExp('{{filePrefix.index}}', 'g');
@@ -40,7 +40,7 @@ export const processFilePrefixPlaceholders = async (value: string, folderPath?:
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let options = placeholderParts[1].trim().replace('}}', '').split(',');
const options = placeholderParts[1].trim().replace('}}', '').split(',');
for (const option of options) {
if (option.startsWith('zeros:')) {
+2 -2
View File
@@ -1,4 +1,4 @@
import { format } from 'date-fns';
import { formatInTimezone } from '../utils';
export const processFmPlaceholders = (value: string, fmData: { [key: string]: any }) => {
// Example: {{fm.date}} or {{fm.date | dateFormat 'DD.MM.YYYY'}}
@@ -27,7 +27,7 @@ export const processFmPlaceholders = (value: string, fmData: { [key: string]: an
// Parse the date value and format it
if (fieldValue) {
const formattedDate = format(new Date(fieldValue), dateFormat);
const formattedDate = formatInTimezone(new Date(fieldValue), dateFormat);
value = value.replace(match, formattedDate);
}
} else if (fieldValue) {
+1 -1
View File
@@ -11,7 +11,7 @@ export const processPathPlaceholders = (
const relPathToken = '{{pathToken.relPath}}';
if (value.includes(relPathToken) && contentFolder?.path) {
const dirName = dirname(filePath);
let relPath = relative(contentFolder.path, dirName);
const relPath = relative(contentFolder.path, dirName);
value = value.replace(relPathToken, relPath);
}
+9 -13
View File
@@ -1,5 +1,4 @@
import { format } from 'date-fns';
import { DateHelper } from './DateHelper';
import { formatInTimezone } from '../utils';
/**
* Replace the time placeholders
@@ -13,10 +12,7 @@ export const processTimePlaceholders = (value: string, dateFormat?: string, arti
const regex = new RegExp('{{now}}', 'g');
if (dateFormat && typeof dateFormat === 'string') {
value = value.replace(
regex,
format(articleDate || new Date(), DateHelper.formatUpdate(dateFormat) as string)
);
value = value.replace(regex, formatInTimezone(articleDate || new Date(), dateFormat));
} else {
value = value.replace(regex, (articleDate || new Date()).toISOString());
}
@@ -24,37 +20,37 @@ export const processTimePlaceholders = (value: string, dateFormat?: string, arti
if (value.includes('{{year}}')) {
const regex = new RegExp('{{year}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'yyyy'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'yyyy'));
}
if (value.includes('{{month}}')) {
const regex = new RegExp('{{month}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'MM'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'MM'));
}
if (value.includes('{{day}}')) {
const regex = new RegExp('{{day}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'dd'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'dd'));
}
if (value.includes('{{hour12}}')) {
const regex = new RegExp('{{hour12}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'hh'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'hh'));
}
if (value.includes('{{hour24}}')) {
const regex = new RegExp('{{hour24}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'HH'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'HH'));
}
if (value.includes('{{ampm}}')) {
const regex = new RegExp('{{ampm}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'aaa'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'aaa'));
}
if (value.includes('{{minute}}')) {
const regex = new RegExp('{{minute}}', 'g');
value = value.replace(regex, format(articleDate || new Date(), 'mm'));
value = value.replace(regex, formatInTimezone(articleDate || new Date(), 'mm'));
}
}
+1
View File
@@ -5,6 +5,7 @@ import { Logger } from '../../helpers/Logger';
import { PostMessageData } from '../../models';
export abstract class BaseListener {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
public static process(msg: PostMessageData) {}
/**
+96 -11
View File
@@ -3,13 +3,15 @@ import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
import { BaseListener } from './BaseListener';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { SortingOption } from '../../dashboardWebView/models';
import { commands, env, Uri } from 'vscode';
import { commands, env, ProgressLocation, Uri, window, workspace } from 'vscode';
import { COMMAND_NAME } from '../../constants';
import * as os from 'os';
import { Folders } from '../../commands';
import { PostMessageData, UnmappedMedia } from '../../models';
import { FilesHelper, MediaLibrary } from '../../helpers';
import { existsAsync, flattenObjectKeys } from '../../utils';
import { join, parse } from 'path';
import { LocalizationKey, localize } from '../../localization';
export class MediaListener extends BaseListener {
private static timers: { [folder: string]: any } = {};
@@ -17,9 +19,10 @@ export class MediaListener extends BaseListener {
public static async process(msg: PostMessageData) {
super.process(msg);
const { page, folder, sorting } = msg.payload ?? {};
switch (msg.command) {
case DashboardMessage.getMedia:
const { page, folder, sorting } = msg?.payload;
this.sendMediaFiles(page, folder, sorting);
break;
case DashboardMessage.refreshMedia:
@@ -53,6 +56,12 @@ export class MediaListener extends BaseListener {
case DashboardMessage.createMediaFolder:
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.payload);
break;
case DashboardMessage.updateMediaFolder:
await this.updateMediaFolder(msg.payload);
break;
case DashboardMessage.deleteMediaFolder:
await this.deleteMediaFolder(msg.payload);
break;
case DashboardMessage.createHexoAssetFolder:
if (msg?.payload.hexoAssetFolderPath) {
Folders.createFolder(msg?.payload.hexoAssetFolderPath);
@@ -61,17 +70,85 @@ export class MediaListener extends BaseListener {
}
}
public static async deleteMediaFolder(msg: { folder: string }) {
if (!msg?.folder) {
return;
}
window.withProgress({
location: ProgressLocation.Notification,
title: localize(LocalizationKey.listenersDashboardMediaListenersDeleteMediaFolderProgressTitle),
cancellable: false
}, async () => {
const folderPath = parse(msg.folder).dir;
const mediaLib = MediaLibrary.getInstance();
const parsedPath = mediaLib.parsePath(msg.folder);
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
for (const fileName of Object.keys(mediaFiles)) {
const filePath = join(msg.folder, fileName);
await mediaLib.remove(filePath);
}
await workspace.fs.delete(Uri.file(msg.folder), { recursive: true, useTrash: false });
await MediaListener.sendMediaFiles(0, folderPath);
});
}
public static async updateMediaFolder(msg: {
folder: string;
wsFolder?: string;
staticFolder?: string;
}) {
if (!msg?.folder) {
return;
}
window.withProgress({
location: ProgressLocation.Notification,
title: localize(LocalizationKey.listenersDashboardMediaListenersUpdateMediaFolderProgressTitle),
cancellable: false
}, async () => {
const folderName = parse(msg.folder).base;
const newFolderName = await window.showInputBox({
prompt: 'Enter new folder name',
value: folderName
});
if (!newFolderName || newFolderName === folderName) {
return;
}
const newFolderPath = join(parse(msg.folder).dir, newFolderName);
// Get all media files from the folder
const mediaLib = MediaLibrary.getInstance();
const parsedPath = mediaLib.parsePath(msg.folder);
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
// Update the folder
await workspace.fs.rename(Uri.file(msg.folder), Uri.file(newFolderPath), { overwrite: false });
// Update the media files
for (const fileName of Object.keys(mediaFiles)) {
const newFilePath = join(newFolderPath, fileName);
const oldFilePath = join(msg.folder, fileName);
await mediaLib.rename(oldFilePath, newFilePath);
}
await this.sendMediaFiles(0, parse(msg.folder).dir);
});
}
/**
* Sends the media files to the dashboard
* @param page
* @param folder
* @param sorting
*/
public static async sendMediaFiles(
page: number = 0,
folder: string = '',
sorting: SortingOption | null = null
) {
public static async sendMediaFiles(page = 0, folder = '', sorting: SortingOption | null = null) {
MediaLibrary.reset();
const files = await MediaHelpers.getMedia(page, folder, sorting);
this.sendMsg(DashboardCommand.media, files);
@@ -143,7 +220,7 @@ export class MediaListener extends BaseListener {
}
if (unmappedFiles && unmappedFiles.length > 0) {
this.sendRequest(command as any, requestId, unmappedFiles);
this.sendRequest(command as DashboardCommand, requestId, unmappedFiles);
}
}
@@ -151,6 +228,7 @@ export class MediaListener extends BaseListener {
* Store the file and send a message after multiple uploads
* @param data
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static async store(data: any) {
try {
const { folder } = data;
@@ -167,7 +245,9 @@ export class MediaListener extends BaseListener {
this.sendMediaFiles(0, folder || '');
delete this.timers[folderPath];
}, 500);
} catch {}
} catch {
// Do nothing
}
}
/**
@@ -178,13 +258,16 @@ export class MediaListener extends BaseListener {
try {
MediaHelpers.deleteFile(data.file);
this.sendMediaFiles(data.page || 0, data.folder || '');
} catch {}
} catch {
// Do nothing
}
}
/**
* Update media metadata
* @param data
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static async update(data: any) {
try {
const { page, folder } = data;
@@ -192,6 +275,8 @@ export class MediaListener extends BaseListener {
await MediaHelpers.updateMetadata(data);
this.sendMediaFiles(page || 0, folder || '');
} catch {}
} catch {
// Do nothing
}
}
}
+3 -3
View File
@@ -97,7 +97,7 @@ export class PagesListener extends BaseListener {
// Recreate all the watchers
for (const folder of folders) {
const folderUri = Uri.parse(folder.path);
let watcher = workspace.createFileSystemWatcher(
const watcher = workspace.createFileSystemWatcher(
new RelativePattern(folderUri, '**/*'),
false,
false,
@@ -200,7 +200,7 @@ export class PagesListener extends BaseListener {
/**
* Retrieve all the markdown pages
*/
public static async getPagesData(clear: boolean = false, cb?: (pages: Page[]) => void) {
public static async getPagesData(clear = false, cb?: (pages: Page[]) => void) {
const ext = Extension.getInstance();
// Get data from the cache
@@ -257,7 +257,7 @@ export class PagesListener extends BaseListener {
*/
private static async createSearchIndex(pages: Page[]) {
const pagesIndex = Fuse.createIndex(
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType'],
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType', 'fmLocale.locale'],
pages
);
await Extension.getInstance().setState(
+2 -2
View File
@@ -198,7 +198,7 @@ export class SettingsListener extends BaseListener {
/**
* Retrieve the settings for the dashboard
*/
public static async getSettings(clear: boolean = false) {
public static async getSettings(clear = false) {
Logger.verbose(`SettingsListener:getSettings:start - clear: ${clear}`);
const settings = await DashboardSettings.get(clear);
Logger.verbose(
@@ -315,7 +315,7 @@ export class SettingsListener extends BaseListener {
private static async copyTemplateFiles(
files: [string, FileType][],
templateFileLocation: string,
extRelPath: string = ''
extRelPath = ''
) {
const wsFolder = Folders.getWorkspaceFolder();
if (!wsFolder) {
+7 -2
View File
@@ -12,7 +12,7 @@ import {
} from '../../constants';
import { SettingsListener } from './SettingsListener';
import { Terminal } from '../../services';
import { existsAsync, readFileAsync } from '../../utils';
import { evaluateCommand, existsAsync, getPlatform, readFileAsync } from '../../utils';
import { join } from 'path';
export class SsgListener extends BaseListener {
@@ -170,7 +170,12 @@ export class SsgListener extends BaseListener {
workspace.fs.copy(scriptPath, tempScriptPath, { overwrite: true });
}
const fullScript = `node "${tempScriptPath.fsPath}" "${contentConfigFile.fsPath}"`;
let nodeExecPath = 'node';
const platform = getPlatform();
if (platform !== 'windows') {
nodeExecPath = await evaluateCommand('node');
}
const fullScript = `${nodeExecPath} "${tempScriptPath.fsPath}" "${contentConfigFile.fsPath}"`;
try {
const result: string = await SsgListener.executeScript(fullScript, wsFolder?.fsPath || '');
+11 -4
View File
@@ -30,6 +30,8 @@ import { Event, commands, extensions } from 'vscode';
import { GitAPIState, GitRepository, PostMessageData } from '../../models';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
export class GitListener {
private static gitAPI: {
@@ -39,7 +41,7 @@ export class GitListener {
getAPI: (version: number) => any;
repositories: GitRepository[];
} | null = null;
private static isRegistered: boolean = false;
private static isRegistered = false;
private static client: SimpleGit | null = null;
private static subClient: SimpleGit | null = null;
private static repository: GitRepository | null = null;
@@ -123,6 +125,7 @@ export class GitListener {
break;
case GeneralCommands.toVSCode.git.selectBranch:
this.selectBranch();
break;
case GeneralCommands.toVSCode.git.isRepo:
this.checkIsGitRepo(msg.command, msg.requestId);
break;
@@ -135,7 +138,11 @@ export class GitListener {
}
const isRepo = await GitListener.isGitRepository();
Dashboard.postWebviewMessage({ command: command as any, payload: isRepo, requestId });
Dashboard.postWebviewMessage({
command: command as DashboardCommand | DashboardMessage,
payload: isRepo,
requestId
});
}
/**
@@ -152,7 +159,7 @@ export class GitListener {
* @param commitMsg The commit message for the push operation.
* @param isSync Determines whether to perform a sync operation (default: true) or a fetch operation.
*/
public static async sync(commitMsg?: string, isSync: boolean = true) {
public static async sync(commitMsg?: string, isSync = true) {
try {
this.sendMsg(GeneralCommands.toWebview.git.syncingStart, isSync ? 'syncing' : 'fetching');
@@ -320,7 +327,7 @@ export class GitListener {
* @param submoduleFolder The path to the submodule folder.
* @returns The Git client instance or null if it cannot be retrieved.
*/
private static getClient(submoduleFolder: string = ''): SimpleGit | null {
private static getClient(submoduleFolder = ''): SimpleGit | null {
if (!submoduleFolder && this.client) {
return this.client;
} else if (submoduleFolder && this.subClient) {
+2 -1
View File
@@ -5,7 +5,8 @@ import { Command } from '../../panelWebView/Command';
import { PostMessageData } from '../../models';
export abstract class BaseListener {
public static process(msg: PostMessageData) {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
public static process(_: PostMessageData) {}
/**
* Send a message to the webview
+13 -5
View File
@@ -115,7 +115,12 @@ export class DataListener extends BaseListener {
}
private static async copilotSuggestTitle(command: string, requestId?: string, title?: string) {
if (!command || !requestId || !title) {
if (!command || !requestId) {
return;
}
if (!title) {
this.sendRequestError(command, requestId, 'No title provided');
return;
}
@@ -349,7 +354,7 @@ export class DataListener extends BaseListener {
metadata.articleDetails = articleDetails;
}
let updatedMetadata = Object.assign({}, metadata);
const updatedMetadata = Object.assign({}, metadata);
if (commaSeparated) {
for (const key of commaSeparated) {
if (updatedMetadata[key] && typeof updatedMetadata[key] === 'string') {
@@ -587,13 +592,14 @@ export class DataListener extends BaseListener {
* @returns
*/
public static async getParentObject(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
article: ParsedFrontMatter,
parents: string[] | undefined,
blockData?: BlockFieldData
) {
let parentObj = data;
let allParents = Object.assign([], parents);
const allParents = Object.assign([], parents);
const contentType = await ArticleHelper.getContentType(article);
let selectedIndexes: number[] = [];
if (blockData?.selectedIndex) {
@@ -611,7 +617,7 @@ export class DataListener extends BaseListener {
parentObj = article.data;
// Loop through the parents of the block field
for (const parent of blockData?.parentFields) {
for (const parent of blockData?.parentFields ?? []) {
if (!parentObj) {
continue;
}
@@ -769,6 +775,7 @@ export class DataListener extends BaseListener {
articleData: {
field: string;
value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: { [key: string]: any };
contentType?: IContentType;
},
@@ -778,7 +785,8 @@ export class DataListener extends BaseListener {
return;
}
let { field, value, data, contentType } = articleData;
const { field, data, contentType } = articleData;
let { value } = articleData;
value = value || '';
const valueBefore = value;
+29 -8
View File
@@ -1,3 +1,4 @@
import { i18n } from '../../commands';
import { ExtensionState } from '../../constants';
import { Page } from '../../dashboardWebView/models';
import { Extension } from '../../helpers';
@@ -29,14 +30,32 @@ export class FieldsListener extends BaseListener {
* @param payload
* @returns
*/
private static async searchByType(command: string, requestId?: string, type?: string) {
if (!type || !requestId) {
private static async searchByType(
command: string,
requestId?: string,
data?: { type?: string; sameLocale?: boolean; activePath?: string }
) {
if (!data?.type || !data?.activePath || !requestId) {
return;
}
const isLocaleEnabled = await i18n.isLocaleEnabled(data.activePath);
const activeLocale = await i18n.getLocale(data.activePath);
if (isLocaleEnabled && !activeLocale?.locale) {
return;
}
PagesListener.getPagesData(false, async (pages) => {
const fuseKeys: Fuse.FuseOptionKey[] = [{ name: 'fmContentType', weight: 1 }];
if (isLocaleEnabled && data.sameLocale) {
fuseKeys.push({ name: 'fmLocale.locale', weight: 1 });
}
const fuseOptions: Fuse.IFuseOptions<Page> = {
keys: [{ name: 'fmContentType', weight: 1 }]
keys: fuseKeys,
findAllMatches: true,
threshold: 0
};
const pagesIndex = await Extension.getInstance().getState<Fuse.FuseIndex<Page>>(
@@ -45,12 +64,14 @@ export class FieldsListener extends BaseListener {
);
const fuseIndex = Fuse.parseIndex(pagesIndex);
const fuse = new Fuse(pages || [], fuseOptions, fuseIndex);
const andExpression: Fuse.Expression[] = [{ fmContentType: data.type ?? '' }];
if (isLocaleEnabled && activeLocale?.locale && data.sameLocale) {
andExpression.push({ 'fmLocale.locale': activeLocale.locale });
}
const results = fuse.search({
$and: [
{
fmContentType: type
}
]
$and: andExpression
});
const pageResults = results.map((page) => page.item);
+51 -23
View File
@@ -492,7 +492,7 @@ export enum LocalizationKey {
*/
dashboardDataViewDataViewCloseSelectedDataFile = 'dashboard.dataView.dataView.closeSelectedDataFile',
/**
* Select your date type first
* Select your data type first
*/
dashboardDataViewEmptyViewHeading = 'dashboard.dataView.emptyView.heading',
/**
@@ -616,7 +616,7 @@ export enum LocalizationKey {
*/
dashboardHeaderPaginationPrevious = 'dashboard.header.pagination.previous',
/**
* next
* Next
*/
dashboardHeaderPaginationNext = 'dashboard.header.pagination.next',
/**
@@ -807,6 +807,10 @@ export enum LocalizationKey {
* Public directory
*/
dashboardMediaFolderItemPublicDirectory = 'dashboard.media.folderItem.publicDirectory',
/**
* Are you sure you want to delete the folder ({0})?
*/
dashboardMediaFolderItemDeleteDescription = 'dashboard.media.folderItem.deleteDescription',
/**
* Insert image
*/
@@ -1448,18 +1452,6 @@ export enum LocalizationKey {
* Actions
*/
panelActionsTitle = 'panel.actions.title',
/**
* More details
*/
panelArticleDetailsTitle = 'panel.articleDetails.title',
/**
* Type
*/
panelArticleDetailsType = 'panel.articleDetails.type',
/**
* Total
*/
panelArticleDetailsTotal = 'panel.articleDetails.total',
/**
* Headings
*/
@@ -1613,17 +1605,29 @@ export enum LocalizationKey {
*/
panelSeoDetailsRecommended = 'panel.seoDetails.recommended',
/**
* Keyword usage {0} *
* Checks
*/
panelSeoKeywordInfoDensity = 'panel.seoKeywordInfo.density',
panelSeoKeywordsChecks = 'panel.seoKeywords.checks',
/**
* Used in heading(s)
* Frequency
*/
panelSeoKeywordsDensityTableTitle = 'panel.seoKeywords.density.tableTitle',
/**
* Keyword density
*/
panelSeoKeywordsDensity = 'panel.seoKeywords.density',
/**
* Heading(s)
*/
panelSeoKeywordInfoValidInfoLabel = 'panel.seoKeywordInfo.validInfo.label',
/**
* Content
*/
panelSeoKeywordInfoValidInfoContent = 'panel.seoKeywordInfo.validInfo.content',
/**
* Recommended frequency: 0.75% - 1.5%
*/
panelSeoKeywordInfoDensityTooltip = 'panel.seoKeywordInfo.density.tooltip',
/**
* Keywords
*/
@@ -1639,19 +1643,15 @@ export enum LocalizationKey {
/**
* * A keyword density of 1-1.5% is sufficient in most cases.
*/
panelSeoKeywordsDensity = 'panel.seoKeywords.density',
panelSeoKeywordsDensityDescription = 'panel.seoKeywords.density.description',
/**
* Recommendations
* Insights
*/
panelSeoStatusTitle = 'panel.seoStatus.title',
/**
* Property
*/
panelSeoStatusHeaderProperty = 'panel.seoStatus.header.property',
/**
* Length
*/
panelSeoStatusHeaderLength = 'panel.seoStatus.header.length',
/**
* Valid
*/
@@ -1884,6 +1884,26 @@ export enum LocalizationKey {
* To which locale do you want to create a new content?
*/
commandsI18nCreateQuickPickPlaceHolder = 'commands.i18n.create.quickPick.placeHolder',
/**
* Open or create translation
*/
commandsI18nCreateOrOpenQuickPickTitle = 'commands.i18n.createOrOpen.quickPick.title',
/**
* Existing translations
*/
commandsI18nCreateOrOpenQuickPickCategoryExisting = 'commands.i18n.createOrOpen.quickPick.category.existing',
/**
* Open "{0}"
*/
commandsI18nCreateOrOpenQuickPickActionOpen = 'commands.i18n.createOrOpen.quickPick.action.open',
/**
* New translations
*/
commandsI18nCreateOrOpenQuickPickCategoryNew = 'commands.i18n.createOrOpen.quickPick.category.new',
/**
* Create "{0}"
*/
commandsI18nCreateOrOpenQuickPickActionCreate = 'commands.i18n.createOrOpen.quickPick.action.create',
/**
* Translating content...
*/
@@ -2556,6 +2576,14 @@ export enum LocalizationKey {
* Could not unpin item.
*/
listenersDashboardDashboardListenerPinItemCoundNotUnPinError = 'listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error',
/**
* Deleting folder...
*/
listenersDashboardMediaListenersDeleteMediaFolderProgressTitle = 'listeners.dashboard.mediaListeners.deleteMediaFolder.progress.title',
/**
* Updating folder...
*/
listenersDashboardMediaListenersUpdateMediaFolderProgressTitle = 'listeners.dashboard.mediaListeners.updateMediaFolder.progress.title',
/**
* Template files copied.
*/

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