forked from iarv/vscode-front-matter
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88cad8caa2 | |||
| a04d56fbde | |||
| ec86b079a6 | |||
| 838ced0560 | |||
| 1c269db91d | |||
| 1ed5131abe | |||
| d944319d53 | |||
| 146bbbf6a1 | |||
| 7bfc72469d | |||
| 324184964b | |||
| 1f6ea6ac20 | |||
| 5a45fdc94f | |||
| b043c22437 | |||
| b48e34ecb0 | |||
| 3bdae40ff0 | |||
| 94df672f4c | |||
| 98c5b56310 | |||
| f38144b8a7 | |||
| 231bd89619 | |||
| 4907a7aaa9 | |||
| 2dc4865581 | |||
| f1ae0d60cc | |||
| 2f76de2a28 | |||
| d7282b18eb | |||
| eb22a97198 | |||
| 42fe1c2887 | |||
| c02275d20b | |||
| 147823bfd0 | |||
| a7f183b6cc | |||
| e10ee11f0e | |||
| fca8d260d5 | |||
| 8cecf8d8be | |||
| 22ce41c3eb | |||
| cb649a9a97 | |||
| 65d430b7cf | |||
| f1f0e0ab58 | |||
| 5f7f847ff8 | |||
| 2c4dbeb1eb | |||
| 17164df11f | |||
| 228c46084d | |||
| e838f18abc | |||
| 3d6359bc2e | |||
| 57b710cc61 | |||
| 7796d52ff9 | |||
| f8f539be0d | |||
| fc96c8922c | |||
| c84af8493b | |||
| 6f288ff757 | |||
| 0e04e687fa | |||
| cec3cbee3a | |||
| c6f40194b4 |
Vendored
-6
@@ -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
|
||||
},
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -258,9 +258,6 @@
|
||||
"panel.fields.textField.limit": "Feldgrenze erreicht {0}",
|
||||
"panel.fields.wrapperField.unknown": "Unbekannter Feldtyp: {0}",
|
||||
"panel.actions.title": "Aktionen",
|
||||
"panel.articleDetails.title": "Weitere Details",
|
||||
"panel.articleDetails.type": "Typ",
|
||||
"panel.articleDetails.total": "Gesamt",
|
||||
"panel.articleDetails.headings": "Überschriften",
|
||||
"panel.articleDetails.paragraphs": "Absätze",
|
||||
"panel.articleDetails.internalLinks": "Interne Links",
|
||||
@@ -299,16 +296,13 @@
|
||||
"panel.publishAction.publish": "Veröffentlichen",
|
||||
"panel.publishAction.unpublish": "Zurück zu Entwurf",
|
||||
"panel.seoDetails.recommended": "Empfohlen",
|
||||
"panel.seoKeywordInfo.density": "Stichwortdichte {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Verwendet in Überschrift(en)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Inhalt",
|
||||
"panel.seoKeywords.title": "Stichwörter",
|
||||
"panel.seoKeywords.header.keyword": "Stichwort",
|
||||
"panel.seoKeywords.header.details": "Details",
|
||||
"panel.seoKeywords.density": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.",
|
||||
"panel.seoStatus.title": "Empfehlungen",
|
||||
"panel.seoKeywords.density.description": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.",
|
||||
"panel.seoStatus.header.property": "Eigenschaft",
|
||||
"panel.seoStatus.header.length": "Länge",
|
||||
"panel.seoStatus.header.valid": "Gültig",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} Zeichen",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} Wörter",
|
||||
|
||||
@@ -263,9 +263,6 @@
|
||||
"panel.fields.textField.limit": "Limite de champ atteinte {0}",
|
||||
"panel.fields.wrapperField.unknown": "Type de champ inconnu : {0}",
|
||||
"panel.actions.title": "Actions",
|
||||
"panel.articleDetails.title": "Plus de détails",
|
||||
"panel.articleDetails.type": "Type",
|
||||
"panel.articleDetails.total": "Total",
|
||||
"panel.articleDetails.headings": "En-têtes",
|
||||
"panel.articleDetails.paragraphs": "Paragraphes",
|
||||
"panel.articleDetails.internalLinks": "Liens internes",
|
||||
@@ -304,16 +301,13 @@
|
||||
"panel.publishAction.publish": "Publié",
|
||||
"panel.publishAction.unpublish": "Retourner au brouillon",
|
||||
"panel.seoDetails.recommended": "Recommandé",
|
||||
"panel.seoKeywordInfo.density": "Utilisation du mot clé {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Utilisé dans le ou les en-tête(s)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Contenu",
|
||||
"panel.seoKeywords.title": "Mot-clés",
|
||||
"panel.seoKeywords.header.keyword": "Mot-clé",
|
||||
"panel.seoKeywords.header.details": "Détails",
|
||||
"panel.seoKeywords.density": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas",
|
||||
"panel.seoStatus.title": "Recommandations",
|
||||
"panel.seoKeywords.density.description": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas",
|
||||
"panel.seoStatus.header.property": "Propriété",
|
||||
"panel.seoStatus.header.length": "Longueur",
|
||||
"panel.seoStatus.header.valid": "Valide",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} caractères",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} mots",
|
||||
|
||||
@@ -263,9 +263,6 @@
|
||||
"panel.fields.textField.limit": "Limite di campi raggiunto {0}",
|
||||
"panel.fields.wrapperField.unknown": "Tipo di campo sconosciuto: {0}",
|
||||
"panel.actions.title": "Azioni",
|
||||
"panel.articleDetails.title": "Più dettagli",
|
||||
"panel.articleDetails.type": "Digitare",
|
||||
"panel.articleDetails.total": "Totale",
|
||||
"panel.articleDetails.headings": "Intestazioni",
|
||||
"panel.articleDetails.paragraphs": "Paragrafi",
|
||||
"panel.articleDetails.internalLinks": "Collegamenti esterni",
|
||||
@@ -304,16 +301,13 @@
|
||||
"panel.publishAction.publish": "Pubblica",
|
||||
"panel.publishAction.unpublish": "Tornare alla bozza",
|
||||
"panel.seoDetails.recommended": "Raccomandato",
|
||||
"panel.seoKeywordInfo.density": "Utilizzo delle parole chiave {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Utilizzato nelle rubriche",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Contenuto",
|
||||
"panel.seoKeywords.title": "Parole chiavi",
|
||||
"panel.seoKeywords.header.keyword": "Parola chiave",
|
||||
"panel.seoKeywords.header.details": "Dettagli",
|
||||
"panel.seoKeywords.density": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.",
|
||||
"panel.seoStatus.title": "Consigli",
|
||||
"panel.seoKeywords.density.description": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.",
|
||||
"panel.seoStatus.header.property": "Proprietà",
|
||||
"panel.seoStatus.header.length": "Lunghezza",
|
||||
"panel.seoStatus.header.valid": "Valido",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} caratteri",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} parole",
|
||||
|
||||
@@ -438,9 +438,6 @@
|
||||
|
||||
"panel.actions.title": "コマンド",
|
||||
|
||||
"panel.articleDetails.title": "詳細",
|
||||
"panel.articleDetails.type": "項目",
|
||||
"panel.articleDetails.total": "数",
|
||||
"panel.articleDetails.headings": "見出し",
|
||||
"panel.articleDetails.paragraphs": "パラグラフ",
|
||||
"panel.articleDetails.internalLinks": "内部リンク",
|
||||
@@ -489,18 +486,15 @@
|
||||
|
||||
"panel.seoDetails.recommended": "推奨",
|
||||
|
||||
"panel.seoKeywordInfo.density": "キーワード出現率 {0} *",
|
||||
"panel.seoKeywordInfo.validInfo.label": "見出しへの利用",
|
||||
"panel.seoKeywordInfo.validInfo.content": "本文",
|
||||
|
||||
"panel.seoKeywords.title": "キーワード",
|
||||
"panel.seoKeywords.header.keyword": "キーワード",
|
||||
"panel.seoKeywords.header.details": "詳細",
|
||||
"panel.seoKeywords.density": "* キーワード出現率は通常1~1.5%で十分です。",
|
||||
"panel.seoKeywords.density.description": "* キーワード出現率は通常1~1.5%で十分です。",
|
||||
|
||||
"panel.seoStatus.title": "推奨項目",
|
||||
"panel.seoStatus.header.property": "項目",
|
||||
"panel.seoStatus.header.length": "長さ",
|
||||
"panel.seoStatus.header.valid": "有効",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} 文字",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} 語",
|
||||
|
||||
+13
-10
@@ -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",
|
||||
@@ -589,7 +589,7 @@
|
||||
"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}",
|
||||
"commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview",
|
||||
|
||||
@@ -776,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.",
|
||||
|
||||
Generated
+523
-237
File diff suppressed because it is too large
Load Diff
+239
-164
@@ -3,14 +3,15 @@
|
||||
"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.5.1",
|
||||
"version": "10.7.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
"color": "#0e131f",
|
||||
"theme": "dark"
|
||||
},
|
||||
"badges": [{
|
||||
"badges": [
|
||||
{
|
||||
"description": "version",
|
||||
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
|
||||
"href": "https://github.com/estruyf/vscode-front-matter"
|
||||
@@ -31,7 +32,7 @@
|
||||
"l10n": "./l10n",
|
||||
"categories": [
|
||||
"AI",
|
||||
"Other"
|
||||
"Visualization"
|
||||
],
|
||||
"keywords": [
|
||||
"Front Matter",
|
||||
@@ -70,7 +71,8 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [{
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
@@ -94,19 +96,23 @@
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}]
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"frontmatter-explorer": [{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}]
|
||||
"frontmatter-explorer": [
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "%settings.configuration.title%",
|
||||
@@ -174,7 +180,8 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -190,7 +197,8 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -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": [],
|
||||
@@ -550,7 +586,8 @@
|
||||
"categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [{
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"contentFolders",
|
||||
@@ -624,7 +661,8 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -820,7 +858,8 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -888,7 +927,8 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -1056,7 +1096,7 @@
|
||||
"error"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.global.notifications.markdownDescription%",
|
||||
"scope": "Templates"
|
||||
"scope": "Global"
|
||||
},
|
||||
"frontMatter.global.disabledNotifications": {
|
||||
"type": "array",
|
||||
@@ -1066,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": "",
|
||||
@@ -1129,26 +1175,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.media.supportedMimeTypes": {
|
||||
@@ -1251,7 +1300,8 @@
|
||||
"fileType": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -1397,7 +1447,8 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1603,7 +1654,8 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1815,48 +1867,51 @@
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
@@ -1869,7 +1924,8 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -2068,7 +2124,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [{
|
||||
"commands": [
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2409,44 +2466,49 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"submenus": [{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}],
|
||||
"submenus": [
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"webview/context": [{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}],
|
||||
"editor/title": [{
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
"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.createOrOpen",
|
||||
@@ -2456,37 +2518,37 @@
|
||||
{
|
||||
"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",
|
||||
@@ -2504,11 +2566,14 @@
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}],
|
||||
"frontmatter.submenu": [{
|
||||
"explorer/context": [
|
||||
{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}
|
||||
],
|
||||
"frontmatter.submenu": [
|
||||
{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "explorerResourceIsFolder",
|
||||
"group": "frontmatter@1"
|
||||
@@ -2524,7 +2589,8 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [{
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2701,7 +2767,8 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [{
|
||||
"view/title": [
|
||||
{
|
||||
"command": "frontMatter.docs",
|
||||
"group": "navigation@-1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2738,13 +2805,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"languages": [{
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}],
|
||||
"grammars": [{
|
||||
"languages": [
|
||||
{
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
@@ -2757,45 +2827,48 @@
|
||||
"path": "./syntaxes/frontmatter-output.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"walkthroughs": [{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [
|
||||
{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
|
||||
@@ -2855,7 +2928,8 @@
|
||||
"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",
|
||||
@@ -2893,6 +2967,7 @@
|
||||
"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",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.0",
|
||||
@@ -2931,4 +3006,4 @@
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,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",
|
||||
@@ -166,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)",
|
||||
|
||||
|
||||
+16
-8
@@ -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,15 +377,15 @@ 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);
|
||||
|
||||
// Is article located in one of the content folders
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
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'
|
||||
|
||||
@@ -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';
|
||||
@@ -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) {
|
||||
@@ -861,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
|
||||
});
|
||||
|
||||
@@ -904,7 +903,7 @@ export class Folders {
|
||||
pattern = isWindows() ? parseWinPath(pattern) : pattern;
|
||||
const files = await glob(pattern, {
|
||||
ignore: [
|
||||
'node_modules/**',
|
||||
'**/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
|
||||
|
||||
@@ -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} />
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -42,6 +42,8 @@ export enum DashboardMessage {
|
||||
insertMedia = 'insertMedia',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
updateMediaFolder = 'updateMediaFolder',
|
||||
deleteMediaFolder = 'deleteMediaFolder',
|
||||
insertFile = 'insertFile',
|
||||
createHexoAssetFolder = 'createHexoAssetFolder',
|
||||
getUnmappedMedia = 'getUnmappedMedia',
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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,17 +24,35 @@ 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) => {
|
||||
const clone = Object.assign({}, prev);
|
||||
if (!clone[filterName] && value) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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 { }
|
||||
@@ -12,28 +11,34 @@ export interface IGroupingProps { }
|
||||
export const Grouping: React.FunctionComponent<
|
||||
IGroupingProps
|
||||
> = () => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const [group, setGroup] = useRecoilState(GroupingAtom);
|
||||
const pages = useRecoilValue(AllPagesAtom);
|
||||
|
||||
const GROUP_OPTIONS = React.useMemo(() => {
|
||||
const 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) => (
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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(() => {
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {}
|
||||
});
|
||||
|
||||
@@ -98,4 +98,17 @@ export const updateCssVariables = (isDarkTheme = 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)'
|
||||
);
|
||||
};
|
||||
|
||||
+4
-10
@@ -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,
|
||||
@@ -49,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) {
|
||||
@@ -121,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();
|
||||
@@ -181,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());
|
||||
|
||||
@@ -241,16 +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(() => {
|
||||
|
||||
+63
-36
@@ -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 {
|
||||
/**
|
||||
@@ -101,7 +102,7 @@ export class CustomScript {
|
||||
);
|
||||
} else {
|
||||
Notifications.warning(
|
||||
l10n.t(LocalizationKey.helpersCustomScriptSingleRunArticleWarning, script.title)
|
||||
localize(LocalizationKey.helpersCustomScriptSingleRunArticleWarning, script.title)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -117,7 +118,7 @@ export class CustomScript {
|
||||
|
||||
if (!folders || folders.length === 0) {
|
||||
Notifications.warning(
|
||||
l10n.t(LocalizationKey.helpersCustomScriptBulkRunNoFilesWarning, script.title)
|
||||
localize(LocalizationKey.helpersCustomScriptBulkRunNoFilesWarning, script.title)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +128,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 (_, __) => {
|
||||
@@ -173,7 +174,7 @@ export class CustomScript {
|
||||
): Promise<void> {
|
||||
if (!path) {
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersCustomScriptRunMediaScriptNoFolderWarning, script.title)
|
||||
localize(LocalizationKey.helpersCustomScriptRunMediaScriptNoFolderWarning, script.title)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -182,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 () => {
|
||||
@@ -268,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;
|
||||
@@ -309,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) {
|
||||
@@ -345,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);
|
||||
}
|
||||
});
|
||||
@@ -356,7 +357,7 @@ export class CustomScript {
|
||||
}
|
||||
} else {
|
||||
Notifications.info(
|
||||
l10n.t(LocalizationKey.helpersCustomScriptShowOutputSuccess, script.title)
|
||||
localize(LocalizationKey.helpersCustomScriptShowOutputSuccess, script.title)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -373,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';
|
||||
@@ -381,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);
|
||||
@@ -388,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);
|
||||
@@ -414,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) {
|
||||
@@ -456,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) {
|
||||
@@ -502,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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) || [],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
@@ -131,6 +132,8 @@ export class Settings {
|
||||
Settings.config = workspace.getConfiguration(CONFIG_KEY);
|
||||
});
|
||||
|
||||
Logger.info(`Logging level: ${Logger.getLevel()}`);
|
||||
|
||||
Settings.onConfigChange();
|
||||
}
|
||||
|
||||
@@ -867,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;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { format } from 'date-fns';
|
||||
import { DateHelper } from './DateHelper';
|
||||
import { formatInTimezone } from '../utils';
|
||||
|
||||
/**
|
||||
* Replace the datetime placeholders
|
||||
@@ -21,10 +20,8 @@ export const processDateTimePlaceholders = (value: string, articleDate?: Date) =
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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,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'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 } = {};
|
||||
@@ -54,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);
|
||||
@@ -62,6 +70,78 @@ 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
|
||||
|
||||
@@ -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 || '');
|
||||
|
||||
@@ -39,17 +39,21 @@ export class FieldsListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLocaleEnabled = await i18n.isLocaleEnabled(data.activePath);
|
||||
const activeLocale = await i18n.getLocale(data.activePath);
|
||||
if (!activeLocale?.locale) {
|
||||
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 },
|
||||
...(data.sameLocale ? [{ name: 'fmLocale.locale', weight: 1 }] : [])
|
||||
],
|
||||
keys: fuseKeys,
|
||||
findAllMatches: true,
|
||||
threshold: 0
|
||||
};
|
||||
@@ -60,11 +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: data.type! },
|
||||
...(data.sameLocale ? [{ 'fmLocale.locale': activeLocale.locale }] : [])
|
||||
]
|
||||
$and: andExpression
|
||||
});
|
||||
const pageResults = results.map((page) => page.item);
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -2576,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.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type ScriptAction =
|
||||
| 'open'
|
||||
| 'copyMediaMetadata'
|
||||
| 'copyMediaMetadataAndDelete'
|
||||
| 'deleteMedia'
|
||||
| 'fieldAction'
|
||||
| 'promptCopilot';
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ShellSetting {
|
||||
path: string;
|
||||
}
|
||||
@@ -22,6 +22,8 @@ export * from './Mode';
|
||||
export * from './PanelSettings';
|
||||
export * from './PostMessageData';
|
||||
export * from './Project';
|
||||
export * from './ScriptAction';
|
||||
export * from './ShellSetting';
|
||||
export * from './Snippets';
|
||||
export * from './SortOrder';
|
||||
export * from './SortType';
|
||||
|
||||
@@ -124,12 +124,26 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = () => {
|
||||
<GitAction settings={settings} />
|
||||
</FeatureFlag>
|
||||
|
||||
{!loading && (<CustomView metadata={metadata} />)}
|
||||
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.globalSettings}>
|
||||
<GlobalSettings settings={settings} isBase={!metadata} />
|
||||
</FeatureFlag>
|
||||
|
||||
{
|
||||
!loading && metadata && (
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.metadata}>
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
{!loading && (<CustomView metadata={metadata} />)}
|
||||
|
||||
{
|
||||
!loading && metadata && settings && settings.seo && (
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.seo}>
|
||||
@@ -153,20 +167,6 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = () => {
|
||||
</FeatureFlag>
|
||||
)}
|
||||
|
||||
{
|
||||
!loading && metadata && (
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.metadata}>
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.recentlyModified}>
|
||||
<FolderAndFiles data={folderAndFiles} isBase={!metadata} />
|
||||
</FeatureFlag>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
|
||||
export interface IArticleDetailsProps {
|
||||
details: {
|
||||
@@ -22,59 +22,42 @@ const ArticleDetails: React.FunctionComponent<IArticleDetailsProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`seo__status__details valid`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelArticleDetailsTitle)}</h4>
|
||||
<>
|
||||
{details?.headings !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsHeadings)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.headings}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsType)}
|
||||
</VSCodeTableHead>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelArticleDetailsTotal)}
|
||||
</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
{details?.paragraphs !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsParagraphs)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.paragraphs}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
<VSCodeTableBody>
|
||||
{details?.headings !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsHeadings)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.headings}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
{details?.internalLinks !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.internalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.paragraphs !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsParagraphs)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.paragraphs}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
{details?.externalLinks !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.externalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.internalLinks !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.internalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.externalLinks !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.externalLinks}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
|
||||
{details?.images !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsImages)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.images}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
</div>
|
||||
{details?.images !== undefined && (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{l10n.t(LocalizationKey.panelArticleDetailsImages)}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{details.images}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
}, [icon]);
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-between w-full mb-2'>
|
||||
<div className='field__title flex items-center justify-between w-full mb-2'>
|
||||
<label className={`metadata_field__label text-base text-[var(--vscode-foreground)] ${className || ''}`}>
|
||||
{Icon}
|
||||
<span style={{ lineHeight: '16px' }}>{label}</span>
|
||||
|
||||
@@ -78,13 +78,10 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
|
||||
const border = useMemo(() => {
|
||||
if (showRequiredState) {
|
||||
updateRequired(false);
|
||||
return '1px solid var(--vscode-inputValidation-errorBorder)';
|
||||
} else if (!isValid) {
|
||||
updateRequired(true);
|
||||
return '1px solid var(--vscode-inputValidation-warningBorder)';
|
||||
} else {
|
||||
updateRequired(true);
|
||||
return '1px solid var(--vscode-inputValidation-infoBorder)';
|
||||
}
|
||||
}, [showRequiredState, isValid]);
|
||||
@@ -162,6 +159,16 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, settings?.seo, name, actions, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showRequiredState) {
|
||||
updateRequired(false);
|
||||
} else if (!isValid) {
|
||||
updateRequired(true);
|
||||
} else {
|
||||
updateRequired(true);
|
||||
}
|
||||
}, [showRequiredState, isValid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (text !== value && (lastUpdated === null || Date.now() - DEBOUNCE_TIME > lastUpdated)) {
|
||||
setText(value || null);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ICenterIconProps { }
|
||||
|
||||
export const CenterIcon: React.FunctionComponent<ICenterIconProps> = () => {
|
||||
return (
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<rect
|
||||
x="1.2"
|
||||
y="0.6"
|
||||
stroke="currentcolor"
|
||||
strokeMiterlimit="10"
|
||||
width="13.6"
|
||||
height="14.8"
|
||||
/>
|
||||
<path
|
||||
stroke="currentcolor"
|
||||
d="M2.6,10V9h10.8v1H2.6z M2.6,6h10.8v1H2.6V6z M13.4,3v1H2.6V3H13.4z M2.6,12v1h10.8v-1H2.6z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,6 @@ export * from './AddIcon';
|
||||
export * from './ArchiveIcon';
|
||||
export * from './BranchIcon';
|
||||
export * from './BugIcon';
|
||||
export * from './CenterIcon';
|
||||
export * from './CopilotIcon';
|
||||
export * from './FileIcon';
|
||||
export * from './FolderOpenedIcon';
|
||||
|
||||
@@ -3,7 +3,6 @@ import { PanelSettings } from '../../models';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { BugIcon } from './Icons/BugIcon';
|
||||
import { CenterIcon } from './Icons/CenterIcon';
|
||||
import { FileIcon } from './Icons/FileIcon';
|
||||
import { FolderOpenedIcon } from './Icons/FolderOpenedIcon';
|
||||
import { TemplateIcon } from './Icons/TemplateIcon';
|
||||
@@ -14,6 +13,7 @@ import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { BookOpenIcon } from '@heroicons/react/24/outline';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { Icon } from 'vscrui';
|
||||
|
||||
export interface IOtherActionsProps {
|
||||
isFile: boolean;
|
||||
@@ -65,7 +65,7 @@ const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({
|
||||
</OtherActionButton>
|
||||
|
||||
<OtherActionButton onClick={() => Messenger.send(CommandToCode.toggleCenterMode)}>
|
||||
<CenterIcon /> <span>{l10n.t(LocalizationKey.panelOtherActionsCenterMode)}</span>
|
||||
<Icon name='layout-centered' className='mr-2' /> <span>{l10n.t(LocalizationKey.panelOtherActionsCenterMode)}</span>
|
||||
</OtherActionButton>
|
||||
|
||||
<OtherActionButton onClick={createAsTemplate} disabled={!isFile}>
|
||||
|
||||
@@ -7,19 +7,20 @@ export interface ISeoFieldInfoProps {
|
||||
value: string;
|
||||
recommendation: string;
|
||||
isValid?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const SeoFieldInfo: React.FunctionComponent<ISeoFieldInfoProps> = ({
|
||||
title,
|
||||
value,
|
||||
recommendation,
|
||||
isValid
|
||||
isValid,
|
||||
className
|
||||
}: React.PropsWithChildren<ISeoFieldInfoProps>) => {
|
||||
return (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableRow className={className || ""}>
|
||||
<VSCodeTableCell className={`capitalize`}>{title}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{value}/{recommendation}</VSCodeTableCell>
|
||||
<VSCodeTableCell>{isValid !== undefined ? <ValidInfo label={undefined} isValid={isValid} /> : <span>-</span>}</VSCodeTableCell>
|
||||
<VSCodeTableCell className='flex items-center text-nowrap'>{isValid !== undefined ? <ValidInfo label={undefined} isValid={isValid} /> : <span className='inline-block w-4 mr-2'>—</span>} {value}/{recommendation}</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { ValidInfo } from './ValidInfo';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
import { Tag } from './Tag';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { Tooltip } from '../../components/common/Tooltip';
|
||||
|
||||
export interface ISeoKeywordInfoProps {
|
||||
keywords: string[];
|
||||
keyword: string;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -16,6 +20,7 @@ export interface ISeoKeywordInfoProps {
|
||||
|
||||
const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
keyword,
|
||||
keywords,
|
||||
title,
|
||||
description,
|
||||
slug,
|
||||
@@ -23,6 +28,7 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
wordCount,
|
||||
headings
|
||||
}: React.PropsWithChildren<ISeoKeywordInfoProps>) => {
|
||||
|
||||
const density = () => {
|
||||
if (!wordCount) {
|
||||
return null;
|
||||
@@ -31,15 +37,16 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
const pattern = new RegExp(`(^${keyword.toLowerCase()}(?=\\s|$))|(\\s${keyword.toLowerCase()}(?=\\s|$))`, 'ig');
|
||||
const count = (content.match(pattern) || []).length;
|
||||
const density = (count / wordCount) * 100;
|
||||
const densityTitle = l10n.t(LocalizationKey.panelSeoKeywordInfoDensity, `${density.toFixed(2)}%`);
|
||||
|
||||
if (density < 0.75) {
|
||||
return <ValidInfo label={densityTitle} isValid={false} />;
|
||||
} else if (density >= 0.75 && density < 1.5) {
|
||||
return <ValidInfo label={densityTitle} isValid={true} />;
|
||||
} else {
|
||||
return <ValidInfo label={densityTitle} isValid={false} />;
|
||||
}
|
||||
const densityTitle = `${density.toFixed(2)}* %`;
|
||||
const color = (density >= 0.75 && density < 1.5) ? "--vscode-charts-green" : "--vscode-notificationsWarningIcon-foreground";
|
||||
return (
|
||||
<span
|
||||
className={`text-[12px] text-[var(${color})] cursor-default`}
|
||||
data-tooltip-id={`tooltip-density-${keyword}`}
|
||||
data-tooltip-content={localize(LocalizationKey.panelSeoKeywordInfoDensityTooltip)}>
|
||||
{densityTitle}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const validateKeywords = (heading: string, keyword: string) => {
|
||||
@@ -63,49 +70,95 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
}
|
||||
|
||||
const exists = headings.filter((heading) => validateKeywords(heading, keyword));
|
||||
return <ValidInfo label={l10n.t(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)} isValid={exists.length > 0} />;
|
||||
return exists.length > 0;
|
||||
};
|
||||
|
||||
const onRemove = React.useCallback((tag: string) => {
|
||||
const newSelection = keywords.filter((s) => s !== tag);
|
||||
Messenger.send(CommandToCode.updateKeywords, {
|
||||
values: newSelection,
|
||||
parents: undefined
|
||||
});
|
||||
}, [keywords]);
|
||||
|
||||
const checks = React.useMemo(() => {
|
||||
return {
|
||||
title: !!title && title.toLowerCase().includes(keyword.toLowerCase()),
|
||||
description: !!description && description.toLowerCase().includes(keyword.toLowerCase()),
|
||||
slug:
|
||||
!!slug &&
|
||||
(slug.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase())),
|
||||
content: !!content && content.toLowerCase().includes(keyword.toLowerCase()),
|
||||
heading: checkHeadings()
|
||||
};
|
||||
}, [title, description, slug, content, headings, wordCount]);
|
||||
|
||||
const tooltipContent = React.useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<h4>Keyword present in:</h4>
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.title} /> {localize(LocalizationKey.commonTitle)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.description} /> {localize(LocalizationKey.commonDescription)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.slug} /> {localize(LocalizationKey.commonSlug)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.content} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoContent)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={!!checks.heading} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)}</span>
|
||||
</>
|
||||
)
|
||||
}, [checks]);
|
||||
|
||||
const checksMarkup = React.useMemo(() => {
|
||||
const validData = Object.values(checks).filter((check) => check).length;
|
||||
const totalChecks = Object.values(checks).length;
|
||||
|
||||
const isValid = validData === totalChecks;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex py-0.5 px-2 my-1 rounded-[3px] justify-center items-center text-[12px] leading-[16px] border border-solid cursor-default
|
||||
${isValid
|
||||
? "text-[var(--vscode-charts-green)] border-[var(--vscode-charts-green)] bg-[var(--frontmatter-success-background)]"
|
||||
: "text-[var(--vscode-notificationsWarningIcon-foreground)] border-[var(--vscode-notificationsWarningIcon-foreground)] bg-[var(--frontmatter-warning-background)]"}`}
|
||||
data-tooltip-id={`tooltip-checks-${keyword}`}
|
||||
>
|
||||
<ValidInfo isValid={isValid} />
|
||||
<span className='mr-[1px]'>{validData}</span>
|
||||
<span>/</span>
|
||||
<span className='ml-[1px]'>{totalChecks}</span>
|
||||
</div>
|
||||
);
|
||||
}, [checks]);
|
||||
|
||||
if (!keyword || typeof keyword !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableCell>{keyword}</VSCodeTableCell>
|
||||
<VSCodeTableCell className={` table__cell__validation`}>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonTitle)}
|
||||
isValid={!!title && title.toLowerCase().includes(keyword.toLowerCase())}
|
||||
<VSCodeTableCell>
|
||||
<div className='flex h-full items-center'>
|
||||
<Tag
|
||||
value={keyword}
|
||||
className={`!w-full !justify-between !mx-0 !my-1 !px-2 !py-0.5`}
|
||||
onRemove={onRemove}
|
||||
onCreate={() => void 0}
|
||||
title={localize(LocalizationKey.panelTagsTagWarning, keyword)}
|
||||
disableConfigurable={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonDescription)}
|
||||
isValid={!!description && description.toLowerCase().includes(keyword.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.commonSlug)}
|
||||
isValid={
|
||||
!!slug &&
|
||||
(slug.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase()))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<ValidInfo
|
||||
label={l10n.t(LocalizationKey.panelSeoKeywordInfoValidInfoContent)}
|
||||
isValid={!!content && content.toLowerCase().includes(keyword.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
{headings && headings.length > 0 &&
|
||||
<div className='flex items-center'>{checkHeadings()}</div>}
|
||||
{wordCount &&
|
||||
<div className='flex items-center'>{density()}</div>}
|
||||
</VSCodeTableCell>
|
||||
<VSCodeTableCell className={`text-center`}>
|
||||
{checksMarkup}
|
||||
|
||||
<Tooltip
|
||||
id={`tooltip-checks-${keyword}`}
|
||||
render={() => tooltipContent} />
|
||||
</VSCodeTableCell>
|
||||
<VSCodeTableCell className={`text-center`}>
|
||||
{density()}
|
||||
|
||||
<Tooltip
|
||||
id={`tooltip-density-${keyword}`} />
|
||||
</VSCodeTableCell>
|
||||
</VSCodeTableRow>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { SeoKeywordInfo } from './SeoKeywordInfo';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
|
||||
export interface ISeoKeywordsProps {
|
||||
keywords: string[] | null;
|
||||
@@ -22,6 +22,8 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
}: React.PropsWithChildren<ISeoKeywordsProps>) => {
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
|
||||
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)]`;
|
||||
|
||||
const validateKeywords = () => {
|
||||
if (!keywords) {
|
||||
return [];
|
||||
@@ -38,6 +40,10 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
return [];
|
||||
};
|
||||
|
||||
const validKeywords = React.useMemo(() => {
|
||||
return validateKeywords();
|
||||
}, [keywords]);
|
||||
|
||||
// Workaround for lit components not updating render
|
||||
React.useEffect(() => {
|
||||
setIsReady(false);
|
||||
@@ -51,38 +57,46 @@ const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`seo__status__keywords`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelSeoKeywordsTitle)}</h4>
|
||||
|
||||
<VSCodeTable>
|
||||
<section className={`seo__keywords__table`}>
|
||||
<VSCodeTable disableOverflow>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableRow className={`border-t border-t-[var(--vscode-editorGroup-border)]`}>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)}
|
||||
{localize(LocalizationKey.panelSeoKeywordsHeaderKeyword)}
|
||||
</VSCodeTableHead>
|
||||
<VSCodeTableHead>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)}
|
||||
<VSCodeTableHead className={`text-center`}>
|
||||
{localize(LocalizationKey.panelSeoKeywordsChecks)}
|
||||
</VSCodeTableHead>
|
||||
|
||||
<VSCodeTableHead className={`text-center`}>
|
||||
<span
|
||||
data-tooltip-id="tooltip-density"
|
||||
data-tooltip-content={localize(LocalizationKey.panelSeoKeywordsDensity)}>
|
||||
{localize(LocalizationKey.panelSeoKeywordsDensityTableTitle)}
|
||||
</span>
|
||||
<Tooltip id="tooltip-density" className={tooltipClasses} style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '19px'
|
||||
}} />
|
||||
</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{validateKeywords().map((keyword, index) => {
|
||||
{validKeywords.map((keyword, index) => {
|
||||
return (
|
||||
<ErrorBoundary key={keyword} fallback={<div />}>
|
||||
<SeoKeywordInfo key={index} keyword={keyword} {...data} />
|
||||
</ErrorBoundary>
|
||||
<SeoKeywordInfo key={`${keyword}-${index}`} keywords={validKeywords} keyword={keyword} {...data} />
|
||||
);
|
||||
})}
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
|
||||
{data.wordCount && (
|
||||
<div className={`text-xs mt-2`}>
|
||||
{l10n.t(LocalizationKey.panelSeoKeywordsDensity)}
|
||||
<div className={`text-xs my-2`}>
|
||||
{localize(LocalizationKey.panelSeoKeywordsDensityDescription)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import { TagType } from '../TagType';
|
||||
import { ArticleDetails } from './ArticleDetails';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import FieldBoundary from './ErrorBoundary/FieldBoundary';
|
||||
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { SeoFieldInfo } from './SeoFieldInfo';
|
||||
import { SeoKeywords } from './SeoKeywords';
|
||||
import { TagPicker } from './Fields/TagPicker';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
import { VSCodeTable, VSCodeTableBody } from './VSCode/VSCodeTable';
|
||||
import useContentType from '../../hooks/useContentType';
|
||||
import { Icon } from 'vscrui';
|
||||
|
||||
export interface ISeoStatusProps {
|
||||
seo: SEO;
|
||||
@@ -38,19 +38,11 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
const descriptionFieldName = contentType?.fields.find(f => f.name === descriptionField)?.title || descriptionField;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`seo__status__details`}>
|
||||
<h4>{localize(LocalizationKey.panelSeoStatusTitle)}</h4>
|
||||
<div className='seo space-y-8'>
|
||||
<section className={`seo__insights`}>
|
||||
<h4 className='!text-left'>{localize(LocalizationKey.panelSeoStatusTitle)}</h4>
|
||||
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderProperty)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderLength)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderValid)}</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{metadata[titleField] && seo.title > 0 ? (
|
||||
<SeoFieldInfo
|
||||
@@ -58,6 +50,7 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
value={metadata[titleField].length}
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.title)}
|
||||
isValid={metadata[titleField].length <= seo.title}
|
||||
className={`border-t border-t-[var(--vscode-editorGroup-border)]`}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -86,34 +79,38 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoWords, seo.content)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ArticleDetails details={metadata.articleDetails} />
|
||||
</VSCodeTableBody>
|
||||
</VSCodeTable>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SeoKeywords
|
||||
keywords={metadata?.keywords}
|
||||
title={metadata[titleField]}
|
||||
description={metadata[descriptionField]}
|
||||
slug={metadata.slug}
|
||||
headings={metadata?.articleDetails?.headingsText}
|
||||
wordCount={metadata?.articleDetails?.wordCount}
|
||||
content={metadata?.articleDetails?.content}
|
||||
/>
|
||||
<section className={`seo__keywords`}>
|
||||
<h4 className='!text-left'>{localize(LocalizationKey.panelSeoKeywordsTitle)}</h4>
|
||||
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={(metadata.keywords as string[]) || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable
|
||||
<SeoKeywords
|
||||
keywords={metadata?.keywords}
|
||||
title={metadata[titleField]}
|
||||
description={metadata[descriptionField]}
|
||||
slug={metadata.slug}
|
||||
headings={metadata?.articleDetails?.headingsText}
|
||||
wordCount={metadata?.articleDetails?.wordCount}
|
||||
content={metadata?.articleDetails?.content}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
|
||||
<ArticleDetails details={metadata.articleDetails} />
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<Icon name="symbol-keyword" className='mr-2' />}
|
||||
crntSelected={(metadata.keywords as string[]) || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
unsetFocus={unsetFocus}
|
||||
disableConfigurable
|
||||
/>
|
||||
</FieldBoundary>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}, [contentType, metadata, seo, focusElm, unsetFocus]);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
|
||||
export interface ITagProps {
|
||||
className: string;
|
||||
@@ -14,16 +13,14 @@ export interface ITagProps {
|
||||
onRemove: (tags: string) => void;
|
||||
}
|
||||
|
||||
const Tag: React.FunctionComponent<ITagProps> = (props: React.PropsWithChildren<ITagProps>) => {
|
||||
const { value, title, onRemove, onCreate, disableConfigurable } = props;
|
||||
|
||||
const Tag: React.FunctionComponent<ITagProps> = ({ value, title, onRemove, onCreate, disableConfigurable, className }: React.PropsWithChildren<ITagProps>) => {
|
||||
return (
|
||||
<>
|
||||
<div className={`tag`}>
|
||||
<div className={`tag ${className || ""}`}>
|
||||
{!disableConfigurable && onCreate && (
|
||||
<button
|
||||
className={`tag__create`}
|
||||
title={l10n.t(LocalizationKey.panelTagAdd, value)}
|
||||
title={localize(LocalizationKey.panelTagAdd, value)}
|
||||
type={`button`}
|
||||
onClick={() => onCreate(value)}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Tag } from './Tag';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
|
||||
export interface ITagsProps {
|
||||
values: string[];
|
||||
@@ -34,7 +33,7 @@ const Tags: React.FunctionComponent<ITagsProps> = (props: React.PropsWithChildre
|
||||
value={t}
|
||||
className={`article__tags__items__pill_exists`}
|
||||
onRemove={onRemove}
|
||||
title={l10n.t(LocalizationKey.commonRemoveValue, t)}
|
||||
title={localize(LocalizationKey.commonRemoveValue, t)}
|
||||
/>
|
||||
))}
|
||||
{unknownTags.map((t, idx) => (
|
||||
@@ -44,7 +43,7 @@ const Tags: React.FunctionComponent<ITagsProps> = (props: React.PropsWithChildre
|
||||
className={`article__tags__items__pill_notexists`}
|
||||
onRemove={onRemove}
|
||||
onCreate={onCreate}
|
||||
title={l10n.t(LocalizationKey.panelTagsTagWarning, t)}
|
||||
title={localize(LocalizationKey.panelTagsTagWarning, t)}
|
||||
disableConfigurable={disableConfigurable}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { cn } from "../../../utils/cn"
|
||||
|
||||
const VSCodeTable = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
React.HTMLAttributes<HTMLTableElement> & { disableOverflow?: boolean }
|
||||
>(({ className, disableOverflow, ...props }, ref) => (
|
||||
<div className={`relative w-full ${disableOverflow ? "" : "overflow-auto"}`}>
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full text-base border-collapse indent-0 [&_tr:nth-child(2n)]:bg-[var(--vscode-keybindingTable-rowsBackground)]", className)}
|
||||
@@ -72,7 +72,7 @@ const VSCodeTableHead = React.forwardRef<
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-6 px-2 py-2 text-left align-middle font-bold",
|
||||
"h-6 px-2 py-2 text-left align-middle font-bold cursor-default",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -4,21 +4,23 @@ import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'
|
||||
export interface IValidInfoProps {
|
||||
label?: string;
|
||||
isValid: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ValidInfo: React.FunctionComponent<IValidInfoProps> = ({
|
||||
label,
|
||||
isValid
|
||||
isValid,
|
||||
className,
|
||||
}: React.PropsWithChildren<IValidInfoProps>) => {
|
||||
return (
|
||||
<>
|
||||
<div className='inline-flex items-center h-full'>
|
||||
{isValid ? (
|
||||
<CheckIcon className={`h-4 w-4 text-[#46ec86] mr-2`} />
|
||||
<CheckIcon className={`h-6 w-6 text-[var(--vscode-charts-green)] mr-2`} />
|
||||
) : (
|
||||
<ExclamationTriangleIcon className={`h-4 w-4 text-[var(--vscode-statusBarItem-warningBackground)] mr-2`} />
|
||||
<ExclamationTriangleIcon className={`h-6 w-6 text-[var(--vscode-notificationsWarningIcon-foreground)] mr-2`} />
|
||||
)}
|
||||
{label && <span>{label}</span>}
|
||||
</>
|
||||
{label && <span className={className || ""}><b>{label}</b></span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,10 +5,16 @@ import { ViewPanel } from './ViewPanel';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { I10nProvider } from '../dashboardWebView/providers/I10nProvider';
|
||||
import { SentryInit } from '../utils/sentryInit';
|
||||
import { updateCssVariables } from '../dashboardWebView/utils/updateCssVariables';
|
||||
import 'vscrui/dist/codicon.css';
|
||||
|
||||
import './styles.css';
|
||||
|
||||
const mutationObserver = new MutationObserver((_, __) => {
|
||||
const darkMode = document.body.classList.contains('vscode-dark');
|
||||
updateCssVariables(darkMode);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
@@ -24,6 +30,9 @@ if (elm) {
|
||||
const isProd = elm?.getAttribute('data-isProd');
|
||||
const isCrashDisabled = elm?.getAttribute('data-is-crash-disabled');
|
||||
|
||||
updateCssVariables(document.body.classList.contains('vscode-dark'));
|
||||
mutationObserver.observe(document.body, { childList: false, attributes: true });
|
||||
|
||||
if (isProd === 'true' && isCrashDisabled === 'false') {
|
||||
Sentry.init(SentryInit(version, environment));
|
||||
|
||||
|
||||
+53
-23
@@ -866,42 +866,58 @@ vscode-divider {
|
||||
padding: 0.25rem 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
outline: none !important;
|
||||
outline-offset: inherit !important;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tag button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
outline: none !important;
|
||||
outline-offset: inherit !important;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.tag__create {
|
||||
margin-right: 0.25rem;
|
||||
|
||||
.tag .tag__create {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.tag .tag__create:hover {
|
||||
color: var(--vscode-inputValidation-infoForeground, #000);
|
||||
background-color: var(--vscode-inputValidation-infoBackground);
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
color: var(--vscode-inputValidation-infoForeground, #000);
|
||||
background-color: var(--vscode-inputValidation-infoBackground);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vscode-dark .tag .tag__create:hover {
|
||||
color: var(--vscode-inputValidation-infoForeground, #fff);
|
||||
}
|
||||
|
||||
.tag {
|
||||
transition: all 100ms;
|
||||
}
|
||||
|
||||
.tag:has(.tag__delete:hover) {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
}
|
||||
|
||||
.tag .tag__delete {
|
||||
margin-left: 0.25rem;
|
||||
|
||||
& svg {
|
||||
stroke-width: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.tag .tag__delete:hover {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground, #000);
|
||||
border-radius: 2px;
|
||||
|
||||
& svg {
|
||||
color: var(--vscode-charts-red);
|
||||
}
|
||||
}
|
||||
|
||||
.vscode-dark .tag .tag__delete:hover {
|
||||
@@ -910,7 +926,8 @@ vscode-divider {
|
||||
|
||||
.tag .tag__value {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
white-space: pre-wrap;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Slug field */
|
||||
@@ -1180,3 +1197,16 @@ vscode-divider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SEO */
|
||||
.seo {
|
||||
.article__tags label,
|
||||
.article__tags__items,
|
||||
.article__tags .field__title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.article__tags {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,34 @@ Example: SEO, website optimization, digital marketing.`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the Copilot service with a given message and returns the response.
|
||||
*
|
||||
* @param {string} prompt - The message to send to the Copilot service.
|
||||
* @returns {Promise<string | undefined>} - The response from the Copilot service, or undefined if no prompt is provided or an error occurs.
|
||||
*
|
||||
* @throws {Error} - Logs an error message if an exception occurs during the process.
|
||||
*/
|
||||
public static async promptCopilot(prompt: string): Promise<string | undefined> {
|
||||
if (!prompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = [LanguageModelChatMessage.User(prompt)];
|
||||
|
||||
const chatResponse = await Copilot.getChatResponse(messages);
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
return chatResponse;
|
||||
} catch (err) {
|
||||
Logger.error(`Copilot:promptCopilot:: ${(err as Error).message}`);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the chat response from the language model.
|
||||
* @param messages - The chat messages to send to the language model.
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { workspace, window, ThemeIcon, TerminalOptions } from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { Folders } from '../commands';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
interface ShellSetting {
|
||||
path: string;
|
||||
}
|
||||
import { LocalizationKey, localize } from '../localization';
|
||||
import { getShellPath } from '../utils';
|
||||
|
||||
export class Terminal {
|
||||
public static readonly terminalName: string = 'Local server';
|
||||
@@ -15,7 +10,7 @@ export class Terminal {
|
||||
* Return the shell path for the current platform
|
||||
*/
|
||||
public static get shell() {
|
||||
const shell: string | { path: string } | undefined = Terminal.getShellPath();
|
||||
const shell: string | { path: string } | undefined = getShellPath();
|
||||
let shellPath: string | undefined = undefined;
|
||||
|
||||
if (typeof shell !== 'string' && !!shell) {
|
||||
@@ -47,7 +42,7 @@ export class Terminal {
|
||||
const terminalOptions: TerminalOptions = {
|
||||
name: Terminal.terminalName,
|
||||
iconPath: new ThemeIcon('server-environment'),
|
||||
message: l10n.t(
|
||||
message: localize(
|
||||
LocalizationKey.servicesTerminalOpenLocalServerTerminalTerminalOptionMessage
|
||||
)
|
||||
};
|
||||
@@ -90,46 +85,4 @@ export class Terminal {
|
||||
return localServerTerminal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the automation profile for the current platform
|
||||
* @returns
|
||||
*/
|
||||
private static getShellPath(): string | ShellSetting | undefined {
|
||||
const platform = Terminal.getPlatform();
|
||||
const terminalSettings = workspace.getConfiguration('terminal');
|
||||
|
||||
const automationProfile = terminalSettings.get<string | ShellSetting>(
|
||||
`integrated.automationProfile.${platform}`
|
||||
);
|
||||
if (!!automationProfile) {
|
||||
return automationProfile;
|
||||
}
|
||||
|
||||
const defaultProfile = terminalSettings.get<string>(`integrated.defaultProfile.${platform}`);
|
||||
const profiles = terminalSettings.get<{ [prop: string]: ShellSetting }>(
|
||||
`integrated.profiles.${platform}`
|
||||
);
|
||||
|
||||
if (defaultProfile && profiles && profiles[defaultProfile]) {
|
||||
return profiles[defaultProfile];
|
||||
}
|
||||
|
||||
return terminalSettings.get(`integrated.shell.${platform}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current platform
|
||||
* @returns
|
||||
*/
|
||||
private static getPlatform = (): 'windows' | 'linux' | 'osx' => {
|
||||
const platform = os.platform();
|
||||
if (platform === 'win32') {
|
||||
return 'windows';
|
||||
} else if (platform === 'darwin') {
|
||||
return 'osx';
|
||||
}
|
||||
|
||||
return 'linux';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { exec } from 'child_process';
|
||||
import { getShellPath } from '../utils';
|
||||
import { Logger } from '../helpers';
|
||||
|
||||
/**
|
||||
* Evaluate the command dynamically using `which` command
|
||||
* @param command
|
||||
* @returns
|
||||
*/
|
||||
export const evaluateCommand = (command: string): Promise<string> => {
|
||||
const shell = getShellPath();
|
||||
let shellPath: string | undefined = undefined;
|
||||
if (typeof shell !== 'string' && !!shell) {
|
||||
shellPath = shell.path;
|
||||
} else {
|
||||
shellPath = shell || undefined;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`which ${command}`, { shell: shellPath }, (error, stdout) => {
|
||||
if (error) {
|
||||
Logger.error(`Error evaluating command: ${command}`);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { format } from 'date-fns';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { SETTING_GLOBAL_TIMEZONE } from '../constants';
|
||||
import { DateHelper, Settings } from '../helpers';
|
||||
|
||||
export const formatInTimezone = (date: Date, dateFormat: string) => {
|
||||
const timezone = Settings.get<string>(SETTING_GLOBAL_TIMEZONE);
|
||||
return timezone
|
||||
? formatInTimeZone(date, timezone, DateHelper.formatUpdate(dateFormat) as string)
|
||||
: format(date, DateHelper.formatUpdate(dateFormat) as string);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as os from 'os';
|
||||
|
||||
/**
|
||||
* Determines the current operating system platform.
|
||||
*
|
||||
* @returns {'windows' | 'linux' | 'osx'} - A string representing the platform:
|
||||
* - 'windows' for Windows OS
|
||||
* - 'osx' for macOS
|
||||
* - 'linux' for Linux OS
|
||||
*/
|
||||
export const getPlatform = (): 'windows' | 'linux' | 'osx' => {
|
||||
const platform = os.platform();
|
||||
if (platform === 'win32') {
|
||||
return 'windows';
|
||||
} else if (platform === 'darwin') {
|
||||
return 'osx';
|
||||
}
|
||||
|
||||
return 'linux';
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { ShellSetting } from '../models';
|
||||
import { getPlatform } from './getPlatform';
|
||||
|
||||
/**
|
||||
* Retrieves the shell path configuration based on the current platform and terminal settings.
|
||||
*
|
||||
* This method checks for the following configurations in order:
|
||||
* 1. `integrated.automationProfile.<platform>`: Returns the automation profile if it exists.
|
||||
* 2. `integrated.defaultProfile.<platform>` and `integrated.profiles.<platform>`: Returns the shell setting from the default profile if it exists.
|
||||
* 3. `integrated.shell.<platform>`: Returns the shell setting if the above configurations are not found.
|
||||
*
|
||||
* @returns {string | ShellSetting | undefined} The shell path configuration or undefined if not found.
|
||||
*/
|
||||
export const getShellPath = (): string | ShellSetting | undefined => {
|
||||
const platform = getPlatform();
|
||||
const terminalSettings = workspace.getConfiguration('terminal');
|
||||
|
||||
const automationProfile = terminalSettings.get<string | ShellSetting>(
|
||||
`integrated.automationProfile.${platform}`
|
||||
);
|
||||
if (!!automationProfile) {
|
||||
return automationProfile;
|
||||
}
|
||||
|
||||
const defaultProfile = terminalSettings.get<string>(`integrated.defaultProfile.${platform}`);
|
||||
const profiles = terminalSettings.get<{ [prop: string]: ShellSetting }>(
|
||||
`integrated.profiles.${platform}`
|
||||
);
|
||||
|
||||
if (defaultProfile && profiles && profiles[defaultProfile]) {
|
||||
return profiles[defaultProfile];
|
||||
}
|
||||
|
||||
return terminalSettings.get(`integrated.shell.${platform}`);
|
||||
};
|
||||
@@ -1,13 +1,17 @@
|
||||
export * from './cn';
|
||||
export * from './copyFileAsync';
|
||||
export * from './encodeEmoji';
|
||||
export * from './evaluateCommand';
|
||||
export * from './existsAsync';
|
||||
export * from './fetchWithTimeout';
|
||||
export * from './fieldWhenClause';
|
||||
export * from './flattenObjectKeys';
|
||||
export * from './formatInTimezone';
|
||||
export * from './getDescriptionField';
|
||||
export * from './getExtensibilityScripts';
|
||||
export * from './getLocalizationFile';
|
||||
export * from './getPlatform';
|
||||
export * from './getShellPath';
|
||||
export * from './getTitleField';
|
||||
export * from './getWebviewJsFiles';
|
||||
export * from './ignoreMsgCommand';
|
||||
|
||||
Reference in New Issue
Block a user