mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
661efcf23f | ||
|
|
152f36e352 | ||
|
|
08697abba4 | ||
|
|
f1d345ebc2 | ||
|
|
e9af7e1793 | ||
|
|
49e7fe6377 | ||
|
|
cc375801c2 | ||
|
|
4a53a180a7 | ||
|
|
51ece235f8 | ||
|
|
36ac891c00 | ||
|
|
5f0fd29cca | ||
|
|
0428e561a8 | ||
|
|
bcba947c1d | ||
|
|
c2d3496152 | ||
|
|
7f1dc88bd4 | ||
|
|
83f4711103 | ||
|
|
0a8723c544 | ||
|
|
bdce486a24 | ||
|
|
6d6a53047a | ||
|
|
afb241ad6a | ||
|
|
4229d262ae | ||
|
|
6b92a6f8b4 | ||
|
|
183e77b77b | ||
|
|
da7d5e6854 | ||
|
|
8a08f54340 | ||
|
|
be54b6286f | ||
|
|
1315602bcc | ||
|
|
0ad0179a4b | ||
|
|
9d68797c95 | ||
|
|
ffaea3b55d | ||
|
|
4565ea75ae | ||
|
|
c4d3f76510 | ||
|
|
ce2bd06f6d | ||
|
|
a29a6600ab | ||
|
|
6cbf86f822 | ||
|
|
514272835a | ||
|
|
3c29df54c1 | ||
|
|
d06be0efa1 | ||
|
|
2375be9211 | ||
|
|
b5b7dcf6b5 | ||
|
|
c81d5240f4 | ||
|
|
06b8a579a8 | ||
|
|
460c4964f6 | ||
|
|
d59d9a98d5 | ||
|
|
83cf0eb8f5 | ||
|
|
7c4aa1d63d | ||
|
|
6e84217458 | ||
|
|
b1380388b6 | ||
|
|
d70f983694 | ||
|
|
d22ebfa6ce | ||
|
|
cf96923d96 | ||
|
|
6150a34547 | ||
|
|
d45cd0d015 |
4
.github/workflows/release-beta.yml
vendored
4
.github/workflows/release-beta.yml
vendored
@@ -13,8 +13,8 @@ jobs:
|
||||
name: Beta
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -13,8 +13,8 @@ jobs:
|
||||
name: Stable
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
### ✨ New features
|
||||
|
||||
- [#731](https://github.com/estruyf/vscode-front-matter/issues/731): Added the ability to map/unmap taxonomy to multiple pages at once
|
||||
- [#746](https://github.com/estruyf/vscode-front-matter/issues/746): Placeholder support added to to the `slug` field
|
||||
- [#749](https://github.com/estruyf/vscode-front-matter/issues/749): Ability to set your own filters on the content dashboard with the `frontMatter.content.filters` setting
|
||||
|
||||
### 🎨 Enhancements
|
||||
@@ -12,8 +13,10 @@
|
||||
- [#673](https://github.com/estruyf/vscode-front-matter/pull/673): Added git settings to the welcome view and settings view
|
||||
- [#727](https://github.com/estruyf/vscode-front-matter/pull/727): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387)
|
||||
- [#737](https://github.com/estruyf/vscode-front-matter/issues/737): Optimize the grid layout of the content and media dashboards
|
||||
- [#739](https://github.com/estruyf/vscode-front-matter/pull/739): New Git settings to disable and require a commit message
|
||||
- [#741](https://github.com/estruyf/vscode-front-matter/issues/741): Added message on the content dashboard when content is processed
|
||||
- [#747](https://github.com/estruyf/vscode-front-matter/issues/747): The `@frontmatter/extensibility` dependency now supports scripts for placeholders
|
||||
- [#752](https://github.com/estruyf/vscode-front-matter/issues/752): Placeholder support in default `list` field values
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
|
||||
3
assets/icons/i18n-dark.svg
Normal file
3
assets/icons/i18n-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="#C5C5C5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 442 B |
3
assets/icons/i18n-light.svg
Normal file
3
assets/icons/i18n-light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="#424242">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 442 B |
@@ -35,6 +35,8 @@
|
||||
"common.no": "no",
|
||||
"common.openSettings": "Open settings",
|
||||
"common.back": "Back",
|
||||
"common.open": "Open",
|
||||
"common.openWithValue": "Open: {0}",
|
||||
|
||||
"loading.initPages": "Loading content",
|
||||
|
||||
@@ -44,6 +46,8 @@
|
||||
"settings.view.common": "Common",
|
||||
"settings.view.contentFolders": "Content folders",
|
||||
"settings.view.astro": "Astro",
|
||||
"settings.view.integration": "Integration",
|
||||
|
||||
"settings.openOnStartup": "Open dashboard on startup",
|
||||
"settings.contentTypes": "Content types",
|
||||
"settings.contentFolders": "Content folders",
|
||||
@@ -55,12 +59,17 @@
|
||||
"settings.git.commitMessage": "Commit message",
|
||||
"settings.git.submoduleInfo": "When working with Git submodules, you can refer to the submodule settings in the documentation.",
|
||||
"settings.git.submoduleLink": "Read more about Git submodules",
|
||||
"settings.integration.title": "Integration",
|
||||
|
||||
"settings.commonSettings.website.title": "Website and SSG settings",
|
||||
"settings.commonSettings.previewUrl": "Preview URL",
|
||||
"settings.commonSettings.websiteUrl": "Website URL",
|
||||
"settings.commonSettings.startCommand": "SSG/Framework start command",
|
||||
|
||||
"settings.integrationsView.deepl.title": "DeepL",
|
||||
"settings.integrationsView.deepl.intput.label": "Authentication key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "Enter your DeepL authentication key",
|
||||
|
||||
"developer.title": "Developer mode",
|
||||
"developer.reload.title": "Reload the dashboard",
|
||||
"developer.reload.label": "Reload",
|
||||
@@ -88,6 +97,8 @@
|
||||
"dashboard.contents.contentActions.menuItem.view": "View",
|
||||
"dashboard.contents.contentActions.alert.title": "Delete: {0}",
|
||||
"dashboard.contents.contentActions.alert.description": "Are you sure you want to delete the \"{0}\" content?",
|
||||
"dashboard.contents.contentActions.translations.create": "Create translation",
|
||||
"dashboard.contents.contentActions.translations.menu": "Translations",
|
||||
|
||||
"dashboard.contents.item.invalidTitle": "<invalid title>",
|
||||
"dashboard.contents.item.invalidDescription": "<invalid description>",
|
||||
@@ -126,6 +137,9 @@
|
||||
|
||||
"dashboard.errorView.description": "Please close the dashboard and try again.",
|
||||
|
||||
"dashboard.filters.languageFilter.label": "Locale",
|
||||
"dashboard.filters.languageFilter.all": "All",
|
||||
|
||||
"dashboard.header.breadcrumb.home": "Home",
|
||||
|
||||
"dashboard.header.clearFilters.title": "Clear filters, grouping, and sorting",
|
||||
@@ -209,10 +223,14 @@
|
||||
"dashboard.media.folderCreation.hexo.create": "Create post asset folder",
|
||||
"dashboard.media.folderCreation.folder.create": "Create new folder",
|
||||
|
||||
"dashboard.media.item.buttom.insert.image": "Insert image",
|
||||
"dashboard.media.item.buttom.insert.snippet": "Insert snippet",
|
||||
|
||||
"dashboard.media.item.quickAction.insert.field": "Insert image for your \"{0}\" field",
|
||||
"dashboard.media.item.quickAction.insert.markdown": "Insert image with markdown markup",
|
||||
"dashboard.media.item.quickAction.copy.path": "Copy media path",
|
||||
"dashboard.media.item.quickAction.delete": "Delete media file",
|
||||
"dashboard.media.item.menuItem.view": "View media details",
|
||||
"dashboard.media.item.menuItem.edit.metadata": "Edit metadata",
|
||||
"dashboard.media.item.menuItem.insert.image": "Insert image",
|
||||
"dashboard.media.item.menuItem.reveal.media": "Reveal media",
|
||||
@@ -293,6 +311,7 @@
|
||||
"dashboard.steps.stepsToGetStarted.astroContentTypes.name": "Create Content-Types for your Astro Content Collections",
|
||||
|
||||
"dashboard.taxonomyView.button.add.title": "Add {0} to taxonomy settings",
|
||||
"dashboard.taxonomyView.button.tag.title": "Tag content",
|
||||
"dashboard.taxonomyView.button.edit.title": "Edit {0}",
|
||||
"dashboard.taxonomyView.button.merge.title": "Merge {0}",
|
||||
"dashboard.taxonomyView.button.move.title": "Move to another taxonomy type",
|
||||
@@ -339,6 +358,11 @@
|
||||
"dashboard.configuration.astro.astroContentTypes.empty": "No Astro Content Collections found.",
|
||||
"dashboard.configuration.astro.astroContentTypes.description": "The following Astro Content Collections can be used to generate a content-type.",
|
||||
|
||||
"panel.git.gitAction.title": "Publish changes",
|
||||
"panel.git.gitAction.branch.select": "Select branch",
|
||||
"panel.git.gitAction.input.placeholder": "Commit message",
|
||||
"panel.git.gitAction.button.fetch": "Fetch",
|
||||
|
||||
"panel.contentType.contentTypeValidator.title": "Content-type",
|
||||
"panel.contentType.contentTypeValidator.hint": "We noticed field differences between the content-type and the front matter data. \n Would you like to create, update, or set the content-type for this content?",
|
||||
"panel.contentType.contentTypeValidator.button.create": "Create content-type",
|
||||
@@ -511,6 +535,17 @@
|
||||
"commands.folders.get.notificationError.remove.action": "Remove folder",
|
||||
"commands.folders.get.notificationError.create.action": "Create folder",
|
||||
|
||||
"commands.i18n.create.warning.noFileSelected": "No file selected.",
|
||||
"commands.i18n.create.warning.noFile": "The file could not be retrieved.",
|
||||
"commands.i18n.create.warning.noContentType": "Content type could not be retrieved for the current file.",
|
||||
"commands.i18n.create.warning.noConfig": "No i18n configuration found.",
|
||||
"commands.i18n.create.warning.notDefaultLocale": "The current file cannot be used for i18n content creation.",
|
||||
"commands.i18n.create.error.fileExists": "The i18n translation already exists.",
|
||||
"commands.i18n.create.success.created": "Created \"{0}\" i18n content file.",
|
||||
"commands.i18n.create.quickPick.title": "Create content for locale",
|
||||
"commands.i18n.create.quickPick.placeHolder": "To which locale do you want to create a new content?",
|
||||
"commands.i18n.translate.progress.title": "Translating content...",
|
||||
|
||||
"commands.preview.panel.title": "Preview: {0}",
|
||||
"commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview",
|
||||
|
||||
@@ -700,6 +735,7 @@
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.progress.title": "Downloading and initializing the template...",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.download.error": "Failed to download the template.",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.init.error": "Failed to initialize the template.",
|
||||
"listeners.dashboard.settingsListener.setSecretValue.message": "Setting has been updated.",
|
||||
|
||||
"listeners.dashboard.snippetListener.addSnippet.missingFields.warning": "Snippet missing title or body",
|
||||
"listeners.dashboard.snippetListener.addSnippet.exists.warning": "Snippet with the same title already exists",
|
||||
|
||||
787
package-lock.json
generated
787
package-lock.json
generated
@@ -8,15 +8,17 @@
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "9.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -44,6 +46,7 @@
|
||||
"assert": "^2.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"clsx": "^2.1.0",
|
||||
"css-loader": "5.2.7",
|
||||
"date-fns": "2.23.0",
|
||||
"dotenv": "^16.3.1",
|
||||
@@ -79,7 +82,6 @@
|
||||
"react-dom": "17.0.1",
|
||||
"react-dropzone": "^11.7.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
@@ -89,7 +91,9 @@
|
||||
"semver": "^7.3.8",
|
||||
"simple-git": "^3.16.0",
|
||||
"style-loader": "2.0.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"uniforms": "^3.10.2",
|
||||
@@ -389,7 +393,6 @@
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -539,6 +542,40 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
|
||||
"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.0.0",
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
|
||||
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "1.7.18",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.18.tgz",
|
||||
@@ -960,6 +997,535 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||
"integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
||||
"integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
|
||||
"integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-menu": "2.0.6",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.4",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.3",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-roving-focus": "1.0.4",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz",
|
||||
"integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
"@radix-ui/react-arrow": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-use-rect": "1.0.1",
|
||||
"@radix-ui/react-use-size": "1.0.1",
|
||||
"@radix-ui/rect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz",
|
||||
"integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz",
|
||||
"integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/rect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz",
|
||||
"integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/rect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz",
|
||||
"integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rc-component/portal": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
|
||||
@@ -1390,7 +1956,7 @@
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.11",
|
||||
@@ -1417,7 +1983,7 @@
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
|
||||
"integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -1439,7 +2005,7 @@
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz",
|
||||
"integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@@ -2216,6 +2782,22 @@
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/aria-hidden": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz",
|
||||
"integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-hidden/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/array-buffer-byte-length": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
|
||||
@@ -2841,6 +3423,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -3120,7 +3711,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.23.0",
|
||||
@@ -3296,6 +3887,11 @@
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -4442,6 +5038,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-nonce": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -5284,7 +5888,6 @@
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
@@ -5822,8 +6425,7 @@
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
@@ -6103,7 +6705,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
@@ -7505,7 +8106,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -9231,7 +9831,6 @@
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
|
||||
"integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
@@ -9262,7 +9861,6 @@
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz",
|
||||
"integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -9382,6 +9980,61 @@
|
||||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll-bar": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz",
|
||||
"integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==",
|
||||
"dependencies": {
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll-bar/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/react-remove-scroll/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.22.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz",
|
||||
@@ -9430,6 +10083,33 @@
|
||||
"react-dom": "^16.3.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
|
||||
"dependencies": {
|
||||
"get-nonce": "^1.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -9535,8 +10215,7 @@
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
@@ -9941,7 +10620,6 @@
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
|
||||
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
@@ -10803,6 +11481,19 @@
|
||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.1.tgz",
|
||||
"integrity": "sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
||||
@@ -10840,6 +11531,15 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
|
||||
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
||||
@@ -11504,6 +12204,57 @@
|
||||
"integrity": "sha512-u+5gi7JyOwhj58ZKwkmkzFGHuepTpmwjqfUDGVjsJJstsCz63CJAINixgJaDcMbmuyWPJIxbtBpIfaDgOQ9KMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/use-callback-ref": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz",
|
||||
"integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-callback-ref/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
|
||||
"dependencies": {
|
||||
"detect-node-es": "^1.1.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||
|
||||
493
package.json
493
package.json
@@ -10,8 +10,7 @@
|
||||
"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"
|
||||
@@ -71,8 +70,7 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"keybindings": [{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
@@ -90,23 +88,19 @@
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}
|
||||
]
|
||||
"activitybar": [{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}]
|
||||
},
|
||||
"views": {
|
||||
"frontmatter-explorer": [
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}
|
||||
]
|
||||
"frontmatter-explorer": [{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "%settings.configuration.title%",
|
||||
@@ -174,8 +168,7 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -191,8 +184,7 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -311,6 +303,17 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.disableCreation.description%"
|
||||
},
|
||||
"defaultLocale": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description%"
|
||||
},
|
||||
"locales": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.locales.description%",
|
||||
"items": {
|
||||
"$ref": "#i18n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -321,6 +324,34 @@
|
||||
},
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.i18n": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "%setting.frontMatter.content.i18n.markdownDescription%",
|
||||
"items": {
|
||||
"$id": "#i18n",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.i18n.items.properties.title.description%"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.i18n.items.properties.locale.description%"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.content.i18n.items.properties.path.description%"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"locale"
|
||||
]
|
||||
},
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.placeholders": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
@@ -500,8 +531,7 @@
|
||||
"categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [
|
||||
{
|
||||
"items": [{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
@@ -569,8 +599,7 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -777,8 +806,7 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -832,8 +860,7 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -899,6 +926,22 @@
|
||||
"markdownDescription": "%setting.frontMatter.git.commitMessage.markdownDescription%",
|
||||
"default": "Synced by Front Matter"
|
||||
},
|
||||
"frontMatter.git.disableOnBranches": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%setting.frontMatter.git.disableOnBranches.markdownDescription%",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"frontMatter.git.requiresCommitMessage": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%setting.frontMatter.git.requiresCommitMessage.markdownDescription%",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"frontMatter.git.submodule.pull": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%setting.frontMatter.git.submodule.pull.markdownDescription%",
|
||||
@@ -1007,6 +1050,79 @@
|
||||
"markdownDescription": "%setting.frontMatter.media.defaultSorting.markdownDescription%",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.media.contentTypes": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.media.contentTypes.markdownDescription%",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.description%",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.name.description%"
|
||||
},
|
||||
"fileTypes": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fileTypes.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.description%",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description%"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description%"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"string"
|
||||
],
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description%"
|
||||
},
|
||||
"single": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
@@ -1234,8 +1350,7 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1429,8 +1544,7 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"allOf": [{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1601,6 +1715,14 @@
|
||||
"default": null,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description%"
|
||||
},
|
||||
"slugTemplate": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"default": null,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.slugTemplate.description%"
|
||||
},
|
||||
"template": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -1630,51 +1752,48 @@
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
@@ -1687,8 +1806,7 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1843,6 +1961,11 @@
|
||||
"markdownDescription": "%setting.frontMatter.taxonomy.slugSuffix.markdownDescription%",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.slugTemplate": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%setting.frontMatter.taxonomy.slugTemplate.markdownDescription%",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.tags": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%setting.frontMatter.taxonomy.tags.markdownDescription%",
|
||||
@@ -1880,8 +2003,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"commands": [{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2196,23 +2318,27 @@
|
||||
"command": "frontMatter.cache.clear",
|
||||
"title": "%command.frontMatter.cache.clear%",
|
||||
"category": "Front Matter"
|
||||
}
|
||||
],
|
||||
"submenus": [
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
"command": "frontMatter.i18n.create",
|
||||
"title": "%command.frontMatter.i18n.create%",
|
||||
"category": "Front Matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/i18n-light.svg",
|
||||
"dark": "assets/icons/i18n-dark.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
@@ -2242,6 +2368,11 @@
|
||||
"group": "navigation@-128",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"group": "navigation@-127",
|
||||
"when": "frontMatter:file:isValid && frontMatter:i18n:default"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.options",
|
||||
"group": "navigation@-126",
|
||||
@@ -2293,14 +2424,11 @@
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}
|
||||
],
|
||||
"frontmatter.submenu": [
|
||||
{
|
||||
"explorer/context": [{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}],
|
||||
"frontmatter.submenu": [{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "explorerResourceIsFolder",
|
||||
"group": "frontmatter@1"
|
||||
@@ -2316,8 +2444,7 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"commandPalette": [{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2325,14 +2452,6 @@
|
||||
"command": "frontMatter.project.switch",
|
||||
"when": "frontMatter:project:switch:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "!frontMatterCanInit"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.preview",
|
||||
"when": "frontMatterCanOpenPreview"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.data",
|
||||
"when": "frontMatter:dashboard:data:enabled"
|
||||
@@ -2345,10 +2464,26 @@
|
||||
"command": "frontMatter.git.sync",
|
||||
"when": "frontMatter:git:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"when": "frontMatter:i18n:default"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.authenticate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertTags",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertCategories",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.registerFolder",
|
||||
"when": "false"
|
||||
@@ -2409,10 +2544,22 @@
|
||||
"command": "frontMatter.markup.options",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.hyperlink",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.config.reload",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.initTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertSnippet",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
@@ -2433,14 +2580,6 @@
|
||||
"command": "frontMatter.insertCategories",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertTags",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.preview",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
@@ -2466,8 +2605,7 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"view/title": [{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2499,57 +2637,52 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"grammars": [
|
||||
{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
"grammars": [{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
}],
|
||||
"walkthroughs": [{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
"scripts": {
|
||||
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
|
||||
@@ -2577,11 +2710,10 @@
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -2609,6 +2741,7 @@
|
||||
"assert": "^2.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"clsx": "^2.1.0",
|
||||
"css-loader": "5.2.7",
|
||||
"date-fns": "2.23.0",
|
||||
"dotenv": "^16.3.1",
|
||||
@@ -2644,7 +2777,6 @@
|
||||
"react-dom": "17.0.1",
|
||||
"react-dropzone": "^11.7.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
@@ -2654,7 +2786,9 @@
|
||||
"semver": "^7.3.8",
|
||||
"simple-git": "^3.16.0",
|
||||
"style-loader": "2.0.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"uniforms": "^3.10.2",
|
||||
@@ -2673,5 +2807,8 @@
|
||||
},
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@
|
||||
"command.frontMatter.markup.unorderedlist": "Unordered list",
|
||||
"command.frontMatter.git.sync": "Sync",
|
||||
"command.frontMatter.cache.clear": "Clear cache",
|
||||
"command.frontMatter.i18n.create": "Create new translation",
|
||||
"settings.configuration.title": "Front Matter: use frontmatter.json for shared team settings",
|
||||
"setting.frontMatter.projects.markdownDescription": "Specify the list of projects to load in the Front Matter CMS. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Specify the name of the project.",
|
||||
@@ -75,6 +76,12 @@
|
||||
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "Defines a prefix for the file name.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "Defines which content types can be used for the current location. If not defined, all content types will be available.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "Disable the creation of new content in the folder.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "Set the default locale ID for the page folder. All content from this folder is translatable to the languages defined in the `frontMatter.content.i18n` setting.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.locales.description": "Define the locales for the page folder. This will be used for the translation of the content.",
|
||||
"setting.frontMatter.content.i18n.markdownDescription": "Specify the locales you want to use for your website. This setting can be overwritten on page folder level. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)",
|
||||
"setting.frontMatter.content.i18n.items.properties.title.description": "Title of the locale",
|
||||
"setting.frontMatter.content.i18n.items.properties.locale.description": "Locale code",
|
||||
"setting.frontMatter.content.i18n.items.properties.path.description": "Relative path of the locale folder",
|
||||
"setting.frontMatter.content.placeholders.markdownDescription": "This array of placeholders defines the placeholders that you can use in your content types and templates for automatically populating your content its front matter. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.placeholders)",
|
||||
"setting.frontMatter.content.placeholders.items.properties.id.description": "ID of the placeholder, in your content type or template, use it as follows: {{placeholder}}",
|
||||
"setting.frontMatter.content.placeholders.items.properties.value.description": "The placeholder its value",
|
||||
@@ -154,6 +161,17 @@
|
||||
"setting.frontMatter.global.disabledNotifications.markdownDescription": "This is an array with the notifications types that can be disabled for Front Matter CMS. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications)",
|
||||
"setting.frontMatter.media.defaultSorting.markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "Specify the mime types to support for the media files. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
|
||||
|
||||
"setting.frontMatter.media.contentTypes.markdownDescription": "Specify the media content types you want to use in Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.media.contenttypes)",
|
||||
"setting.frontMatter.media.contentTypes.items.description": "Define the media content types you want to use in Front Matter.",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.name.description": "Name of the media content type",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fileTypes.description": "Specify the file types to allow for the media content type",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.description": "Define the fields of the media content type",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description": "Title to show in the UI",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description": "Name of the field to use",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "Define the type of field",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "Is a single line field",
|
||||
|
||||
"setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
|
||||
@@ -211,6 +229,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "Specify if the comparison is case sensitive. Default: true",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "Specify if you want to create a folder when creating new content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "Defines a custom preview path for the content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "Defines a custom slug template for the content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "An optional template that can be used for creating new content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "An optional post script that can be used after new content creation.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.filePrefix.description": "Defines a prefix for the file name.",
|
||||
@@ -237,6 +256,7 @@
|
||||
"setting.frontMatter.taxonomy.seoTitleLength.markdownDescription": "Specifies the optimal title length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlelength)",
|
||||
"setting.frontMatter.taxonomy.slugPrefix.markdownDescription": "Specify a prefix for the slug. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugprefix)",
|
||||
"setting.frontMatter.taxonomy.slugSuffix.markdownDescription": "Specify a suffix for the slug. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugsuffix)",
|
||||
"setting.frontMatter.taxonomy.slugTemplate.markdownDescription": "Defines a custom slug template for the content you will create. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugtemplate)",
|
||||
"setting.frontMatter.taxonomy.tags.markdownDescription": "Specifies the tags which can be used in the Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags)",
|
||||
"setting.frontMatter.telemetry.disable.markdownDescription": "Specify if you want to disable the telemetry. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable)",
|
||||
"setting.frontMatter.templates.enabled.markdownDescription": "Specify if you want to use templates. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled)",
|
||||
@@ -251,5 +271,8 @@
|
||||
"command.frontMatter.settings.refresh": "Refresh Front Matter Settings",
|
||||
"setting.frontMatter.config.dynamicFilePath.markdownDescription": "Specify the path to the dynamic config file (ex: [[workspace]]/config.js). [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "Specify if the content type can be used as sub content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "Specify if the content type is sub content."
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "Specify if the content type is sub content.",
|
||||
|
||||
"setting.frontMatter.git.disableOnBranches.markdownDescription": "Specify the branches on which you want to disable the Git actions. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches)",
|
||||
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "Specify if you want to require a commit message when publishing your changes for a specified branch. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage)"
|
||||
}
|
||||
@@ -15,18 +15,23 @@ import {
|
||||
import * as vscode from 'vscode';
|
||||
import { CustomPlaceholder, Field } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Settings,
|
||||
SlugHelper,
|
||||
processArticlePlaceholdersFromData,
|
||||
processTimePlaceholders
|
||||
} from '../helpers';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { extname, basename, parse, dirname } from 'path';
|
||||
import { COMMAND_NAME, DefaultFields } from '../constants';
|
||||
import { DashboardData, SnippetRange } from '../models/DashboardData';
|
||||
import { DashboardData, SnippetInfo, SnippetRange } from '../models/DashboardData';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { MediaListener } from '../listeners/panel';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { processKnownPlaceholders } from '../helpers/PlaceholderHelper';
|
||||
import { Position } from 'vscode';
|
||||
import { SNIPPET } from '../constants/Snippet';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
@@ -124,7 +129,7 @@ export class Article {
|
||||
/**
|
||||
* Generate the new slug
|
||||
*/
|
||||
public static generateSlug(title: string) {
|
||||
public static generateSlug(title: string, article?: ParsedFrontMatter, slugTemplate?: string) {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
@@ -132,13 +137,15 @@ export class Article {
|
||||
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
|
||||
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
|
||||
|
||||
const slug = SlugHelper.createSlug(title);
|
||||
if (article?.data) {
|
||||
const slug = SlugHelper.createSlug(title, article?.data, slugTemplate);
|
||||
|
||||
if (slug) {
|
||||
return {
|
||||
slug,
|
||||
slugWithPrefixAndSuffix: `${prefix}${slug}${suffix}`
|
||||
};
|
||||
if (slug) {
|
||||
return {
|
||||
slug,
|
||||
slugWithPrefixAndSuffix: `${prefix}${slug}${suffix}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -168,7 +175,7 @@ export class Article {
|
||||
|
||||
const titleField = 'title';
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const slugInfo = Article.generateSlug(articleTitle);
|
||||
const slugInfo = Article.generateSlug(articleTitle, article, contentType.slugTemplate);
|
||||
|
||||
if (slugInfo && slugInfo.slug && slugInfo.slugWithPrefixAndSuffix) {
|
||||
article.data['slug'] = slugInfo.slugWithPrefixAndSuffix;
|
||||
@@ -192,9 +199,13 @@ export class Article {
|
||||
);
|
||||
for (const pField of customPlaceholderFields) {
|
||||
article.data[pField.name] = customPlaceholder.value;
|
||||
article.data[pField.name] = processKnownPlaceholders(
|
||||
article.data[pField.name] = processArticlePlaceholdersFromData(
|
||||
article.data[pField.name],
|
||||
article.data,
|
||||
contentType
|
||||
);
|
||||
article.data[pField.name] = processTimePlaceholders(
|
||||
article.data[pField.name],
|
||||
articleTitle,
|
||||
dateFormat
|
||||
);
|
||||
}
|
||||
@@ -388,7 +399,7 @@ export class Article {
|
||||
snippetStartBeforePos = linesBeforeSelection.length - snippetStartBeforePos - 1;
|
||||
}
|
||||
|
||||
let snippetInfo: { id: string; fields: any[] } | undefined = undefined;
|
||||
let snippetInfo: SnippetInfo | undefined = undefined;
|
||||
let range: SnippetRange | undefined = undefined;
|
||||
if (
|
||||
snippetEndAfterPos > -1 &&
|
||||
@@ -412,6 +423,7 @@ export class Article {
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType = article ? ArticleHelper.getContentType(article) : undefined;
|
||||
|
||||
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: NavigationType.Snippets,
|
||||
@@ -419,6 +431,7 @@ export class Article {
|
||||
fileTitle: article?.data.title || '',
|
||||
filePath: editor.document.uri.fsPath,
|
||||
fieldName: basename(editor.document.uri.fsPath),
|
||||
contentType,
|
||||
position,
|
||||
range,
|
||||
selection: selectionText,
|
||||
|
||||
@@ -3,11 +3,13 @@ import {
|
||||
CONTEXT,
|
||||
ExtensionState,
|
||||
SETTING_EXPERIMENTAL,
|
||||
SETTING_EXTENSIBILITY_SCRIPTS
|
||||
SETTING_EXTENSIBILITY_SCRIPTS,
|
||||
COMMAND_NAME,
|
||||
TelemetryEvent
|
||||
} from '../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode';
|
||||
import { DashboardSettings, Logger, Settings as SettingsHelper } from '../helpers';
|
||||
import { DashboardSettings, Logger, Settings as SettingsHelper, Telemetry } from '../helpers';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
@@ -31,6 +33,8 @@ import { GitListener, ModeListener } from '../listeners/general';
|
||||
import { Folders } from './Folders';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -51,6 +55,56 @@ export class Dashboard {
|
||||
}
|
||||
}
|
||||
|
||||
public static registerCommands() {
|
||||
const subscriptions = Extension.getInstance().subscriptions;
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openContentDashboard);
|
||||
if (!data) {
|
||||
Dashboard.open({ type: NavigationType.Contents });
|
||||
} else {
|
||||
Dashboard.open(data);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardMedia, () => {
|
||||
Telemetry.send(TelemetryEvent.openMediaDashboard);
|
||||
Dashboard.open({ type: NavigationType.Media });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardSnippets, () => {
|
||||
Telemetry.send(TelemetryEvent.openSnippetsDashboard);
|
||||
Dashboard.open({ type: NavigationType.Snippets });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardData, () => {
|
||||
Telemetry.send(TelemetryEvent.openDataDashboard);
|
||||
Dashboard.open({ type: NavigationType.Data });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardTaxonomy, () => {
|
||||
Telemetry.send(TelemetryEvent.openTaxonomyDashboard);
|
||||
Dashboard.open({ type: NavigationType.Taxonomy });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.dashboardClose, () => {
|
||||
Telemetry.send(TelemetryEvent.closeDashboard);
|
||||
Dashboard.close();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open or reveal the dashboard
|
||||
*/
|
||||
@@ -204,7 +258,7 @@ export class Dashboard {
|
||||
* @param msg
|
||||
*/
|
||||
public static postWebviewMessage(msg: {
|
||||
command: DashboardCommand;
|
||||
command: DashboardCommand | DashboardMessage;
|
||||
requestId?: string;
|
||||
payload?: unknown;
|
||||
error?: unknown;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder';
|
||||
import { Questions } from './../helpers/Questions';
|
||||
import {
|
||||
SETTING_CONTENT_I18N,
|
||||
SETTING_CONTENT_PAGE_FOLDERS,
|
||||
SETTING_CONTENT_STATIC_FOLDER,
|
||||
SETTING_CONTENT_SUPPORTED_FILETYPES,
|
||||
@@ -9,11 +10,11 @@ import {
|
||||
} from './../constants';
|
||||
import { commands, Uri, workspace, window } from 'vscode';
|
||||
import { basename, dirname, join, relative, sep } from 'path';
|
||||
import { ContentFolder, FileInfo, FolderInfo, StaticFolder } from '../models';
|
||||
import { ContentFolder, FileInfo, FolderInfo, I18nConfig, StaticFolder } from '../models';
|
||||
import uniqBy = require('lodash.uniqby');
|
||||
import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Logger, processKnownPlaceholders, Settings } from '../helpers';
|
||||
import { Logger, Settings, processTimePlaceholders } from '../helpers';
|
||||
import { existsSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
@@ -288,67 +289,34 @@ export class Folders {
|
||||
const folderInfo: FolderInfo[] = [];
|
||||
|
||||
for (const folder of folders) {
|
||||
try {
|
||||
const folderPath = parseWinPath(folder.path);
|
||||
const crntFolderInfo = await Folders.getFilesByFolder(folder, supportedFiles, limit);
|
||||
if (crntFolderInfo) {
|
||||
folderInfo.push(crntFolderInfo);
|
||||
}
|
||||
|
||||
if (typeof folderPath === 'string') {
|
||||
let files: Uri[] = [];
|
||||
// Process localization folders
|
||||
if (folder.defaultLocale) {
|
||||
const i18nConfig = folder.locales || Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
|
||||
if (i18nConfig) {
|
||||
for (const i18n of i18nConfig) {
|
||||
if (i18n.locale !== folder.defaultLocale && i18n.path) {
|
||||
const i18nFolder = {
|
||||
...folder,
|
||||
path: join(folder.path, i18n.path),
|
||||
title: `${folder.title} (${i18n.title})`
|
||||
} as ContentFolder;
|
||||
|
||||
for (const fileType of supportedFiles || DEFAULT_FILE_TYPES) {
|
||||
let filePath = join(
|
||||
folderPath,
|
||||
folder.excludeSubdir ? '/' : '**',
|
||||
`*${fileType.startsWith('.') ? '' : '.'}${fileType}`
|
||||
);
|
||||
|
||||
if (folderPath === '' && folder.excludeSubdir) {
|
||||
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
|
||||
}
|
||||
|
||||
let foundFiles = await Folders.findFiles(filePath);
|
||||
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
|
||||
|
||||
files = [...files, ...foundFiles];
|
||||
}
|
||||
|
||||
if (files) {
|
||||
let fileStats: FileInfo[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const fileName = basename(file.fsPath);
|
||||
const folderName = dirname(file.fsPath).split(sep).pop();
|
||||
|
||||
const stats = await workspace.fs.stat(file);
|
||||
|
||||
fileStats.push({
|
||||
filePath: file.fsPath,
|
||||
fileName,
|
||||
folderName,
|
||||
...stats
|
||||
});
|
||||
} catch (error) {
|
||||
// Skip the file
|
||||
const crntFolderInfo = await Folders.getFilesByFolder(
|
||||
i18nFolder,
|
||||
supportedFiles,
|
||||
limit
|
||||
);
|
||||
if (crntFolderInfo) {
|
||||
folderInfo.push(crntFolderInfo);
|
||||
}
|
||||
}
|
||||
|
||||
fileStats = fileStats.sort((a, b) => b.mtime - a.mtime);
|
||||
|
||||
if (limit) {
|
||||
fileStats = fileStats.slice(0, limit);
|
||||
}
|
||||
|
||||
folderInfo.push({
|
||||
title: folder.title,
|
||||
files: files.length,
|
||||
lastModified: fileStats
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip the current folder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +345,7 @@ export class Folders {
|
||||
let folderPath: string | undefined = Folders.absWsFolder(folder, wsFolder);
|
||||
if (folderPath.includes(`{{`) && folderPath.includes(`}}`)) {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
folderPath = processKnownPlaceholders(folderPath, undefined, dateFormat);
|
||||
folderPath = processTimePlaceholders(folderPath, dateFormat);
|
||||
} else {
|
||||
if (folderPath && !existsSync(folderPath)) {
|
||||
Notifications.errorShowOnce(
|
||||
@@ -603,6 +571,97 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the page folder that matches the given file path.
|
||||
*
|
||||
* @param filePath - The file path to match against the page folders.
|
||||
* @returns The page folder that matches the file path, or undefined if no match is found.
|
||||
*/
|
||||
public static getPageFolderByFilePath(filePath: string): ContentFolder | undefined {
|
||||
const folders = Folders.get();
|
||||
const parsedPath = parseWinPath(filePath);
|
||||
const pageFolderMatches = folders
|
||||
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
|
||||
.sort((a, b) => b.path.length - a.path.length);
|
||||
|
||||
if (pageFolderMatches.length > 0 && pageFolderMatches[0]) {
|
||||
return pageFolderMatches[0];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private static async getFilesByFolder(
|
||||
folder: ContentFolder,
|
||||
supportedFiles: string[] | undefined,
|
||||
limit?: number
|
||||
): Promise<FolderInfo | undefined> {
|
||||
try {
|
||||
const folderPath = parseWinPath(folder.path);
|
||||
|
||||
if (typeof folderPath === 'string') {
|
||||
let files: Uri[] = [];
|
||||
|
||||
for (const fileType of supportedFiles || DEFAULT_FILE_TYPES) {
|
||||
let filePath = join(
|
||||
folderPath,
|
||||
folder.excludeSubdir ? '/' : '**',
|
||||
`*${fileType.startsWith('.') ? '' : '.'}${fileType}`
|
||||
);
|
||||
|
||||
if (folderPath === '' && folder.excludeSubdir) {
|
||||
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
|
||||
}
|
||||
|
||||
let foundFiles = await Folders.findFiles(filePath);
|
||||
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
|
||||
|
||||
files = [...files, ...foundFiles];
|
||||
}
|
||||
|
||||
if (files) {
|
||||
let fileStats: FileInfo[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const fileName = basename(file.fsPath);
|
||||
const folderName = dirname(file.fsPath).split(sep).pop();
|
||||
|
||||
const stats = await workspace.fs.stat(file);
|
||||
|
||||
fileStats.push({
|
||||
filePath: file.fsPath,
|
||||
fileName,
|
||||
folderName,
|
||||
...stats
|
||||
});
|
||||
} catch (error) {
|
||||
// Skip the file
|
||||
}
|
||||
}
|
||||
|
||||
fileStats = fileStats.sort((a, b) => b.mtime - a.mtime);
|
||||
|
||||
if (limit) {
|
||||
fileStats = fileStats.slice(0, limit);
|
||||
}
|
||||
|
||||
return {
|
||||
title: folder.title,
|
||||
files: files.length,
|
||||
lastModified: fileStats
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip the current folder
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all content folders
|
||||
* @param pattern
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { join, parse } from 'path';
|
||||
import { commands, env, Uri, ViewColumn, window, WebviewPanel, extensions } from 'vscode';
|
||||
import { Extension, parseWinPath, processKnownPlaceholders, Settings } from '../helpers';
|
||||
import { Extension, parseWinPath, processTimePlaceholders, Settings } from '../helpers';
|
||||
import { ContentFolder, ContentType, PreviewSettings } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
@@ -294,7 +294,7 @@ export class Preview {
|
||||
if (pathname) {
|
||||
// Known placeholders
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
pathname = processKnownPlaceholders(pathname, article?.data?.title, dateFormat);
|
||||
pathname = processTimePlaceholders(pathname, dateFormat);
|
||||
|
||||
// Custom placeholders
|
||||
pathname = await ArticleHelper.processCustomPlaceholders(
|
||||
@@ -318,7 +318,7 @@ export class Preview {
|
||||
}
|
||||
|
||||
// Support front matter placeholders - {{fm.<field>}}
|
||||
pathname = processFmPlaceholders(pathname, article?.data);
|
||||
pathname = article?.data ? processFmPlaceholders(pathname, article?.data) : pathname;
|
||||
|
||||
try {
|
||||
const articleDate = ArticleHelper.getDate(article);
|
||||
|
||||
@@ -18,8 +18,8 @@ export class Settings {
|
||||
const taxonomy = type === TaxonomyType.Tag ? 'tag' : 'category';
|
||||
|
||||
const newOption = await vscode.window.showInputBox({
|
||||
prompt: l10n.t(LocalizationKey.commandsFoldersCreateInputPrompt, taxonomy),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsFoldersCreateInputPlaceholder, taxonomy),
|
||||
prompt: l10n.t(LocalizationKey.commandsSettingsCreateInputPrompt, taxonomy),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsSettingsCreateInputPlaceholder, taxonomy),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Field } from '../models';
|
||||
import { Preview } from './Preview';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { i18n } from './i18n';
|
||||
|
||||
export class StatusListener {
|
||||
/**
|
||||
@@ -42,6 +43,10 @@ export class StatusListener {
|
||||
try {
|
||||
commands.executeCommand('setContext', CONTEXT.isValidFile, true);
|
||||
|
||||
// Check i18n
|
||||
const isI18nDefault = await i18n.isDefaultLanguage(document.uri.fsPath);
|
||||
commands.executeCommand('setContext', CONTEXT.isI18nDefault, isI18nDefault);
|
||||
|
||||
const article = editor
|
||||
? ArticleHelper.getFrontMatter(editor)
|
||||
: await ArticleHelper.getFrontMatterByPath(document.uri.fsPath);
|
||||
@@ -83,6 +88,7 @@ export class StatusListener {
|
||||
}
|
||||
} else {
|
||||
commands.executeCommand('setContext', CONTEXT.isValidFile, false);
|
||||
commands.executeCommand('setContext', CONTEXT.isI18nDefault, false);
|
||||
|
||||
const panel = PanelProvider.getInstance();
|
||||
if (panel && panel.visible) {
|
||||
|
||||
517
src/commands/i18n.ts
Normal file
517
src/commands/i18n.ts
Normal file
@@ -0,0 +1,517 @@
|
||||
import { ProgressLocation, Uri, commands, window, workspace } from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
ContentType,
|
||||
Extension,
|
||||
FrameworkDetector,
|
||||
Logger,
|
||||
Notifications,
|
||||
Settings,
|
||||
openFileInEditor,
|
||||
parseWinPath
|
||||
} from '../helpers';
|
||||
import { COMMAND_NAME, ExtensionState, SETTING_CONTENT_I18N } from '../constants';
|
||||
import { ContentFolder, Field, I18nConfig, ContentType as IContentType } from '../models';
|
||||
import { join, parse } from 'path';
|
||||
import { existsAsync } from '../utils';
|
||||
import { Folders } from '.';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export class i18n {
|
||||
private static processedFiles: {
|
||||
[filePath: string]: { dir: string; filename: string; isPageBundle: boolean };
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* Registers the i18n commands.
|
||||
*/
|
||||
public static register() {
|
||||
const subscriptions = Extension.getInstance().subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.create, i18n.create));
|
||||
|
||||
i18n.clearFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the processed files
|
||||
*/
|
||||
public static clearFiles() {
|
||||
i18n.processedFiles = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the I18nConfig settings from the application.
|
||||
* @returns An array of I18nConfig objects if settings are found, otherwise undefined.
|
||||
*/
|
||||
public static async getSettings(filePath: string): Promise<I18nConfig[] | undefined> {
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
if (!pageFolder) {
|
||||
pageFolder = await i18n.getPageFolder(filePath);
|
||||
}
|
||||
|
||||
if (!pageFolder || !pageFolder.locales) {
|
||||
return i18nSettings;
|
||||
}
|
||||
|
||||
return pageFolder.locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given file path corresponds to the default language.
|
||||
* @param filePath - The file path to check.
|
||||
* @returns True if the file path corresponds to the default language, false otherwise.
|
||||
*/
|
||||
public static async isDefaultLanguage(filePath: string): Promise<boolean> {
|
||||
const i18nSettings = await i18n.getSettings(filePath);
|
||||
if (!i18nSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
if (!pageFolder || !pageFolder.defaultLocale) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
if (pageFolder.path) {
|
||||
let pageFolderPath = parseWinPath(pageFolder.path);
|
||||
if (!pageFolderPath.endsWith('/')) {
|
||||
pageFolderPath += '/';
|
||||
}
|
||||
|
||||
return (
|
||||
parseWinPath(fileInfo.dir).toLowerCase() === parseWinPath(pageFolderPath).toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the I18nConfig for a given file path.
|
||||
* @param filePath - The path of the file.
|
||||
* @returns The I18nConfig object if found, otherwise undefined.
|
||||
*/
|
||||
public static async getLocale(filePath: string): Promise<I18nConfig | undefined> {
|
||||
const i18nSettings = await i18n.getSettings(filePath);
|
||||
if (!i18nSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
if (pageFolder && pageFolder.defaultLocale) {
|
||||
let pageFolderPath = parseWinPath(pageFolder.path);
|
||||
if (!pageFolderPath.endsWith('/')) {
|
||||
pageFolderPath += '/';
|
||||
}
|
||||
|
||||
if (
|
||||
pageFolder.path &&
|
||||
parseWinPath(fileInfo.dir).toLowerCase() === parseWinPath(pageFolderPath).toLowerCase()
|
||||
) {
|
||||
return i18nSettings.find((i18n) => i18n.locale === pageFolder?.defaultLocale);
|
||||
}
|
||||
}
|
||||
|
||||
pageFolder = await i18n.getPageFolder(filePath);
|
||||
if (!pageFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const locale of i18nSettings) {
|
||||
if (locale.path && pageFolder.defaultLocale !== locale.locale) {
|
||||
const translation = join(pageFolder.path, locale.path, fileInfo.filename);
|
||||
if (parseWinPath(translation).toLowerCase() === parseWinPath(filePath).toLowerCase()) {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves translations for a given file path.
|
||||
* @param filePath - The path of the file for which translations are requested.
|
||||
* @returns A promise that resolves to an object containing translations for each locale, or undefined if i18n settings are not available.
|
||||
*/
|
||||
public static async getTranslations(filePath: string): Promise<
|
||||
| {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
const i18nSettings = await i18n.getSettings(filePath);
|
||||
if (!i18nSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const translations: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
} = {};
|
||||
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
if (pageFolder && pageFolder.defaultLocale) {
|
||||
for (const i18n of i18nSettings) {
|
||||
const translation = join(pageFolder.path, i18n.path || '', fileInfo.filename);
|
||||
if (await existsAsync(translation)) {
|
||||
translations[i18n.locale] = {
|
||||
locale: i18n,
|
||||
path: translation
|
||||
};
|
||||
}
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
|
||||
pageFolder = await i18n.getPageFolder(filePath);
|
||||
if (!pageFolder) {
|
||||
return translations;
|
||||
}
|
||||
|
||||
for (const i18n of i18nSettings) {
|
||||
const translation = join(pageFolder.path, i18n.path || '', fileInfo.filename);
|
||||
if (await existsAsync(translation)) {
|
||||
translations[i18n.locale] = {
|
||||
locale: i18n,
|
||||
path: translation
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new content file for a specific locale based on the i18n configuration.
|
||||
* If a file path is provided, the new content file will be created in the same directory.
|
||||
* If no file path is provided, the active file in the editor will be used.
|
||||
* @param filePath The path of the file where the new content file should be created.
|
||||
*/
|
||||
private static async create(fileUri?: Uri | string) {
|
||||
if (!fileUri) {
|
||||
const filePath = ArticleHelper.getActiveFile();
|
||||
fileUri = filePath ? Uri.file(filePath) : undefined;
|
||||
}
|
||||
|
||||
if (!fileUri) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof fileUri === 'string') {
|
||||
fileUri = Uri.file(fileUri);
|
||||
}
|
||||
|
||||
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
|
||||
if (!i18nSettings) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
const isDefaultLanguage = await i18n.isDefaultLanguage(fileUri.fsPath);
|
||||
if (!isDefaultLanguage) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNotDefaultLocale));
|
||||
return;
|
||||
}
|
||||
|
||||
const locale = await window.showQuickPick(
|
||||
i18nSettings.filter((i18n) => i18n.path).map((i18n) => i18n.title || i18n.locale),
|
||||
{
|
||||
title: l10n.t(LocalizationKey.commandsI18nCreateQuickPickTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
);
|
||||
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedI18n = i18nSettings.find(
|
||||
(i18n) => i18n.title === locale || i18n.locale === locale
|
||||
);
|
||||
if (!selectedI18n || !selectedI18n.path) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
|
||||
if (!article) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the directory of the file
|
||||
const fileInfo = parse(fileUri.fsPath);
|
||||
let dir = fileInfo.dir;
|
||||
let pageBundleDir = '';
|
||||
|
||||
if (await ArticleHelper.isPageBundle(fileUri.fsPath)) {
|
||||
dir = ArticleHelper.getPageFolderFromBundlePath(fileUri.fsPath);
|
||||
pageBundleDir = fileUri.fsPath.replace(dir, '');
|
||||
pageBundleDir = join(parse(pageBundleDir).dir);
|
||||
}
|
||||
|
||||
const i18nDir = join(dir, selectedI18n.path, pageBundleDir);
|
||||
|
||||
if (!(await existsAsync(i18nDir))) {
|
||||
await workspace.fs.createDirectory(Uri.file(i18nDir));
|
||||
}
|
||||
|
||||
article = await i18n.updateFrontMatter(
|
||||
article,
|
||||
fileUri.fsPath,
|
||||
contentType,
|
||||
selectedI18n,
|
||||
i18nDir
|
||||
);
|
||||
|
||||
const newFilePath = join(i18nDir, fileInfo.base);
|
||||
if (await existsAsync(newFilePath)) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorFileExists));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
|
||||
if (sourceLocale?.locale) {
|
||||
article = await i18n.translate(article, sourceLocale, selectedI18n);
|
||||
}
|
||||
|
||||
const newFileUri = Uri.file(newFilePath);
|
||||
await workspace.fs.writeFile(
|
||||
newFileUri,
|
||||
Buffer.from(ArticleHelper.stringifyFrontMatter(article.content, article.data))
|
||||
);
|
||||
|
||||
await openFileInEditor(newFilePath);
|
||||
|
||||
PagesListener.refresh();
|
||||
|
||||
Notifications.info(
|
||||
l10n.t(
|
||||
LocalizationKey.commandsI18nCreateSuccessCreated,
|
||||
selectedI18n.title || selectedI18n.locale
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given article from the source locale to the target locale using DeepL translation service.
|
||||
* @param article - The article to be translated.
|
||||
* @param sourceLocale - The source locale configuration.
|
||||
* @param targetLocale - The target locale configuration.
|
||||
* @returns A promise that resolves to the translated article.
|
||||
*/
|
||||
private static async translate(
|
||||
article: ParsedFrontMatter,
|
||||
sourceLocale: I18nConfig,
|
||||
targetLocale: I18nConfig
|
||||
) {
|
||||
return new Promise<ParsedFrontMatter>(async (resolve) => {
|
||||
const authKey = await Extension.getInstance().getSecret(ExtensionState.Secrets.DeeplApiKey);
|
||||
if (!authKey) {
|
||||
resolve(article);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: l10n.t(LocalizationKey.commandsI18nTranslateProgressTitle),
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
const title = article.data.title;
|
||||
const description = article.data.description;
|
||||
const content = article.content;
|
||||
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
text: [title, description, content],
|
||||
source_lang: sourceLocale.locale,
|
||||
target_lang: targetLocale.locale
|
||||
});
|
||||
|
||||
let host = authKey.endsWith(':fx') ? 'api-free.deepl.com' : 'api.deepl.com';
|
||||
|
||||
const response = await fetch(`https://${host}/v2/translate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `DeepL-Auth-Key ${authKey}`,
|
||||
'User-Agent': `FrontMatterCMS/${Extension.getInstance().version}`,
|
||||
'Content-Type': 'application/json',
|
||||
'content-length': body.length.toString(),
|
||||
Accept: 'application/json'
|
||||
},
|
||||
body
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepL: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.translations || data.translations.length < 3) {
|
||||
throw new Error('DeepL: Invalid response');
|
||||
}
|
||||
|
||||
article.data.title = data.translations[0].text;
|
||||
article.data.description = data.translations[1].text;
|
||||
article.content = data.translations[2].text;
|
||||
} catch (error) {
|
||||
Notifications.error(`${(error as Error).message}`);
|
||||
}
|
||||
|
||||
resolve(article);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename and directory information from the given file path.
|
||||
* If the file is a page bundle, the directory will be adjusted accordingly.
|
||||
* @param filePath - The path of the file.
|
||||
* @returns An object containing the filename and directory.
|
||||
*/
|
||||
private static async getFileInfo(filePath: string): Promise<{ filename: string; dir: string }> {
|
||||
if (i18n.processedFiles[filePath]) {
|
||||
return i18n.processedFiles[filePath];
|
||||
}
|
||||
|
||||
const fileInfo = parse(filePath);
|
||||
let filename = fileInfo.base;
|
||||
let dir = fileInfo.dir;
|
||||
|
||||
const isPageBundle = await ArticleHelper.isPageBundle(filePath);
|
||||
if (isPageBundle) {
|
||||
dir = ArticleHelper.getPageFolderFromBundlePath(filePath);
|
||||
filename = join(parseWinPath(filePath).replace(parseWinPath(dir), ''));
|
||||
}
|
||||
|
||||
if (!dir.endsWith('/')) {
|
||||
dir += '/';
|
||||
}
|
||||
|
||||
i18n.processedFiles[filePath] = {
|
||||
isPageBundle,
|
||||
filename,
|
||||
dir
|
||||
};
|
||||
|
||||
return i18n.processedFiles[filePath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the page folder for a given file path.
|
||||
*
|
||||
* @param filePath - The path of the file.
|
||||
* @returns A promise that resolves to the ContentFolder object representing the page folder, or undefined if not found.
|
||||
*/
|
||||
private static async getPageFolder(filePath: string): Promise<ContentFolder | undefined> {
|
||||
const folders = Folders.get();
|
||||
|
||||
const localeFolders = folders?.filter((folder) => folder.defaultLocale);
|
||||
if (!localeFolders) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
for (const folder of localeFolders) {
|
||||
const defaultFile = join(folder.path, fileInfo.filename);
|
||||
if (await existsAsync(defaultFile)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the front matter of an article with internationalization (i18n) support.
|
||||
*
|
||||
* @param article - The parsed front matter of the article.
|
||||
* @param filePath - The path of the file containing the front matter.
|
||||
* @param contentType - The content type of the article.
|
||||
* @param i18nConfig - The configuration for internationalization.
|
||||
* @param i18nDir - The directory where the i18n files are located.
|
||||
* @returns A Promise that resolves to the updated parsed front matter.
|
||||
*/
|
||||
private static async updateFrontMatter(
|
||||
article: ParsedFrontMatter,
|
||||
filePath: string,
|
||||
contentType: IContentType,
|
||||
i18nConfig: I18nConfig,
|
||||
i18nDir: string
|
||||
): Promise<ParsedFrontMatter> {
|
||||
const imageFields = ContentType.findFieldsByTypeDeep(contentType.fields, 'image');
|
||||
if (imageFields.length > 0) {
|
||||
article.data = await i18n.processImageFields(article.data, filePath, imageFields, i18nDir);
|
||||
}
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the image fields in the provided data object.
|
||||
* Replaces the image field values with the relative path to the image file.
|
||||
*
|
||||
* @param data - The data object containing the field values.
|
||||
* @param filePath - The absolute file path of the data object.
|
||||
* @param fields - The array of field arrays to process.
|
||||
* @param i18nDir - The directory path for internationalization.
|
||||
* @returns The updated data object with image field values replaced by relative paths.
|
||||
*/
|
||||
private static async processImageFields(
|
||||
data: { [key: string]: any },
|
||||
filePath: string,
|
||||
fields: Field[][],
|
||||
i18nDir: string
|
||||
) {
|
||||
for (const field of fields) {
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const f of field) {
|
||||
if (f.type === 'image') {
|
||||
const value = data[f.name];
|
||||
if (value) {
|
||||
let imgPath = FrameworkDetector.getAbsPathByFile(value, filePath);
|
||||
imgPath = FrameworkDetector.getRelPathByFileDir(imgPath, i18nDir);
|
||||
data[f.name] = imgPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
149
src/components/shadcn/Dropdown.tsx
Normal file
149
src/components/shadcn/Dropdown.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { cn } from "../../utils/cn"
|
||||
import { ChevronRightIcon } from "@heroicons/react/24/outline"
|
||||
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-[var(--vscode-list-hoverBackground)] data-[state=open]:bg-[var(--vscode-list-hoverBackground)]",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded border border-[var(--frontmatter-border)] bg-[var(--vscode-sideBar-background)] p-1 text-[var(--vscode-editor-foreground)] shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] rounded border border-[var(--frontmatter-border)] bg-[var(--vscode-sideBar-background)] p-1 text-[var(--vscode-editor-foreground)] shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-96 overflow-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-[var(--vscode-list-hoverBackground)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer disabled:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-[var(--frontmatter-border)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
}
|
||||
@@ -67,6 +67,11 @@ export const COMMAND_NAME = {
|
||||
addMissingFields: getCommandName('contenttype.addMissingFields'),
|
||||
setContentType: getCommandName('contenttype.setContentType'),
|
||||
|
||||
// i18n
|
||||
i18n: {
|
||||
create: getCommandName('i18n.create')
|
||||
},
|
||||
|
||||
// Project
|
||||
switchProject: getCommandName('project.switch'),
|
||||
|
||||
|
||||
@@ -30,5 +30,9 @@ export const ExtensionState = {
|
||||
v7_0_0: {
|
||||
dateFields: `frontMatter:Updates:v7.0.0:dateFields`
|
||||
}
|
||||
},
|
||||
|
||||
Secrets: {
|
||||
DeeplApiKey: `frontMatter:Secrets:DeeplApiKey`
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
export const GeneralCommands = {
|
||||
toWebview: {
|
||||
setMode: 'setMode',
|
||||
gitSyncingStart: 'gitSyncingStart',
|
||||
gitSyncingEnd: 'gitSyncingEnd',
|
||||
git: {
|
||||
syncingStart: 'gitSyncingStart',
|
||||
syncingEnd: 'gitSyncingEnd',
|
||||
branchName: 'gitBranchName'
|
||||
},
|
||||
setLocalization: 'setLocalization'
|
||||
},
|
||||
toVSCode: {
|
||||
openLink: 'openLink',
|
||||
gitSync: 'gitSync',
|
||||
gitIsRepo: 'gitIsRepo',
|
||||
git: {
|
||||
isRepo: 'gitIsRepo',
|
||||
sync: 'gitSync',
|
||||
fetch: 'getFetch',
|
||||
getBranch: 'getBranch',
|
||||
selectBranch: 'gitSelectBranch'
|
||||
},
|
||||
secrets: {
|
||||
get: 'getSecret',
|
||||
set: 'setSecret'
|
||||
},
|
||||
runCommand: 'runCommand',
|
||||
getLocalization: 'getLocalization',
|
||||
openOnWebsite: 'openOnWebsite'
|
||||
}
|
||||
|
||||
@@ -49,5 +49,6 @@ export const TelemetryEvent = {
|
||||
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
|
||||
|
||||
// Git
|
||||
gitSync: 'gitSync'
|
||||
gitSync: 'gitSync',
|
||||
gitFetch: 'gitFetch'
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ export const CONTEXT = {
|
||||
isValidFile: 'frontMatter:file:isValid',
|
||||
isDevelopment: 'frontMatter:isDevelopment',
|
||||
|
||||
isI18nDefault: 'frontMatter:i18n:default',
|
||||
|
||||
hasViewModes: 'frontMatter:has:modes',
|
||||
|
||||
isSnippetsDashboardEnabled: 'frontMatter:dashboard:snippets:enabled',
|
||||
|
||||
@@ -25,6 +25,7 @@ export const SETTING_TAXONOMY_CONTENT_TYPES = 'taxonomy.contentTypes';
|
||||
|
||||
export const SETTING_SLUG_PREFIX = 'taxonomy.slugPrefix';
|
||||
export const SETTING_SLUG_SUFFIX = 'taxonomy.slugSuffix';
|
||||
export const SETTING_SLUG_TEMPLATE = 'taxonomy.slugTemplate';
|
||||
export const SETTING_SLUG_UPDATE_FILE_NAME = 'taxonomy.alignFilename';
|
||||
|
||||
export const SETTING_INDENT_ARRAY = 'taxonomy.indentArrays';
|
||||
@@ -56,6 +57,7 @@ export const SETTING_CUSTOM_SCRIPTS = 'custom.scripts';
|
||||
|
||||
export const SETTING_AUTO_UPDATE_DATE = 'content.autoUpdateDate';
|
||||
export const SETTING_CONTENT_PAGE_FOLDERS = 'content.pageFolders';
|
||||
export const SETTING_CONTENT_I18N = 'content.i18n';
|
||||
export const SETTING_CONTENT_STATIC_FOLDER = 'content.publicFolder';
|
||||
export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = 'content.fmHighlight';
|
||||
export const SETTING_CONTENT_DRAFT_FIELD = 'content.draftField';
|
||||
@@ -75,6 +77,7 @@ export const SETTING_CONTENT_HIDE_FRONTMATTER = 'content.hideFm';
|
||||
export const SETTING_CONTENT_HIDE_FRONTMATTER_MESSAGE = 'content.hideFmMessage';
|
||||
|
||||
export const SETTING_MEDIA_SUPPORTED_MIMETYPES = 'media.supportedMimeTypes';
|
||||
export const SETTING_MEDIA_CONTENTTYPES = 'media.contentTypes';
|
||||
|
||||
export const SETTING_DASHBOARD_OPENONSTART = 'dashboard.openOnStart';
|
||||
export const SETTING_DASHBOARD_CONTENT_TAGS = 'dashboard.content.cardTags';
|
||||
@@ -99,6 +102,8 @@ export const SETTING_FRAMEWORK_START = 'framework.startCommand';
|
||||
export const SETTING_SITE_BASEURL = 'site.baseURL';
|
||||
|
||||
export const SETTING_GIT_ENABLED = 'git.enabled';
|
||||
export const SETTING_GIT_DISABLED_BRANCHES = 'git.disableOnBranches';
|
||||
export const SETTING_GIT_REQUIRES_COMMIT_MSG = 'git.requiresCommitMessage';
|
||||
export const SETTING_GIT_COMMIT_MSG = 'git.commitMessage';
|
||||
export const SETTING_GIT_SUBMODULE_PULL = 'git.submodule.pull';
|
||||
export const SETTING_GIT_SUBMODULE_PUSH = 'git.submodule.push';
|
||||
|
||||
@@ -54,6 +54,7 @@ export enum DashboardMessage {
|
||||
insertSnippet = 'insertSnippet',
|
||||
addSnippet = 'addSnippet',
|
||||
updateSnippet = 'updateSnippet',
|
||||
updateSnippetPlaceholders = 'updateSnippetPlaceholders',
|
||||
|
||||
// Taxonomy dashboard
|
||||
getTaxonomyData = 'getTaxonomyData',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IChoiceButtonProps {
|
||||
title: string;
|
||||
@@ -36,40 +36,39 @@ export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({
|
||||
{title}
|
||||
</button>
|
||||
|
||||
{choices.length > 0 && (
|
||||
<Menu as="span" className="-ml-px relative block">
|
||||
<Menu.Button
|
||||
className={`h-full inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium focus:outline-none rounded-r text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardCommonChoiceButtonOpen)}</span>
|
||||
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
|
||||
<MenuItems widthClass={`w-56`} disablePopper>
|
||||
<div className="py-1">
|
||||
{choices.map((choice, idx) => (
|
||||
<MenuItem
|
||||
key={idx}
|
||||
title={
|
||||
choice.icon ? (
|
||||
<div className="flex items-center">
|
||||
{choice.icon}
|
||||
<span>{choice.title}</span>
|
||||
</div>
|
||||
) : (
|
||||
choice.title
|
||||
)
|
||||
}
|
||||
value={null}
|
||||
onClick={choice.onClick}
|
||||
disabled={choice.disabled}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
)}
|
||||
</span>
|
||||
{choices.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='h-full inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium focus:outline-none rounded-r text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50'
|
||||
disabled={disabled}>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardCommonChoiceButtonOpen)}</span>
|
||||
<ChevronDownIcon className={`h-4 w-4`} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='end'>
|
||||
{choices.map((choice, idx) => (
|
||||
<MenuItem
|
||||
key={idx}
|
||||
title={
|
||||
choice.icon ? (
|
||||
<div className="flex items-center">
|
||||
{choice.icon}
|
||||
<span>{choice.title}</span>
|
||||
</div>
|
||||
) : (
|
||||
choice.title
|
||||
)
|
||||
}
|
||||
value={null}
|
||||
onClick={choice.onClick}
|
||||
disabled={choice.disabled}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
</span >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, EllipsisVerticalIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { CustomScript, I18nConfig, ScriptType } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { MenuItem, MenuItems, ActionMenuButton, QuickAction } from '../Menu';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { useState } from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { GeneralCommands } from '../../../constants';
|
||||
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
|
||||
import { PinIcon } from '../Icons/PinIcon';
|
||||
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IContentActionsProps {
|
||||
title: string;
|
||||
@@ -22,6 +20,14 @@ export interface IContentActionsProps {
|
||||
relPath: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
listView?: boolean;
|
||||
locale?: I18nConfig;
|
||||
isDefaultLocale?: boolean;
|
||||
translations?: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
onOpen: () => void;
|
||||
}
|
||||
|
||||
@@ -31,25 +37,21 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
relPath,
|
||||
scripts,
|
||||
onOpen,
|
||||
listView
|
||||
listView,
|
||||
isDefaultLocale,
|
||||
translations,
|
||||
locale
|
||||
}: React.PropsWithChildren<IContentActionsProps>) => {
|
||||
const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom);
|
||||
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<any>(null);
|
||||
const [popperElement, setPopperElement] = useState<any>(null);
|
||||
const { styles, attributes, forceUpdate } = usePopper(referenceElement, popperElement, {
|
||||
placement: listView ? 'right-start' : 'bottom-end',
|
||||
strategy: 'fixed'
|
||||
});
|
||||
|
||||
const onView = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const onView = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onOpen();
|
||||
};
|
||||
|
||||
const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const onDelete = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
setShowDeletionAlert(true);
|
||||
};
|
||||
@@ -61,7 +63,11 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
setShowDeletionAlert(false);
|
||||
};
|
||||
|
||||
const openOnWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const onOpenFile = (filePath: string) => {
|
||||
messageHandler.send(DashboardMessage.openFile, filePath);
|
||||
}
|
||||
|
||||
const openOnWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (settings?.websiteUrl && path) {
|
||||
Messenger.send(GeneralCommands.toVSCode.openOnWebsite, {
|
||||
@@ -71,14 +77,14 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
}
|
||||
}, [settings?.websiteUrl, path]);
|
||||
|
||||
const pinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const pinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
messageHandler.request<string[]>(DashboardMessage.pinItem, path).then((result) => {
|
||||
setPinnedItems(result || []);
|
||||
})
|
||||
}, [path]);
|
||||
|
||||
const unpinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const unpinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
messageHandler.request<string[]>(DashboardMessage.unpinItem, path).then((result) => {
|
||||
setPinnedItems(result || []);
|
||||
@@ -86,13 +92,20 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
}, [path]);
|
||||
|
||||
const runCustomScript = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>, script: CustomScript) => {
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
Messenger.send(DashboardMessage.runCustomScript, { script, path });
|
||||
},
|
||||
[path]
|
||||
);
|
||||
|
||||
const runCommand = React.useCallback((commandId: string) => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
|
||||
command: commandId,
|
||||
args: path
|
||||
})
|
||||
}, [path]);
|
||||
|
||||
const isPinned = React.useMemo(() => {
|
||||
return pinnedItems.includes(relPath);
|
||||
}, [pinnedItems, relPath]);
|
||||
@@ -106,19 +119,56 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
!script.hidden
|
||||
)
|
||||
.map((script) => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<CommandLineIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{script.title}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={(value, e) => runCustomScript(e, script)}
|
||||
/>
|
||||
<DropdownMenuItem key={script.id || script.title} onClick={(e) => runCustomScript(e, script)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
const translationsMenu = React.useMemo(() => {
|
||||
if (!locale || !translations || Object.keys(translations).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const crntLocale = translations[locale.locale];
|
||||
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
|
||||
|
||||
if (otherLocales.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => onOpenFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => onOpenFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
}, [translations, locale, isDefaultLocale]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -129,7 +179,7 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
className={`flex items-center border border-transparent rounded-full ${listView ? '' : 'p-2 -mt-4'
|
||||
} group-hover/card:bg-[var(--vscode-sideBar-background)] group-hover/card:border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<Menu as="div" className={`relative flex text-left`}>
|
||||
<div className={`relative flex text-left`}>
|
||||
{!listView && (
|
||||
<div className="hidden group-hover/card:flex">
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} onClick={onView}>
|
||||
@@ -144,74 +194,62 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonDelete)} onClick={onDelete}>
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={setReferenceElement} className={`flex`}>
|
||||
<ActionMenuButton title={l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)} />
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] data-[state=open]:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<div
|
||||
className="menu_items__wrapper z-20"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<MenuItems
|
||||
updatePopper={forceUpdate || undefined}
|
||||
widthClass="w-44"
|
||||
marginTopClass={listView ? '' : ''}
|
||||
>
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<PinIcon className={`mr-2 h-5 w-5 flex-shrink-0 ${isPinned ? "" : "-rotate-90"}`} aria-hidden={true} />{' '}
|
||||
<span>{isPinned ? l10n.t(LocalizationKey.commonUnpin) : l10n.t(LocalizationKey.commonPin)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={(_, e) => isPinned ? unpinItem(e) : pinItem(e)}
|
||||
/>
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={(_, e) => onView(e)}
|
||||
/>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={(e) => isPinned ? unpinItem(e) : pinItem(e)}>
|
||||
<PinIcon className={`mr-2 h-4 w-4 ${isPinned ? "" : "-rotate-90"}`} aria-hidden={true} />
|
||||
<span>{isPinned ? l10n.t(LocalizationKey.commonUnpin) : l10n.t(LocalizationKey.commonPin)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onView}>
|
||||
<EyeIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{
|
||||
settings?.websiteUrl && (
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<GlobeEuropeAfricaIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={(_, e) => openOnWebsite(e)}
|
||||
/>
|
||||
<DropdownMenuItem onClick={openOnWebsite}>
|
||||
<GlobeEuropeAfricaIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
locale && isDefaultLocale && (
|
||||
<DropdownMenuItem onClick={() => runCommand(COMMAND_NAME.i18n.create)}>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsCreate)}</span>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
{translationsMenu}
|
||||
|
||||
{customScriptActions}
|
||||
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={(_, e) => onDelete(e)}
|
||||
/>
|
||||
</MenuItems>
|
||||
</div>
|
||||
</Menu>
|
||||
<DropdownMenuItem onClick={onDelete} className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
|
||||
<TrashIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
26
src/dashboardWebView/components/Contents/I18nLabel.tsx
Normal file
26
src/dashboardWebView/components/Contents/I18nLabel.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { Page } from '../../models';
|
||||
import { ChevronDownIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuItem } from '../Menu';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface II18nLabelProps {
|
||||
page: Page;
|
||||
}
|
||||
|
||||
export const I18nLabel: React.FunctionComponent<II18nLabelProps> = ({
|
||||
page
|
||||
}: React.PropsWithChildren<II18nLabelProps>) => {
|
||||
if (!page.fmLocale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-2 flex items-center">
|
||||
<LanguageIcon className="mr-1 h-4 w-4 inline-block" />
|
||||
<span className="text-xs">{page.fmLocale.title || page.fmLocale.locale}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { LocalizationKey } from '../../../localization';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { I18nLabel } from './I18nLabel';
|
||||
|
||||
export interface IItemProps extends Page { }
|
||||
|
||||
@@ -69,6 +70,34 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return [];
|
||||
}, [settings, pageData]);
|
||||
|
||||
const statusPlaceholder = useMemo(() => {
|
||||
if (!statusHtml && !cardFields?.state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
statusHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: statusHtml }} />
|
||||
) : (
|
||||
cardFields?.state && draftField && draftField.name && typeof pageData[draftField.name] !== "undefined" ? <Status draft={pageData[draftField.name]} published={pageData.fmPublished} /> : null
|
||||
)
|
||||
)
|
||||
}, [statusHtml, cardFields?.state, draftField, pageData]);
|
||||
|
||||
const datePlaceholder = useMemo(() => {
|
||||
if (!dateHtml && !cardFields?.date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
dateHtml ? (
|
||||
<div className='mr-4' dangerouslySetInnerHTML={{ __html: dateHtml }} />
|
||||
) : (
|
||||
cardFields?.date && pageData.date ? <DateField className={`mr-4`} value={pageData.date} format={pageData.fmDateFormat} /> : null
|
||||
)
|
||||
)
|
||||
}, [dateHtml, cardFields?.date, pageData]);
|
||||
|
||||
const hasDraftOrDate = useMemo(() => {
|
||||
return cardFields && (cardFields.state || cardFields.date);
|
||||
}, [cardFields]);
|
||||
@@ -80,9 +109,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
className={`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
className={`relative h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]
|
||||
}`}
|
||||
className={`relative h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
{
|
||||
imageHtml ?
|
||||
@@ -105,53 +134,59 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</button>
|
||||
|
||||
<div className="relative p-4 w-full grow">
|
||||
<div className={`flex justify-between items-center ${hasDraftOrDate ? `mb-2` : ``}`}>
|
||||
{
|
||||
statusHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: statusHtml }} />
|
||||
) : (
|
||||
cardFields?.state && draftField && draftField.name && <Status draft={pageData[draftField.name]} published={pageData.fmPublished} />
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
dateHtml ? (
|
||||
<div className='mr-4' dangerouslySetInnerHTML={{ __html: dateHtml }} />
|
||||
) : (
|
||||
cardFields?.date && <DateField className={`mr-4`} value={pageData.date} format={pageData.fmDateFormat} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
(statusPlaceholder || datePlaceholder) && (
|
||||
<div className={`flex justify-between items-center ${hasDraftOrDate ? `mb-2` : ``}`}>
|
||||
{statusPlaceholder}
|
||||
{datePlaceholder}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<ContentActions
|
||||
title={pageData.title}
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
locale={pageData.fmLocale}
|
||||
isDefaultLocale={pageData.fmDefaultLocale}
|
||||
translations={pageData.fmTranslations}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile}
|
||||
/>
|
||||
|
||||
<button onClick={openFile} className={`text-left block`}>
|
||||
<I18nLabel page={pageData} />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
className={`text-left block`}>
|
||||
{
|
||||
titleHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: titleHtml }} />
|
||||
) : (
|
||||
<h2 className="mb-2 font-bold">
|
||||
{escapedTitle}
|
||||
<h2 className="font-bold">
|
||||
<span>{escapedTitle}</span>
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
|
||||
<button onClick={openFile} className={`text-left block`}>
|
||||
{
|
||||
descriptionHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
|
||||
) : (
|
||||
<p className={`text-xs text-[vara(--vscode-titleBar-activeForeground)]`}>{escapedDescription}</p>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
{
|
||||
(escapedDescription || descriptionHtml) && (
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
className={`mt-2 text-left block`}>
|
||||
{
|
||||
descriptionHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
|
||||
) : (
|
||||
<p className={`text-xs text-[vara(--vscode-titleBar-activeForeground)]`}>{escapedDescription}</p>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
tagsHtml ? (
|
||||
@@ -197,7 +232,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`}
|
||||
>
|
||||
<div className="col-span-8 font-bold truncate flex items-center space-x-4">
|
||||
<button title={`Open: ${escapedTitle}`} onClick={openFile}>
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}>
|
||||
{escapedTitle}
|
||||
</button>
|
||||
|
||||
|
||||
66
src/dashboardWebView/components/Filters/LanguageFilter.tsx
Normal file
66
src/dashboardWebView/components/Filters/LanguageFilter.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as React from 'react';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuSeparator } from '../../../components/shadcn/Dropdown';
|
||||
import { LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { DEFAULT_LOCALE_STATE, LocaleAtom, LocalesAtom } from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ILanguageFilterProps { }
|
||||
|
||||
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = ({ }: React.PropsWithChildren<ILanguageFilterProps>) => {
|
||||
const locales = useRecoilValue(LocalesAtom);
|
||||
const [crntLocale, setCrntLocale] = useRecoilState(LocaleAtom);
|
||||
|
||||
const crntLocaleName = React.useMemo(() => {
|
||||
if (!crntLocale || !locales || locales.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const locale = locales.find(locale => locale.locale === crntLocale);
|
||||
|
||||
return locale?.title || locale?.locale;
|
||||
}, [crntLocale, locales]);
|
||||
|
||||
if (!locales || locales.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={
|
||||
<>
|
||||
<LanguageIcon className={`inline-block w-4 h-4 mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardFiltersLanguageFilterLabel)}</span>
|
||||
</>
|
||||
}
|
||||
title={crntLocaleName || l10n.t(LocalizationKey.dashboardFiltersLanguageFilterAll)}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
<MenuItem
|
||||
title={l10n.t(LocalizationKey.dashboardFiltersLanguageFilterAll)}
|
||||
value={null}
|
||||
isCurrent={crntLocale === DEFAULT_LOCALE_STATE}
|
||||
onClick={() => setCrntLocale(DEFAULT_LOCALE_STATE)}
|
||||
/>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
locales.map((locale) => (
|
||||
<MenuItem
|
||||
key={locale.locale}
|
||||
title={locale.title || locale.locale}
|
||||
value={locale.locale}
|
||||
isCurrent={locale.locale === crntLocale}
|
||||
onClick={(value) => setCrntLocale(value)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
CategoryAtom,
|
||||
DEFAULT_TAG_STATE,
|
||||
DEFAULT_CATEGORY_STATE,
|
||||
FiltersAtom
|
||||
FiltersAtom,
|
||||
LocaleAtom,
|
||||
DEFAULT_LOCALE_STATE
|
||||
} from '../../state';
|
||||
import { DefaultValue } from 'recoil';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
@@ -34,12 +36,14 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
const locale = useRecoilValue(LocaleAtom);
|
||||
const filters = useRecoilValue(FiltersAtom);
|
||||
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetFolder = useResetRecoilState(FolderAtom);
|
||||
const resetTag = useResetRecoilState(TagAtom);
|
||||
const resetCategory = useResetRecoilState(CategoryAtom);
|
||||
const resetLocale = useResetRecoilState(LocaleAtom);
|
||||
const resetFilters = useResetRecoilState(FiltersAtom);
|
||||
|
||||
const reset = () => {
|
||||
@@ -48,6 +52,7 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
resetFolder();
|
||||
resetTag();
|
||||
resetCategory();
|
||||
resetLocale();
|
||||
resetFilters();
|
||||
};
|
||||
|
||||
@@ -61,19 +66,20 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
folder !== DEFAULT_FOLDER_STATE ||
|
||||
tag !== DEFAULT_TAG_STATE ||
|
||||
category !== DEFAULT_CATEGORY_STATE ||
|
||||
locale !== DEFAULT_LOCALE_STATE ||
|
||||
hasCustomFilters
|
||||
) {
|
||||
setShow(true);
|
||||
} else {
|
||||
setShow(false);
|
||||
}
|
||||
}, [folder, tag, category, hasCustomFilters]);
|
||||
}, [folder, tag, category, locale, hasCustomFilters]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`flex items-center hover:text-[var(--vscode-textLink-activeForeground)]`}
|
||||
className={`flex items-center hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={reset}
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderClearFiltersTitle)}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { FunnelIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { FunnelIcon } from '@heroicons/react/24/solid';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IFilterProps {
|
||||
label: string;
|
||||
@@ -25,37 +25,35 @@ export const Filter: React.FunctionComponent<IFilterProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton
|
||||
label={
|
||||
<>
|
||||
<FunnelIcon className={`inline-block w-5 h-5 mr-1`} />
|
||||
<span>{label}</span>
|
||||
</>
|
||||
}
|
||||
title={activeItem || DEFAULT_VALUE}
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={
|
||||
<>
|
||||
<FunnelIcon className={`inline-block w-4 h-4 mr-2`} />
|
||||
<span>{label}</span>
|
||||
</>
|
||||
}
|
||||
title={activeItem || DEFAULT_VALUE}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<MenuItem
|
||||
title={DEFAULT_VALUE}
|
||||
value={null}
|
||||
isCurrent={!activeItem}
|
||||
onClick={() => onClick(null)}
|
||||
/>
|
||||
|
||||
<MenuItems disablePopper>
|
||||
{items.map((option) => (
|
||||
<MenuItem
|
||||
title={DEFAULT_VALUE}
|
||||
value={null}
|
||||
isCurrent={!!activeItem}
|
||||
onClick={() => onClick(null)}
|
||||
key={option}
|
||||
title={option}
|
||||
value={option}
|
||||
isCurrent={option === activeItem}
|
||||
onClick={() => onClick(option)}
|
||||
/>
|
||||
|
||||
{items.map((option) => (
|
||||
<MenuItem
|
||||
key={option}
|
||||
title={option}
|
||||
value={option}
|
||||
isCurrent={option === activeItem}
|
||||
onClick={() => onClick(option)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CategoryAtom, SettingsSelector, TagAtom, FiltersAtom, FilterValuesAtom
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { firstToUpper } from '../../../helpers/StringHelpers';
|
||||
import { LanguageFilter } from '../Filters/LanguageFilter';
|
||||
|
||||
export interface IFiltersProps { }
|
||||
|
||||
@@ -17,7 +18,6 @@ export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWi
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
const otherFilters = useMemo(() => settings?.filters?.filter((filter) => filter !== "pageFolders" && filter !== "tags" && filter !== "categories"), [settings?.filters]);
|
||||
|
||||
const otherFilterValues = useMemo(() => {
|
||||
@@ -74,6 +74,8 @@ export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWi
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageFilter />
|
||||
|
||||
{
|
||||
settings?.filters?.includes("pageFolders") && (
|
||||
<FoldersFilter />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { FolderAtom, SettingsSelector } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IFoldersFilterProps { }
|
||||
|
||||
@@ -21,29 +21,27 @@ export const FoldersFilter: React.FunctionComponent<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton label={l10n.t(LocalizationKey.dashboardHeaderFoldersMenuButtonShowing)} title={crntFolder || DEFAULT_TYPE} />
|
||||
<DropdownMenu>
|
||||
<MenuButton label={l10n.t(LocalizationKey.dashboardHeaderFoldersMenuButtonShowing)} title={crntFolder || DEFAULT_TYPE} />
|
||||
|
||||
<MenuItems disablePopper>
|
||||
<DropdownMenuContent>
|
||||
<MenuItem
|
||||
title={DEFAULT_TYPE}
|
||||
value={null}
|
||||
isCurrent={!crntFolder}
|
||||
onClick={(value) => setCrntFolder(value)}
|
||||
/>
|
||||
|
||||
{contentFolders.map((option) => (
|
||||
<MenuItem
|
||||
title={DEFAULT_TYPE}
|
||||
value={null}
|
||||
isCurrent={!crntFolder}
|
||||
key={option.title}
|
||||
title={option.title}
|
||||
value={option.title}
|
||||
isCurrent={option.title === crntFolder}
|
||||
onClick={(value) => setCrntFolder(value)}
|
||||
/>
|
||||
|
||||
{contentFolders.map((option) => (
|
||||
<MenuItem
|
||||
key={option.title}
|
||||
title={option.title}
|
||||
value={option.title}
|
||||
isCurrent={option.title === crntFolder}
|
||||
onClick={(value) => setCrntFolder(value)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import { AllPagesAtom, GroupingAtom } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IGroupingProps { }
|
||||
|
||||
@@ -42,22 +42,20 @@ export const Grouping: React.FunctionComponent<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton label={l10n.t(LocalizationKey.dashboardHeaderGroupingMenuButtonLabel)} title={crntGroup?.name || ''} />
|
||||
<DropdownMenu>
|
||||
<MenuButton label={l10n.t(LocalizationKey.dashboardHeaderGroupingMenuButtonLabel)} title={crntGroup?.name || ''} />
|
||||
|
||||
<MenuItems disablePopper>
|
||||
{GROUP_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.name}
|
||||
value={option.id}
|
||||
isCurrent={option.id === crntGroup?.id}
|
||||
onClick={(value) => setGroup(value)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
<DropdownMenuContent>
|
||||
{GROUP_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.name}
|
||||
value={option.id}
|
||||
isCurrent={option.id === crntGroup?.id}
|
||||
onClick={(value) => setGroup(value)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -164,7 +164,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
<Searchbox />
|
||||
|
||||
<div className={`flex items-center justify-end space-x-4 flex-1`}>
|
||||
<SyncButton />
|
||||
{/* <SyncButton /> */}
|
||||
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ArrowsRightLeftIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IProjectSwitcherProps { }
|
||||
|
||||
@@ -32,8 +32,8 @@ export const ProjectSwitcher: React.FunctionComponent<IProjectSwitcherProps> = (
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center mr-4 z-[51]">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<div className="mr-4 z-[51]">
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={(
|
||||
<div className="inline-flex items-center">
|
||||
@@ -43,7 +43,7 @@ export const ProjectSwitcher: React.FunctionComponent<IProjectSwitcherProps> = (
|
||||
)}
|
||||
title={crntProject} />
|
||||
|
||||
<MenuItems disablePopper>
|
||||
<DropdownMenuContent>
|
||||
{projects.map((p) => (
|
||||
<MenuItem
|
||||
key={p.name}
|
||||
@@ -53,8 +53,8 @@ export const ProjectSwitcher: React.FunctionComponent<IProjectSwitcherProps> = (
|
||||
onClick={(value) => setProject(p.name)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div >
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { ExtensionState } from '../../../constants';
|
||||
@@ -9,10 +8,11 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { NavigationType } from '../../models';
|
||||
import { SortingOption } from '../../models/SortingOption';
|
||||
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import { Sorting as SortingHelpers } from '../../../helpers/Sorting';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface ISortingProps {
|
||||
disableCustomSorting?: boolean;
|
||||
@@ -177,26 +177,24 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton
|
||||
label={l10n.t(LocalizationKey.dashboardHeaderSortingLabel)}
|
||||
title={crntSort?.title || crntSort?.name || ''}
|
||||
disabled={!!searchValue}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={l10n.t(LocalizationKey.dashboardHeaderSortingLabel)}
|
||||
title={crntSort?.title || crntSort?.name || ''}
|
||||
disabled={!!searchValue}
|
||||
/>
|
||||
|
||||
<MenuItems widthClass="w-48" disablePopper>
|
||||
{allOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.title || option.name}
|
||||
value={option}
|
||||
isCurrent={option.id === crntSort.id}
|
||||
onClick={(value) => updateSorting(value)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
<DropdownMenuContent>
|
||||
{allOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.title || option.name}
|
||||
value={option}
|
||||
isCurrent={option.id === crntSort.id}
|
||||
onClick={(value) => updateSorting(value)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,15 +18,15 @@ export const SyncButton: React.FunctionComponent<ISyncButtonProps> = (
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
|
||||
const pull = () => {
|
||||
Messenger.send(GeneralCommands.toVSCode.gitSync);
|
||||
Messenger.send(GeneralCommands.toVSCode.git.sync);
|
||||
};
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const { command } = message.data;
|
||||
|
||||
if (command === GeneralCommands.toWebview.gitSyncingStart) {
|
||||
if (command === GeneralCommands.toWebview.git.syncingStart) {
|
||||
setIsSyncing(true);
|
||||
} else if (command === GeneralCommands.toWebview.gitSyncingEnd) {
|
||||
} else if (command === GeneralCommands.toWebview.git.syncingEnd) {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
227
src/dashboardWebView/components/Media/DetailsForm.tsx
Normal file
227
src/dashboardWebView/components/Media/DetailsForm.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DetailsInput } from './DetailsInput';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DEFAULT_MEDIA_CONTENT_TYPE, MediaInfo, UnmappedMedia } from '../../../models';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { basename } from 'path';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { PageSelector, SelectedMediaFolderSelector, SettingsAtom } from '../../state';
|
||||
|
||||
export interface IDetailsFormProps {
|
||||
media: MediaInfo;
|
||||
isImageFile: boolean;
|
||||
isVideoFile: boolean;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const DetailsForm: React.FunctionComponent<IDetailsFormProps> = ({
|
||||
media,
|
||||
isImageFile,
|
||||
isVideoFile,
|
||||
onDismiss,
|
||||
}: React.PropsWithChildren<IDetailsFormProps>) => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const [filename, setFilename] = React.useState<string>(media.filename);
|
||||
const [unmapped, setUnmapped] = React.useState<UnmappedMedia[]>([]);
|
||||
const [metadata, setMetadata] = React.useState<{ [fieldName: string]: string }>({});
|
||||
|
||||
const fileInfo = useMemo(() => {
|
||||
const fileInfo = filename ? basename(filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
const name = fileInfo?.join('.');
|
||||
|
||||
return { name, extension };
|
||||
}, [filename]);
|
||||
|
||||
const fields = useMemo(() => {
|
||||
const contentType = settings?.media.contentTypes.find((c) => c.fileTypes?.map(t => t.toLowerCase()).includes(fileInfo.extension as string)) || DEFAULT_MEDIA_CONTENT_TYPE;
|
||||
return contentType.fields;
|
||||
}, [fileInfo, settings?.media.contentTypes]);
|
||||
|
||||
const updateMetadata = useCallback((fieldName: string, value: string) => {
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata,
|
||||
[fieldName]: value
|
||||
}));
|
||||
}, [metadata]);
|
||||
|
||||
const remapMetadata = useCallback((item: UnmappedMedia) => {
|
||||
Messenger.send(DashboardMessage.remapMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
unmappedItem: item,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onDismiss();
|
||||
}, [media, selectedFolder, page]);
|
||||
|
||||
const onSubmitMetadata = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
filename,
|
||||
page,
|
||||
folder: selectedFolder,
|
||||
metadata,
|
||||
});
|
||||
|
||||
onDismiss();
|
||||
}, [media, filename, metadata, selectedFolder, page, onDismiss]);
|
||||
|
||||
const formFields = useMemo(() => {
|
||||
return fields.map((field) => {
|
||||
if (field.name === "title") {
|
||||
return (
|
||||
<div key="title">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`title`} value={metadata?.title || ""} onChange={(e) => updateMetadata("title", e)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.name === "caption") {
|
||||
if (isImageFile || isVideoFile) {
|
||||
return (
|
||||
<div key="caption">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`caption`} value={metadata?.caption || ""} onChange={(e) => updateMetadata("caption", e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (field.name === "alt") {
|
||||
if (isImageFile) {
|
||||
return (
|
||||
<div key="alt">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`alt`} value={metadata?.alt || ""} onChange={(e) => updateMetadata("alt", e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.name}>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{field.title || field.name}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={field.name} value={metadata[field.name] || ""} onChange={(e) => updateMetadata(field.name, e)} isTextArea={!field.single} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [fields, metadata, updateMetadata]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fields && media.metadata && fileInfo?.extension) {
|
||||
const metadataFields: { [fieldName: string]: string } = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
metadataFields[field.name] = (media.metadata[field.name] || '') as string;
|
||||
});
|
||||
|
||||
setMetadata(metadataFields);
|
||||
}
|
||||
}, [fileInfo, media.metadata, fields]);
|
||||
|
||||
useEffect(() => {
|
||||
messageHandler.request<UnmappedMedia[]>(DashboardMessage.getUnmappedMedia, media.filename).then((result) => {
|
||||
setUnmapped(result);
|
||||
});
|
||||
}, [media.filename]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className={`text-base text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)}
|
||||
</h3>
|
||||
|
||||
{
|
||||
unmapped && unmapped.length > 0 && (
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)}
|
||||
</p>
|
||||
<ul className='pl-4'>
|
||||
{
|
||||
unmapped.map((item) => (
|
||||
<li className='list-disc'>
|
||||
<button
|
||||
key={item.file}
|
||||
className='text-left hover:text-[var(--frontmatter-link-hover)]'
|
||||
onClick={() => remapMetadata(item)}>
|
||||
{item.file}{item.metadata.title ? ` (${item.metadata.title})` : ''}
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)}
|
||||
</label>
|
||||
<div className="relative mt-1">
|
||||
<DetailsInput name={`filename`} value={fileInfo.name || ""} onChange={(e) => setFilename(`${e}.${fileInfo.extension}`)} />
|
||||
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span className={`sm:text-sm placeholder-[var(--vscode-input-placeholderForeground)]`}>.{fileInfo?.extension}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formFields}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1`}
|
||||
onClick={onSubmitMetadata}
|
||||
disabled={!filename}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]`}
|
||||
onClick={onDismiss}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonCancel)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,17 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { PencilSquareIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { format } from 'date-fns';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { Fragment, useCallback, useMemo } from 'react';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import { DateHelper } from '../../../helpers/DateHelper';
|
||||
import { MediaInfo, UnmappedMedia } from '../../../models';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { PageSelector, SelectedMediaFolderSelector } from '../../state';
|
||||
import { DEFAULT_MEDIA_CONTENT_TYPE, MediaInfo } from '../../../models';
|
||||
import { DetailsItem } from './DetailsItem';
|
||||
import { DetailsInput } from './DetailsInput';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DetailsForm } from './DetailsForm';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsAtom } from '../../state';
|
||||
import { basename } from 'path';
|
||||
|
||||
export interface IDetailsSlideOverProps {
|
||||
imgSrc: string;
|
||||
@@ -42,62 +40,56 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
isImageFile,
|
||||
isVideoFile
|
||||
}: React.PropsWithChildren<IDetailsSlideOverProps>) => {
|
||||
const [filename, setFilename] = React.useState<string>(media.filename);
|
||||
const [caption, setCaption] = React.useState<string | undefined>(media.caption);
|
||||
const [title, setTitle] = React.useState<string | undefined>(media.title);
|
||||
const [unmapped, setUnmapped] = React.useState<UnmappedMedia[]>([]);
|
||||
const [alt, setAlt] = React.useState(media.alt);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const createdDate = useMemo(() => DateHelper.tryParse(media.ctime), [media]);
|
||||
const modifiedDate = useMemo(() => DateHelper.tryParse(media.mtime), [media]);
|
||||
|
||||
const fileInfo = filename ? basename(filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
const name = fileInfo?.join('.');
|
||||
const extension = useMemo(() => {
|
||||
const fileInfo = media.filename ? basename(media.filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
return extension;
|
||||
}, [media.filename]);
|
||||
|
||||
const onSubmitMetadata = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
filename,
|
||||
caption,
|
||||
alt,
|
||||
title,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onEditClose();
|
||||
}, [media, filename, caption, alt, title, selectedFolder, page]);
|
||||
|
||||
const remapMetadata = useCallback((item: UnmappedMedia) => {
|
||||
Messenger.send(DashboardMessage.remapMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
unmappedItem: item,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onEditClose();
|
||||
}, [media, filename, caption, alt, title, selectedFolder, page]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTitle(media.title);
|
||||
setAlt(media.alt);
|
||||
setCaption(media.caption);
|
||||
setFilename(media.filename);
|
||||
}, [media]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showForm) {
|
||||
messageHandler.request<UnmappedMedia[]>(DashboardMessage.getUnmappedMedia, filename).then((result) => {
|
||||
setUnmapped(result);
|
||||
});
|
||||
} else {
|
||||
setUnmapped([]);
|
||||
const fields = useMemo(() => {
|
||||
if (extension) {
|
||||
const contentType = settings?.media.contentTypes.find((c) => c.fileTypes?.map(t => t.toLowerCase()).includes(extension as string)) || DEFAULT_MEDIA_CONTENT_TYPE;
|
||||
return contentType.fields;
|
||||
}
|
||||
}, [showForm, filename]);
|
||||
}, [extension, settings?.media.contentTypes]);
|
||||
|
||||
const detailItems = useMemo(() => {
|
||||
const items = [];
|
||||
|
||||
items.push(
|
||||
<DetailsItem key="filename" title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)} details={media.filename} />
|
||||
);
|
||||
|
||||
fields?.forEach((field) => {
|
||||
if (field.name === "title") {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
|
||||
);
|
||||
} else if (field.name === "caption") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
|
||||
);
|
||||
}
|
||||
} else if (field.name === "alt") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
|
||||
);
|
||||
}
|
||||
} else {
|
||||
items.push(
|
||||
<DetailsItem title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [fields, media.metadata]);
|
||||
|
||||
return (
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
@@ -168,101 +160,11 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
<div>
|
||||
{/* EDIT METADATA FORM */}
|
||||
{showForm && (
|
||||
<>
|
||||
<h3 className={`text-base text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)}
|
||||
</h3>
|
||||
|
||||
{
|
||||
unmapped && unmapped.length > 0 && (
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)}
|
||||
</p>
|
||||
<ul className='pl-4'>
|
||||
{
|
||||
unmapped.map((item) => (
|
||||
<li className='list-disc'>
|
||||
<button
|
||||
key={item.file}
|
||||
className='text-left hover:text-[var(--frontmatter-link-hover)]'
|
||||
onClick={() => remapMetadata(item)}>
|
||||
{item.file}{item.metadata.title ? ` (${item.metadata.title})` : ''}
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)}
|
||||
</p>
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)}
|
||||
</label>
|
||||
<div className="relative mt-1">
|
||||
<DetailsInput name={`filename`} value={name || ""} onChange={(e) => setFilename(`${e}.${extension}`)} />
|
||||
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span className={`sm:text-sm placeholder-[var(--vscode-input-placeholderForeground)]`}>.{extension}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`title`} value={title || ""} onChange={(e) => setTitle(e)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(isImageFile || isVideoFile) && (
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`caption`} value={caption || ""} onChange={(e) => setCaption(e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isImageFile && (
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`alt`} value={alt || ""} onChange={(e) => setAlt(e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1`}
|
||||
onClick={onSubmitMetadata}
|
||||
disabled={!filename}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]`}
|
||||
onClick={onEditClose}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonCancel)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
<DetailsForm
|
||||
media={media}
|
||||
isImageFile={isImageFile}
|
||||
isVideoFile={isVideoFile}
|
||||
onDismiss={onEditClose} />
|
||||
)}
|
||||
|
||||
{!showForm && (
|
||||
@@ -275,15 +177,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
</button>
|
||||
</h3>
|
||||
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)} details={media.filename} />
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.title || ""} />
|
||||
|
||||
{isImageFile && (
|
||||
<>
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.caption || ''} />
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.alt || ''} />
|
||||
</>
|
||||
)}
|
||||
{detailItems}
|
||||
</dl>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import {
|
||||
ClipboardIcon,
|
||||
CodeBracketIcon,
|
||||
DocumentIcon,
|
||||
EyeIcon,
|
||||
MusicalNoteIcon,
|
||||
PencilIcon,
|
||||
PhotoIcon,
|
||||
PlusIcon,
|
||||
CommandLineIcon,
|
||||
TrashIcon,
|
||||
VideoCameraIcon
|
||||
VideoCameraIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { CustomScript } from '../../../helpers/CustomScript';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { ScriptType, Snippet } from '../../../models';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
@@ -29,23 +21,22 @@ import {
|
||||
SettingsSelector,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
import { MenuItem, MenuItems } from '../Menu';
|
||||
import { ActionMenuButton } from '../Menu/ActionMenuButton';
|
||||
import { QuickAction } from '../Menu/QuickAction';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { InfoDialog } from '../Modals/InfoDialog';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { MediaSnippetForm } from './MediaSnippetForm';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ItemMenu } from './ItemMenu';
|
||||
import { getRelPath } from '../../utils';
|
||||
import { Snippet } from '../../../models';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
}
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
media
|
||||
media,
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const [, setLightbox] = useRecoilState(LightboxAtom);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
@@ -55,24 +46,19 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [showSnippetFormDialog, setShowSnippetFormDialog] = useState(false);
|
||||
const [mediaData, setMediaData] = useState<any | undefined>(undefined);
|
||||
const [caption, setCaption] = useState(media.caption);
|
||||
const [alt, setAlt] = useState(media.alt);
|
||||
const [filename, setFilename] = useState<string | null>(null);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const relPath = useMemo(() => {
|
||||
return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder);
|
||||
}, [media.fsPath, settings?.staticFolder, settings?.wsFolder]);
|
||||
|
||||
const hasViewData = useMemo(() => {
|
||||
return viewData?.data?.filePath !== undefined;
|
||||
}, [viewData]);
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<any>(null);
|
||||
const [popperElement, setPopperElement] = useState<any>(null);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed'
|
||||
});
|
||||
|
||||
const mediaSnippets = useMemo(() => {
|
||||
if (!settings?.snippets) {
|
||||
return [];
|
||||
@@ -101,44 +87,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return '';
|
||||
};
|
||||
|
||||
const getRelPath = () => {
|
||||
let relPath: string | undefined = '';
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
const wsFolderParsed = parseWinPath(settings.wsFolder);
|
||||
const mediaParsed = parseWinPath(media.fsPath);
|
||||
|
||||
relPath = mediaParsed.split(wsFolderParsed).pop();
|
||||
|
||||
// If the static folder is the root, we can just return the relative path
|
||||
if (settings.staticFolder === "/") {
|
||||
return relPath;
|
||||
} else if (settings.staticFolder && relPath) {
|
||||
const staticFolderParsed = parseWinPath(settings.staticFolder);
|
||||
relPath = relPath.split(staticFolderParsed).pop();
|
||||
}
|
||||
}
|
||||
return relPath;
|
||||
};
|
||||
|
||||
const getFileName = () => {
|
||||
return basename(parseWinPath(media.fsPath) || '');
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || '');
|
||||
};
|
||||
|
||||
const runCustomScript = (script: CustomScript) => {
|
||||
Messenger.send(DashboardMessage.runCustomScript, {
|
||||
script,
|
||||
path: media.fsPath
|
||||
});
|
||||
};
|
||||
|
||||
const insertToArticle = () => {
|
||||
const relPath = getRelPath();
|
||||
|
||||
const insertIntoArticle = useCallback(() => {
|
||||
if (viewData?.data?.type === 'file') {
|
||||
Messenger.send(DashboardMessage.insertFile, {
|
||||
relPath: parseWinPath(relPath) || '',
|
||||
@@ -150,7 +103,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
position: viewData?.data?.position || null,
|
||||
blockData:
|
||||
typeof viewData?.data?.blockData !== 'undefined' ? viewData?.data?.blockData : undefined,
|
||||
title: media.title
|
||||
title: media.metadata.title
|
||||
});
|
||||
} else {
|
||||
Messenger.send(DashboardMessage.insertMedia, {
|
||||
@@ -163,12 +116,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
position: viewData?.data?.position || null,
|
||||
blockData:
|
||||
typeof viewData?.data?.blockData !== 'undefined' ? viewData?.data?.blockData : undefined,
|
||||
alt: alt || '',
|
||||
caption: caption || '',
|
||||
title: media.title || ''
|
||||
alt: media.metadata.alt || '',
|
||||
caption: media.metadata.caption || '',
|
||||
title: media.metadata.title || ''
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [media, settings, viewData, relPath]);
|
||||
|
||||
const insertSnippet = useCallback(() => {
|
||||
if (mediaSnippets.length === 1) {
|
||||
@@ -186,16 +139,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
(snippet: Snippet) => {
|
||||
setShowSnippetSelection(false);
|
||||
|
||||
const relPath = getRelPath();
|
||||
|
||||
const fieldData = {
|
||||
mediaUrl: (parseWinPath(relPath) || '').replace(/ /g, '%20'),
|
||||
alt: alt || '',
|
||||
caption: caption || '',
|
||||
title: media.title || '',
|
||||
filename: basename(relPath || ''),
|
||||
mediaWidth: media?.dimensions?.width?.toString() || '',
|
||||
mediaHeight: media?.dimensions?.height?.toString() || ''
|
||||
mediaHeight: media?.dimensions?.height?.toString() || '',
|
||||
...media.metadata
|
||||
};
|
||||
|
||||
if (!snippet.fields || snippet.fields.length === 0) {
|
||||
@@ -215,7 +164,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setMediaData(fieldData);
|
||||
}
|
||||
},
|
||||
[alt, caption, media, settings, viewData, mediaSnippets]
|
||||
[media, settings, viewData, mediaSnippets, relPath]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -223,8 +172,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
*/
|
||||
const insertMediaSnippetToArticle = useCallback(
|
||||
(output: string) => {
|
||||
const relPath = getRelPath();
|
||||
|
||||
Messenger.send(DashboardMessage.insertMedia, {
|
||||
relPath: parseWinPath(relPath) || '',
|
||||
file: viewData?.data?.filePath,
|
||||
@@ -233,20 +180,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
snippet: output
|
||||
});
|
||||
},
|
||||
[viewData]
|
||||
[viewData, relPath]
|
||||
);
|
||||
|
||||
const deleteMedia = () => {
|
||||
setShowAlert(true);
|
||||
};
|
||||
|
||||
const revealMedia = () => {
|
||||
Messenger.send(DashboardMessage.revealMedia, {
|
||||
file: media.fsPath,
|
||||
folder: selectedFolder
|
||||
});
|
||||
};
|
||||
|
||||
const confirmDeletion = () => {
|
||||
Messenger.send(DashboardMessage.deleteMedia, {
|
||||
file: media.fsPath,
|
||||
@@ -290,10 +226,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return sizeDetails.join(' - ');
|
||||
};
|
||||
|
||||
const viewMediaDetails = () => {
|
||||
setShowDetails(true);
|
||||
};
|
||||
|
||||
const openLightbox = useCallback(() => {
|
||||
if (isImageFile) {
|
||||
setLightbox(media.vsPath || '');
|
||||
@@ -305,23 +237,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setShowDetails(true);
|
||||
};
|
||||
|
||||
const customScriptActions = () => {
|
||||
return (settings?.scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
|
||||
.map((script) => (
|
||||
<MenuItem
|
||||
key={script.title}
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<CommandLineIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{script.title}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={() => runCustomScript(script)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const isVideoFile = useMemo(() => {
|
||||
if (media.mimeType?.startsWith('video/')) {
|
||||
return true;
|
||||
@@ -404,18 +319,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setMediaData(undefined);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (media.alt !== alt) {
|
||||
setAlt(media.alt);
|
||||
}
|
||||
}, [media.alt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (media.caption !== caption) {
|
||||
setCaption(media.caption);
|
||||
}
|
||||
}, [media.caption]);
|
||||
|
||||
useEffect(() => {
|
||||
const name = basename(parseWinPath(media.fsPath) || '');
|
||||
if (name !== filename) {
|
||||
@@ -455,9 +358,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
} flex items-center justify-center`}
|
||||
>
|
||||
<button
|
||||
title="Insert image"
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemButtomInsertImage)}
|
||||
className={`h-1/3 text-white hover:text-[var(--vscode-button-background)]`}
|
||||
onClick={insertToArticle}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-full h-full hover:drop-shadow-md `} aria-hidden="true" />
|
||||
</button>
|
||||
@@ -465,7 +368,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
{viewData?.data?.position && mediaSnippets.length > 0 && (
|
||||
<div className={`h-full w-1/3 flex items-center justify-center`}>
|
||||
<button
|
||||
title="Insert snippet"
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemButtomInsertSnippet)}
|
||||
className={`h-1/3 text-white hover:text-[var(--vscode-button-background)]`}
|
||||
onClick={insertSnippet}
|
||||
>
|
||||
@@ -480,171 +383,45 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
)}
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-12`}>
|
||||
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<Menu as="div" className="relative z-10 flex text-left">
|
||||
<div className="hidden group-hover/actions:flex">
|
||||
<QuickAction title="View media details" onClick={viewMediaDetails}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
<ItemMenu
|
||||
media={media}
|
||||
relPath={relPath}
|
||||
selectedFolder={selectedFolder}
|
||||
viewData={viewData?.data}
|
||||
snippets={mediaSnippets}
|
||||
scripts={settings?.scripts}
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
showUpdateMedia={updateMetadata}
|
||||
showMediaDetails={() => setShowDetails(true)}
|
||||
processSnippet={processSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
|
||||
<QuickAction title="Edit metadata" onClick={updateMetadata}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.data?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.data.metadataInsert && viewData.data.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.data.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
onClick={insertToArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.data?.position && mediaSnippets.length > 0 && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)}
|
||||
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)} onClick={deleteMedia}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<div ref={setReferenceElement} className={`flex`}>
|
||||
<ActionMenuButton title={l10n.t(LocalizationKey.commonMenu)} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="menu_items__wrapper z-20"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<MenuItems widthClass="w-40">
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<PencilIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={updateMetadata}
|
||||
/>
|
||||
|
||||
{viewData?.data?.filePath ? (
|
||||
<>
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<PlusIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemInsertImage)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={insertToArticle}
|
||||
/>
|
||||
|
||||
{viewData?.data?.position &&
|
||||
mediaSnippets.length > 0 &&
|
||||
mediaSnippets.map((snippet, idx) => (
|
||||
<MenuItem
|
||||
key={idx}
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<CodeBracketIcon
|
||||
className="mr-2 h-5 w-5 flex-shrink-0"
|
||||
aria-hidden={true}
|
||||
/>{' '}
|
||||
<span>{snippet.title}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={() => processSnippet(snippet)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{customScriptActions()}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<ClipboardIcon
|
||||
className="mr-2 h-5 w-5 flex-shrink-0"
|
||||
aria-hidden={true}
|
||||
/>{' '}
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={copyToClipboard}
|
||||
/>
|
||||
|
||||
{customScriptActions()}
|
||||
</>
|
||||
)}
|
||||
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={revealMedia}
|
||||
/>
|
||||
|
||||
<MenuItem
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} />{' '}
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</div>
|
||||
}
|
||||
onClick={deleteMedia}
|
||||
/>
|
||||
</MenuItems>
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--vscode-foreground)]}`}>
|
||||
{basename(parseWinPath(media.fsPath) || '')}
|
||||
</p>
|
||||
{!isImageFile && media.title && (
|
||||
{!isImageFile && media.metadata.title && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.title}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.title}</span>
|
||||
</p>
|
||||
)}
|
||||
{media.caption && (
|
||||
{media.metadata.caption && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.caption}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.caption}</span>
|
||||
</p>
|
||||
)}
|
||||
{!media.caption && media.alt && (
|
||||
{!media.metadata.caption && media.metadata.alt && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.alt}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.alt}</span>
|
||||
</p>
|
||||
)}
|
||||
{(media?.size || media?.dimensions) && (
|
||||
|
||||
193
src/dashboardWebView/components/Media/ItemMenu.tsx
Normal file
193
src/dashboardWebView/components/Media/ItemMenu.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { CustomScript, MediaInfo, ScriptType, Snippet, ViewData } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
|
||||
export interface IItemMenuProps {
|
||||
media: MediaInfo;
|
||||
relPath?: string;
|
||||
selectedFolder: string | null;
|
||||
viewData?: ViewData;
|
||||
snippets: Snippet[];
|
||||
scripts?: CustomScript[];
|
||||
insertIntoArticle: () => void;
|
||||
insertSnippet: () => void;
|
||||
showUpdateMedia: () => void;
|
||||
showMediaDetails: () => void;
|
||||
processSnippet: (snippet: Snippet) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
media,
|
||||
relPath,
|
||||
selectedFolder,
|
||||
viewData,
|
||||
snippets,
|
||||
scripts,
|
||||
insertIntoArticle,
|
||||
insertSnippet,
|
||||
showUpdateMedia,
|
||||
showMediaDetails,
|
||||
processSnippet,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IItemMenuProps>) => {
|
||||
|
||||
const copyToClipboard = React.useCallback(() => {
|
||||
if (relPath) {
|
||||
messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || '');
|
||||
}
|
||||
}, [relPath]);
|
||||
|
||||
const runCustomScript = React.useCallback((script: CustomScript) => {
|
||||
messageHandler.send(DashboardMessage.runCustomScript, {
|
||||
script,
|
||||
path: media.fsPath
|
||||
});
|
||||
}, [media]);
|
||||
|
||||
const revealMedia = React.useCallback(() => {
|
||||
messageHandler.send(DashboardMessage.revealMedia, {
|
||||
file: media.fsPath,
|
||||
folder: selectedFolder
|
||||
});
|
||||
}, [selectedFolder]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
return (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
return (
|
||||
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<div className="relative z-10 flex text-left">
|
||||
<div className="hidden group-hover/actions:flex">
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)} onClick={showMediaDetails}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} onClick={showUpdateMedia}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={showUpdateMedia}>
|
||||
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{
|
||||
viewData?.filePath ? (
|
||||
<>
|
||||
<DropdownMenuItem onClick={insertIntoArticle}>
|
||||
<PlusIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemInsertImage)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{
|
||||
viewData?.position &&
|
||||
snippets.length > 0 &&
|
||||
snippets.map((snippet, idx) => (
|
||||
<DropdownMenuItem key={idx} onClick={() => processSnippet(snippet)}>
|
||||
<CodeBracketIcon
|
||||
className="mr-2 h-4 w-4"
|
||||
aria-hidden={true}
|
||||
/>
|
||||
<span>{snippet.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
|
||||
{customScriptActions}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuItem onClick={copyToClipboard}>
|
||||
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{customScriptActions}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<DropdownMenuItem onClick={revealMedia}>
|
||||
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onDelete} className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
|
||||
<TrashIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -250,7 +250,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
)}
|
||||
|
||||
<List>
|
||||
{allMedia.map((file) => (
|
||||
{allMedia.map((file, idx) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))}
|
||||
</List>
|
||||
|
||||
@@ -33,8 +33,8 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
|
||||
|
||||
return (
|
||||
<SnippetSlideOver
|
||||
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.title || media.filename)}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.title || media.filename)}
|
||||
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.metadata.title || media.filename)}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.metadata.title || media.filename)}
|
||||
isSaveDisabled={false}
|
||||
trigger={insertToArticle}
|
||||
dismiss={onDismiss}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { EllipsisVerticalIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -14,15 +13,14 @@ export const ActionMenuButton: React.FunctionComponent<IActionMenuButtonProps> =
|
||||
ref
|
||||
}: React.PropsWithChildren<IActionMenuButtonProps>) => {
|
||||
return (
|
||||
<Menu.Button
|
||||
<button
|
||||
ref={ref || null}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation()}
|
||||
disabled={disabled}
|
||||
className={`group inline-flex justify-center text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] ${disabled ? 'opacity-50' : ''
|
||||
}`}
|
||||
className={`group inline-flex justify-center text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] ${disabled ? 'opacity-50' : ''}`}
|
||||
>
|
||||
<span className="sr-only">{title}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IMenuButtonProps {
|
||||
label: string | JSX.Element;
|
||||
@@ -15,18 +15,16 @@ export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>{label}:</div>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
<Menu.Button
|
||||
disabled={disabled}
|
||||
className={`group inline-flex justify-center text-sm font-medium text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]`}
|
||||
>
|
||||
{title}
|
||||
<ChevronDownIcon
|
||||
className={`flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-[var(--vscode-textLink-foreground)] group-hover:text-[var(--vscode-textLink-activeForeground)]`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
<DropdownMenuTrigger
|
||||
className='text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)] flex items-center focus:outline-none'
|
||||
disabled={disabled}>
|
||||
<span>{title}</span>
|
||||
<ChevronDownIcon className={`-mr-1 ml-1 h-4 w-4`} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { DropdownMenuItem } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IMenuItemProps {
|
||||
title: JSX.Element | string;
|
||||
value?: any;
|
||||
isCurrent?: boolean;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick: (value: any, e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClick: (value: any, e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
}
|
||||
|
||||
export const MenuItem: React.FunctionComponent<IMenuItemProps> = ({
|
||||
title,
|
||||
value,
|
||||
isCurrent,
|
||||
className,
|
||||
disabled,
|
||||
onClick
|
||||
}: React.PropsWithChildren<IMenuItemProps>) => {
|
||||
return (
|
||||
<Menu.Item>
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={(e) => onClick(value, e)}
|
||||
className={`${!isCurrent ? `font-normal` : `font-bold`
|
||||
} block px-4 py-2 text-sm w-full text-left disabled:opacity-50 text-[var(--vscode-editor-foreground)] hover:bg-[var(--vscode-list-hoverBackground)]`}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<DropdownMenuItem
|
||||
className={`${!isCurrent ? `font-normal` : `font-bold`} ${className || ''}`}
|
||||
disabled={disabled}
|
||||
onClick={(e) => onClick(value, e)}
|
||||
>
|
||||
{title}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
export interface IMenuItemsProps {
|
||||
widthClass?: string;
|
||||
marginTopClass?: string;
|
||||
updatePopper?: () => void;
|
||||
disablePopper?: boolean;
|
||||
}
|
||||
|
||||
export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({
|
||||
widthClass,
|
||||
marginTopClass,
|
||||
children,
|
||||
updatePopper,
|
||||
disablePopper
|
||||
}: React.PropsWithChildren<IMenuItemsProps>) => {
|
||||
return (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
beforeEnter={() => (updatePopper ? updatePopper() : null)}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={`${widthClass || ''} ${marginTopClass || 'mt-2'} ${disablePopper ? 'origin-top-right absolute right-0 z-20' : ''
|
||||
} rounded shadow-2xl ring-1 ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto bg-[var(--vscode-sideBar-background)] ring-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<div className="py-1">{children}</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IQuickActionProps {
|
||||
title: string;
|
||||
className?: string;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({
|
||||
title,
|
||||
className,
|
||||
onClick,
|
||||
children
|
||||
}: React.PropsWithChildren<IQuickActionProps>) => {
|
||||
@@ -15,7 +18,7 @@ export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({
|
||||
type="button"
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
className={`px-2 group inline-flex justify-center text-sm font-medium text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]`}
|
||||
className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)}
|
||||
>
|
||||
{children}
|
||||
<span className="sr-only">{title}</span>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './ActionMenuButton';
|
||||
export * from './MenuButton';
|
||||
export * from './MenuItem';
|
||||
export * from './MenuItems';
|
||||
export * from './QuickAction';
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { GeneralCommands, ExtensionState } from '../../../constants';
|
||||
import { SettingsInput } from './SettingsInput';
|
||||
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
export interface IIntegrationsViewProps { }
|
||||
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = ({ }: React.PropsWithChildren<IIntegrationsViewProps>) => {
|
||||
const [deeplApiKey, setDeeplApiKey] = React.useState<string>('');
|
||||
const [crntDeeplApiKey, setCrntDeeplApiKey] = React.useState<string>('');
|
||||
|
||||
const onSave = React.useCallback(() => {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.set, {
|
||||
key: ExtensionState.Secrets.DeeplApiKey,
|
||||
value: crntDeeplApiKey
|
||||
}).then((apiKey: string) => {
|
||||
setDeeplApiKey(apiKey);
|
||||
});
|
||||
}, [crntDeeplApiKey]);
|
||||
|
||||
const onChange = (_: string, value: string) => {
|
||||
setCrntDeeplApiKey(value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
messageHandler.request<string>(GeneralCommands.toVSCode.secrets.get, ExtensionState.Secrets.DeeplApiKey).then((apiKey: string) => {
|
||||
setDeeplApiKey(apiKey);
|
||||
setCrntDeeplApiKey(apiKey);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='w-full divide-y divide-[var(--frontmatter-border)]'>
|
||||
<div className='py-4 space-y-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsIntegrationsViewDeeplTitle)}</h2>
|
||||
|
||||
<SettingsInput
|
||||
label={l10n.t(LocalizationKey.settingsIntegrationsViewDeeplIntputLabel)}
|
||||
name={ExtensionState.Secrets.DeeplApiKey}
|
||||
value={crntDeeplApiKey || ""}
|
||||
placeholder={l10n.t(LocalizationKey.settingsIntegrationsViewDeeplIntputPlaceholder)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<div className={`mt-4 flex gap-2`}>
|
||||
<VSCodeButton
|
||||
onClick={onSave}
|
||||
disabled={deeplApiKey === crntDeeplApiKey}>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,8 @@ import { useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { CogIcon } from '@heroicons/react/24/solid';
|
||||
import { NavigationType } from '../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ISettingsLinkProps {
|
||||
onNavigate: (navigationType: NavigationType) => void;
|
||||
@@ -20,11 +22,11 @@ export const SettingsLink: React.FunctionComponent<ISettingsLinkProps> = ({
|
||||
return (
|
||||
<button
|
||||
className="flex items-center mr-4 hover:text-[var(--vscode-textLink-activeForeground)]"
|
||||
title={`Settings`}
|
||||
title={l10n.t(LocalizationKey.commonSettings)}
|
||||
onClick={() => onNavigate(NavigationType.Settings)}
|
||||
>
|
||||
<CogIcon className="h-4 w-4" />
|
||||
<span className='sr-only'>Settings</span>
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.commonSettings)}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { COMMAND_NAME } from '../../../constants';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react';
|
||||
import { CommonSettings } from './CommonSettings';
|
||||
import { IntegrationsView } from './IntegrationsView';
|
||||
|
||||
export interface ISettingsViewProps { }
|
||||
|
||||
@@ -54,6 +55,8 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelTab id="view-4">{l10n.t(LocalizationKey.settingsViewIntegration)}</VSCodePanelTab>
|
||||
|
||||
<VSCodePanelView id="view-1">
|
||||
<CommonSettings />
|
||||
</VSCodePanelView>
|
||||
@@ -82,6 +85,10 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
</VSCodePanelView>
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelView id="view-4">
|
||||
<IntegrationsView />
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -260,6 +260,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
ref={formRef}
|
||||
snippetKey={snippetKey}
|
||||
snippet={snippet}
|
||||
filePath={viewData?.data?.filePath}
|
||||
fieldInfo={viewData?.data?.snippetInfo?.fields}
|
||||
selection={viewData?.data?.selection} />
|
||||
</FormDialog>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({
|
||||
<label htmlFor={`title`} className="block text-sm font-medium capitalize">
|
||||
{l10n.t(LocalizationKey.commonTitle)}
|
||||
{' '}
|
||||
<span className={`text-[var(--vscode-editorError-foreground)]`} title="Required field">
|
||||
<span className={`text-[var(--vscode-editorError-foreground)]`} title={l10n.t(LocalizationKey.fieldRequired)}>
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
@@ -72,7 +72,7 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({
|
||||
<label htmlFor={`snippet`} className="block text-sm font-medium capitalize">
|
||||
{l10n.t(LocalizationKey.dashboardSnippetsViewNewFormSnippetInputSnippetLabel)}
|
||||
{' '}
|
||||
<span className="text-[var(--vscode-editorError-foreground)]" title="Required field">
|
||||
<span className="text-[var(--vscode-editorError-foreground)]" title={l10n.t(LocalizationKey.fieldRequired)}>
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { processKnownPlaceholders } from '../../../helpers/PlaceholderHelper';
|
||||
import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { Snippet, SnippetField, SnippetInfoField, SnippetSpecialPlaceholders } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -14,6 +13,7 @@ export interface ISnippetFormProps {
|
||||
snippetKey?: string;
|
||||
snippet: Snippet;
|
||||
selection: string | undefined;
|
||||
filePath?: string;
|
||||
fieldInfo?: SnippetInfoField[];
|
||||
mediaData?: any;
|
||||
onInsert?: (mediaData: any) => void;
|
||||
@@ -24,7 +24,7 @@ export interface SnippetFormHandle {
|
||||
}
|
||||
|
||||
const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFormProps> = (
|
||||
{ snippetKey, snippet, selection, fieldInfo, mediaData, onInsert },
|
||||
{ snippetKey, snippet, selection, filePath, fieldInfo, mediaData, onInsert },
|
||||
ref
|
||||
) => {
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
@@ -41,20 +41,19 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
|
||||
);
|
||||
|
||||
const insertPlaceholderValues = useCallback(
|
||||
(value: SnippetSpecialPlaceholders) => {
|
||||
async (value: SnippetSpecialPlaceholders) => {
|
||||
if (value === 'FM_SELECTED_TEXT') {
|
||||
return selection || '';
|
||||
}
|
||||
|
||||
value = processKnownPlaceholders(
|
||||
value = await messageHandler.request<string>(DashboardMessage.updateSnippetPlaceholders, {
|
||||
value,
|
||||
viewData?.data?.fileTitle || '',
|
||||
settings?.date.format || ''
|
||||
);
|
||||
filePath
|
||||
});
|
||||
|
||||
return value;
|
||||
},
|
||||
[selection]
|
||||
[selection, filePath]
|
||||
);
|
||||
|
||||
const insertValueFromMedia = useCallback(
|
||||
@@ -66,6 +65,10 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
|
||||
if (mediaData[fieldName]) {
|
||||
return mediaData[fieldName];
|
||||
}
|
||||
|
||||
if (mediaData.metadata && mediaData.metadata[fieldName]) {
|
||||
return mediaData.metadata[fieldName];
|
||||
}
|
||||
},
|
||||
[mediaData]
|
||||
);
|
||||
@@ -124,7 +127,7 @@ ${snippetBody}
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
const processFields = useCallback(async () => {
|
||||
// Get all placeholder variables from the snippet
|
||||
const body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
||||
|
||||
@@ -143,7 +146,7 @@ ${snippetBody}
|
||||
if (idx > -1) {
|
||||
allFields.push({
|
||||
...field,
|
||||
value: insertPlaceholderValues(field.default || '')
|
||||
value: await insertPlaceholderValues(field.default || '')
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -163,6 +166,10 @@ ${snippetBody}
|
||||
}
|
||||
|
||||
setFields(allFields);
|
||||
}, [snippet, insertPlaceholderValues, insertValueFromMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
processFields();
|
||||
}, [snippet]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Settings } from '../../models/Settings';
|
||||
import { Status } from '../../models/Status';
|
||||
import { Step } from './Step';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { MenuItem } from '../Menu';
|
||||
import { Framework, StaticFolder, Template } from '../../../models';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
@@ -19,6 +18,7 @@ import { Spinner } from '../Common/Spinner';
|
||||
import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
|
||||
import { ContentFolders } from '../Configuration/Common/ContentFolders';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IStepsToGetStartedProps {
|
||||
settings: Settings;
|
||||
@@ -116,42 +116,33 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
|
||||
{l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedFrameworkDescription)}
|
||||
</div>
|
||||
|
||||
<Menu as="div" className="relative inline-block text-left mt-4">
|
||||
<div>
|
||||
<Menu.Button className={`group flex justify-center p-2 rounded-md border text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}>
|
||||
{framework ? framework : l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedFrameworkSelect)}
|
||||
<ChevronDownIcon
|
||||
className={`flex-shrink-0 -mr-1 ml-1 h-5 w-5`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='mt-4 group flex justify-center p-2 rounded-md border text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
|
||||
<span className="">{framework ? framework : l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedFrameworkSelect)}</span>
|
||||
<ChevronDownIcon className="-mr-1 ml-1 w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<Menu.Items
|
||||
className={`w-40 origin-top-left absolute left-0 z-10 mt-2 rounded-md shadow-2xl ring-1 ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto bg-[var(--vscode-sideBar-background)] ring-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<div className="py-1">
|
||||
<DropdownMenuContent align='start'>
|
||||
<MenuItem
|
||||
title={l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedFrameworkSelectOther)}
|
||||
value={`other`}
|
||||
isCurrent={!framework}
|
||||
onClick={(value: string) => setFrameworkAndSendMessage(value)}
|
||||
/>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{frameworks.map((f) => (
|
||||
<MenuItem
|
||||
title={l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedFrameworkSelectOther)}
|
||||
value={`other`}
|
||||
isCurrent={!framework}
|
||||
key={f.name}
|
||||
title={f.name}
|
||||
value={f.name}
|
||||
isCurrent={f.name === framework}
|
||||
onClick={(value: string) => setFrameworkAndSendMessage(value)}
|
||||
/>
|
||||
|
||||
<hr className={`border-[var(--frontmatter-border)]`} />
|
||||
|
||||
{frameworks.map((f) => (
|
||||
<MenuItem
|
||||
key={f.name}
|
||||
title={f.name}
|
||||
value={f.name}
|
||||
isCurrent={f.name === framework}
|
||||
onClick={(value: string) => setFrameworkAndSendMessage(value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
),
|
||||
show: true,
|
||||
@@ -255,7 +246,7 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
|
||||
</div>
|
||||
),
|
||||
show: isGitRepo,
|
||||
status: settings.git.actions ? Status.Completed : Status.NotStarted
|
||||
status: settings.git?.actions ? Status.Completed : Status.NotStarted
|
||||
},
|
||||
{
|
||||
id: `welcome-import`,
|
||||
@@ -293,12 +284,12 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
|
||||
}, [settings.crntFramework, settings.framework]);
|
||||
|
||||
React.useEffect(() => {
|
||||
messageHandler.request<boolean>(GeneralCommands.toVSCode.gitIsRepo).then((result) => {
|
||||
messageHandler.request<boolean>(GeneralCommands.toVSCode.git.isRepo).then((result) => {
|
||||
setIsGitRepo(result);
|
||||
});
|
||||
|
||||
setIsGitEnabled(settings.git.actions);
|
||||
}, [settings.git.actions]);
|
||||
setIsGitEnabled(settings.git?.actions || false);
|
||||
}, [settings.git?.actions]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchTemplates = async () => {
|
||||
|
||||
@@ -82,13 +82,13 @@ export const TaxonomyActions: React.FunctionComponent<ITaxonomyActionsProps> = (
|
||||
)}
|
||||
|
||||
<LinkButton
|
||||
title={`Tag content`}
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewButtonTagTitle)}
|
||||
onClick={onTagging}>
|
||||
<div className='relative'>
|
||||
<TagIcon className={`w-4 h-4`} aria-hidden={true} />
|
||||
<PlusCircleIcon className={`w-3 h-3 absolute left-[-3px] bottom-[-4px] border-1 bg-[var(--vscode-editor-background)] rounded-full`} aria-hidden={true} />
|
||||
</div>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardTaxonomyViewButtonTagTitle)}</span>
|
||||
</LinkButton>
|
||||
|
||||
<LinkButton
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
FilterValuesAtom,
|
||||
FiltersAtom,
|
||||
FolderSelector,
|
||||
LocaleAtom,
|
||||
LocalesAtom,
|
||||
SearchSelector,
|
||||
SettingsSelector,
|
||||
SortingAtom,
|
||||
@@ -22,20 +24,25 @@ import { parseWinPath } from '../../helpers/parseWinPath';
|
||||
import { sortPages } from '../../utils/sortPages';
|
||||
import { ExtensionState } from '../../constants';
|
||||
import { SortingOption } from '../models';
|
||||
import { I18nConfig } from '../../models';
|
||||
import { usePrevious } from '../../panelWebView/hooks/usePrevious';
|
||||
|
||||
export default function usePages(pages: Page[]) {
|
||||
const [pageItems, setPageItems] = useRecoilState(AllPagesAtom);
|
||||
const [sortedPages, setSortedPages] = useState<Page[]>([]);
|
||||
const [pageItems, setPageItems] = useRecoilState(AllPagesAtom);
|
||||
const [sorting, setSorting] = useRecoilState(SortingAtom);
|
||||
const [tabInfo, setTabInfo] = useRecoilState(TabInfoAtom);
|
||||
const [locales, setLocales] = useRecoilState(LocalesAtom);
|
||||
const [, setFilterValues] = useRecoilState(FilterValuesAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const tab = useRecoilValue(TabSelector);
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
const search = useRecoilValue(SearchSelector);
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const locale = useRecoilValue(LocaleAtom);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
const filters = useRecoilValue(FiltersAtom);
|
||||
const tabPrevious = usePrevious(tab);
|
||||
|
||||
/**
|
||||
* Process all the pages by applying the sorting, filtering and searching.
|
||||
@@ -90,6 +97,11 @@ export default function usePages(pages: Page[]) {
|
||||
);
|
||||
}
|
||||
|
||||
// If filtered by locale
|
||||
if (locale) {
|
||||
pagesSorted = pagesSorted.filter((page) => page.fmLocale && page.fmLocale.locale === locale);
|
||||
}
|
||||
|
||||
const filterNames = Object.keys(filters);
|
||||
if (filterNames.length > 0) {
|
||||
for (const filter of filterNames) {
|
||||
@@ -102,7 +114,7 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
setSortedPages(pagesSorted);
|
||||
},
|
||||
[settings, tab, folder, search, tag, category, sorting, tabInfo, filters]
|
||||
[settings, tab, folder, search, tag, category, locale, sorting, tabInfo, filters]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -114,6 +126,24 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
let crntPages: Page[] = Object.assign([], pages);
|
||||
|
||||
// Update the translations of pages
|
||||
crntPages = crntPages.map((page) => {
|
||||
if (page.fmTranslations) {
|
||||
const translations = Object.assign({}, page.fmTranslations);
|
||||
|
||||
for (const [key, value] of Object.entries(translations)) {
|
||||
const translatedPage = crntPages.find((p) => parseWinPath(p.fmFilePath).toLowerCase() === parseWinPath(value.path).toLowerCase());
|
||||
if (!translatedPage) {
|
||||
delete translations[key];
|
||||
}
|
||||
}
|
||||
|
||||
return { ...page, fmTranslations: translations };
|
||||
}
|
||||
|
||||
return page;
|
||||
});
|
||||
|
||||
// Process the tab data
|
||||
const draftTypes = Object.assign({}, tabInfo);
|
||||
draftTypes[Tab.All] = crntPages.length;
|
||||
@@ -190,10 +220,21 @@ export default function usePages(pages: Page[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (tabPrevious !== tab || !locales || locales.length === 0) {
|
||||
// Store the locale information
|
||||
const config: I18nConfig[] = [];
|
||||
crntPages.forEach((page) => {
|
||||
if (page.fmLocale && !config.some(locale => locale.locale === page.fmLocale?.locale)) {
|
||||
config.push(page.fmLocale);
|
||||
}
|
||||
});
|
||||
setLocales(config);
|
||||
}
|
||||
|
||||
// Set the pages
|
||||
setPageItems(crntPages);
|
||||
},
|
||||
[tab, tabInfo, settings, filters]
|
||||
[tab, tabInfo, settings, filters, locales, tabPrevious]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -235,7 +276,7 @@ export default function usePages(pages: Page[]) {
|
||||
} else {
|
||||
startPageProcessing();
|
||||
}
|
||||
}, [settings?.draftField, pages, sorting, search, tag, category, filters, folder]);
|
||||
}, [settings?.draftField, pages, sorting, search, tag, category, locale, filters, folder]);
|
||||
|
||||
useEffect(() => {
|
||||
processByTab(sortedPages);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { I18nConfig } from '../../models';
|
||||
|
||||
export interface Page {
|
||||
// Properties for caching
|
||||
fmCachePath: string;
|
||||
@@ -19,6 +21,16 @@ export interface Page {
|
||||
fmContentType: string;
|
||||
fmDateFormat: string | undefined;
|
||||
|
||||
// i18n fields
|
||||
fmDefaultLocale?: boolean;
|
||||
fmLocale?: I18nConfig;
|
||||
fmTranslations?: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
}
|
||||
};
|
||||
|
||||
title: string;
|
||||
slug: string;
|
||||
date: string | Date;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DraftField,
|
||||
Framework,
|
||||
GitSettings,
|
||||
MediaContentType,
|
||||
Project,
|
||||
Snippets,
|
||||
SortingSetting
|
||||
@@ -19,7 +20,7 @@ import { DataFile } from '../../models/DataFile';
|
||||
export interface Settings {
|
||||
projects: Project[];
|
||||
project: Project;
|
||||
git: GitSettings;
|
||||
git: GitSettings | undefined;
|
||||
beta: boolean;
|
||||
initialized: boolean;
|
||||
wsFolder: string;
|
||||
@@ -47,6 +48,11 @@ export interface Settings {
|
||||
snippetsWrapper: boolean;
|
||||
date: { format: string };
|
||||
lastUpdated: number;
|
||||
media: MediaDashboardSettings;
|
||||
}
|
||||
|
||||
export interface MediaDashboardSettings {
|
||||
contentTypes: MediaContentType[];
|
||||
}
|
||||
|
||||
export interface DashboardState {
|
||||
|
||||
8
src/dashboardWebView/state/atom/LocaleAtom.ts
Normal file
8
src/dashboardWebView/state/atom/LocaleAtom.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const DEFAULT_LOCALE_STATE = '';
|
||||
|
||||
export const LocaleAtom = atom<string | null>({
|
||||
key: 'LocaleAtom',
|
||||
default: DEFAULT_LOCALE_STATE
|
||||
});
|
||||
7
src/dashboardWebView/state/atom/LocalesAtom.ts
Normal file
7
src/dashboardWebView/state/atom/LocalesAtom.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
import { I18nConfig } from '../../../models';
|
||||
|
||||
export const LocalesAtom = atom<I18nConfig[] | undefined>({
|
||||
key: 'LocalesAtom',
|
||||
default: undefined
|
||||
});
|
||||
@@ -9,6 +9,8 @@ export * from './FolderAtom';
|
||||
export * from './GroupingAtom';
|
||||
export * from './LightboxAtom';
|
||||
export * from './LoadingAtom';
|
||||
export * from './LocaleAtom';
|
||||
export * from './LocalesAtom';
|
||||
export * from './MediaFoldersAtom';
|
||||
export * from './MediaTotalAtom';
|
||||
export * from './ModeAtom';
|
||||
|
||||
20
src/dashboardWebView/utils/getRelPath.ts
Normal file
20
src/dashboardWebView/utils/getRelPath.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { parseWinPath } from '../../helpers/parseWinPath';
|
||||
|
||||
export const getRelPath = (path: string, staticFolder?: string, wsFolder?: string) => {
|
||||
let relPath: string | undefined = '';
|
||||
if (wsFolder && path) {
|
||||
const wsFolderParsed = parseWinPath(wsFolder);
|
||||
const mediaParsed = parseWinPath(path);
|
||||
|
||||
relPath = mediaParsed.split(wsFolderParsed).pop();
|
||||
|
||||
// If the static folder is the root, we can just return the relative path
|
||||
if (staticFolder === '/') {
|
||||
return relPath;
|
||||
} else if (staticFolder && relPath) {
|
||||
const staticFolderParsed = parseWinPath(staticFolder);
|
||||
relPath = relPath.split(staticFolderParsed).pop();
|
||||
}
|
||||
}
|
||||
return relPath;
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './getRelPath';
|
||||
export * from './preserveColor';
|
||||
export * from './updateCssVariables';
|
||||
|
||||
@@ -13,11 +13,10 @@ import {
|
||||
} from './helpers';
|
||||
import ContentProvider from './providers/ContentProvider';
|
||||
import { PagesListener } from './listeners/dashboard';
|
||||
import { NavigationType } from './dashboardWebView/models';
|
||||
import { ModeSwitch } from './services/ModeSwitch';
|
||||
import { PagesParser } from './services/PagesParser';
|
||||
import { ContentType, Telemetry, Extension } from './helpers';
|
||||
import { TaxonomyType, DashboardData } from './models';
|
||||
import { TaxonomyType } from './models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import {
|
||||
Backers,
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
} from './commands';
|
||||
import { join } from 'path';
|
||||
import { Terminal } from './services';
|
||||
import { i18n } from './commands/i18n';
|
||||
|
||||
let pageUpdateDebouncer: { (fnc: any, time: number): void };
|
||||
let editDebounce: { (fnc: any, time: number): void };
|
||||
@@ -87,51 +87,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Pages dashboard
|
||||
Dashboard.init();
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openContentDashboard);
|
||||
if (!data) {
|
||||
Dashboard.open({ type: NavigationType.Contents });
|
||||
} else {
|
||||
Dashboard.open(data);
|
||||
}
|
||||
})
|
||||
);
|
||||
Dashboard.registerCommands();
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboardMedia, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openMediaDashboard);
|
||||
Dashboard.open({ type: NavigationType.Media });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboardSnippets, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openSnippetsDashboard);
|
||||
Dashboard.open({ type: NavigationType.Snippets });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboardData, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openDataDashboard);
|
||||
Dashboard.open({ type: NavigationType.Data });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboardTaxonomy, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openTaxonomyDashboard);
|
||||
Dashboard.open({ type: NavigationType.Taxonomy });
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.closeDashboard);
|
||||
Dashboard.close();
|
||||
})
|
||||
);
|
||||
i18n.register();
|
||||
|
||||
if (!extension.getVersion().usedVersion) {
|
||||
vscode.commands.executeCommand(COMMAND_NAME.dashboard);
|
||||
|
||||
@@ -23,11 +23,21 @@ import {
|
||||
} from '../constants';
|
||||
import { DumpOptions } from 'js-yaml';
|
||||
import { FrontMatterParser, ParsedFrontMatter } from '../parsers';
|
||||
import { ContentType, Extension, Logger, Settings, SlugHelper, isValidFile, parseWinPath } from '.';
|
||||
import {
|
||||
ContentType,
|
||||
Extension,
|
||||
Logger,
|
||||
Settings,
|
||||
SlugHelper,
|
||||
isValidFile,
|
||||
parseWinPath,
|
||||
processArticlePlaceholdersFromPath,
|
||||
processTimePlaceholders
|
||||
} from '.';
|
||||
import { format, parse } from 'date-fns';
|
||||
import { Notifications } from './Notifications';
|
||||
import { Article } from '../commands';
|
||||
import { join } from 'path';
|
||||
import { join, parse as parseFile } from 'path';
|
||||
import { EditorHelper } from '@estruyf/vscode';
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { ContentType as IContentType } from '../models';
|
||||
@@ -37,10 +47,9 @@ import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
import { Link, Parent } from 'mdast-util-from-markdown/lib';
|
||||
import { Content } from 'mdast';
|
||||
import { processKnownPlaceholders } from './PlaceholderHelper';
|
||||
import { CustomScript } from './CustomScript';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { existsAsync, readFileAsync } from '../utils';
|
||||
import { existsAsync } from '../utils';
|
||||
import { mkdirAsync } from '../utils/mkdirAsync';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
@@ -57,6 +66,19 @@ export class ArticleHelper {
|
||||
return ArticleHelper.getFrontMatterFromDocument(editor.document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the front matter from the current active document.
|
||||
* @returns The front matter object if found, otherwise undefined.
|
||||
*/
|
||||
public static getFrontMatterFromCurrentDocument() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
return ArticleHelper.getFrontMatterFromDocument(editor.document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of the specified document
|
||||
*
|
||||
@@ -101,8 +123,14 @@ export class ArticleHelper {
|
||||
* Retrieve the file's front matter by its path
|
||||
* @param filePath
|
||||
*/
|
||||
public static async getFrontMatterByPath(filePath: string) {
|
||||
const file = await readFileAsync(filePath, { encoding: 'utf-8' });
|
||||
public static async getFrontMatterByPath(
|
||||
filePath: string
|
||||
): Promise<ParsedFrontMatter | undefined> {
|
||||
const file = await ArticleHelper.getContents(filePath);
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.parseFile(file, filePath);
|
||||
if (!article) {
|
||||
return undefined;
|
||||
@@ -114,6 +142,20 @@ export class ArticleHelper {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of a file asynchronously.
|
||||
* @param filePath - The path of the file to read.
|
||||
* @returns A promise that resolves to the contents of the file, or undefined if the file does not exist.
|
||||
*/
|
||||
public static async getContents(filePath: string): Promise<string | undefined> {
|
||||
const file = await workspace.fs.readFile(Uri.file(parseWinPath(filePath)));
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the new information in the file
|
||||
*
|
||||
@@ -260,6 +302,35 @@ export class ArticleHelper {
|
||||
return isSupportedLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given file path represents a page bundle.
|
||||
*
|
||||
* @param filePath - The path of the file to check.
|
||||
* @returns A boolean indicating whether the file is a page bundle or not.
|
||||
*/
|
||||
public static async isPageBundle(filePath: string) {
|
||||
let article = await ArticleHelper.getFrontMatterByPath(filePath);
|
||||
if (!article) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
return !!contentType.pageBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the page folder from the given bundle file path.
|
||||
*
|
||||
* @param filePath - The file path of the bundle.
|
||||
* @returns The page folder path.
|
||||
*/
|
||||
public static getPageFolderFromBundlePath(filePath: string) {
|
||||
// Remove the last folder from the dir
|
||||
const dir = parseFile(filePath).dir;
|
||||
const lastSlash = dir.lastIndexOf('/');
|
||||
return dir.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date from front matter
|
||||
*/
|
||||
@@ -348,21 +419,9 @@ export class ArticleHelper {
|
||||
if (article.data.type) {
|
||||
contentType = contentTypes.find((ct) => ct.name === article.data.type);
|
||||
} else if (!contentType && article.path) {
|
||||
// Get the content type by the folder name
|
||||
let folders = Folders.get();
|
||||
let parsedPath = parseWinPath(article.path);
|
||||
let pageFolderMatches = folders.filter(
|
||||
(folder) => parsedPath && folder.path && parsedPath.includes(folder.path)
|
||||
);
|
||||
|
||||
// Sort by longest path
|
||||
pageFolderMatches = pageFolderMatches.sort((a, b) => b.path.length - a.path.length);
|
||||
if (
|
||||
pageFolderMatches.length > 0 &&
|
||||
pageFolderMatches[0].contentTypes &&
|
||||
pageFolderMatches[0].contentTypes.length === 1
|
||||
) {
|
||||
const contentTypeName = pageFolderMatches[0].contentTypes[0];
|
||||
const pageFolder = Folders.getPageFolderByFilePath(article.path);
|
||||
if (pageFolder && pageFolder.contentTypes?.length === 1) {
|
||||
const contentTypeName = pageFolder.contentTypes[0];
|
||||
contentType = contentTypes.find((ct) => ct.name === contentTypeName);
|
||||
}
|
||||
}
|
||||
@@ -524,7 +583,12 @@ export class ArticleHelper {
|
||||
* @param title
|
||||
* @returns
|
||||
*/
|
||||
public static async updatePlaceholders(data: any, title: string, filePath: string) {
|
||||
public static async updatePlaceholders(
|
||||
data: any,
|
||||
title: string,
|
||||
filePath: string,
|
||||
slugTemplate?: string
|
||||
) {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
const fmData = Object.assign({}, data);
|
||||
|
||||
@@ -536,10 +600,11 @@ export class ArticleHelper {
|
||||
}
|
||||
|
||||
if (fieldName === 'slug' && (fieldValue === null || fieldValue === '')) {
|
||||
fmData[fieldName] = SlugHelper.createSlug(title);
|
||||
fmData[fieldName] = SlugHelper.createSlug(title, fmData, slugTemplate);
|
||||
}
|
||||
|
||||
fmData[fieldName] = processKnownPlaceholders(fmData[fieldName], title, dateFormat);
|
||||
fmData[fieldName] = await processArticlePlaceholdersFromPath(fmData[fieldName], filePath);
|
||||
fmData[fieldName] = processTimePlaceholders(fmData[fieldName], dateFormat);
|
||||
fmData[fieldName] = await this.processCustomPlaceholders(fmData[fieldName], title, filePath);
|
||||
}
|
||||
|
||||
@@ -597,7 +662,11 @@ export class ArticleHelper {
|
||||
}
|
||||
|
||||
const regex = new RegExp(`{{${placeholder.id}}}`, 'g');
|
||||
const updatedValue = processKnownPlaceholders(placeHolderValue, title, dateFormat);
|
||||
let updatedValue = filePath
|
||||
? await processArticlePlaceholdersFromPath(placeHolderValue, filePath)
|
||||
: placeHolderValue;
|
||||
|
||||
updatedValue = processTimePlaceholders(updatedValue, dateFormat);
|
||||
|
||||
if (value === `{{${placeholder.id}}}`) {
|
||||
value = updatedValue;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ModeListener } from './../listeners/general/ModeListener';
|
||||
import { PagesListener } from './../listeners/dashboard';
|
||||
import { ArticleHelper, CustomScript, Logger, Settings } from '.';
|
||||
import {
|
||||
ArticleHelper,
|
||||
CustomScript,
|
||||
Logger,
|
||||
Settings,
|
||||
processArticlePlaceholdersFromData,
|
||||
processTimePlaceholders
|
||||
} from '.';
|
||||
import {
|
||||
DefaultFieldValues,
|
||||
EXTENSION_NAME,
|
||||
@@ -26,7 +33,6 @@ import { Questions } from './Questions';
|
||||
import { Notifications } from './Notifications';
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from '../constants/ContentType';
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { processKnownPlaceholders } from './PlaceholderHelper';
|
||||
import { basename } from 'path';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { encodeEmoji, existsAsync, fieldWhenClause, writeFileAsync } from '../utils';
|
||||
@@ -299,17 +305,17 @@ export class ContentType {
|
||||
|
||||
Telemetry.send(TelemetryEvent.addMissingFields);
|
||||
|
||||
const content = ArticleHelper.getCurrent();
|
||||
const article = ArticleHelper.getCurrent();
|
||||
|
||||
if (!content || !content.data) {
|
||||
if (!article || !article.data) {
|
||||
Notifications.warning(
|
||||
l10n.t(LocalizationKey.helpersContentTypeAddMissingFieldsNoFrontMatterWarning)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(content);
|
||||
const updatedFields = ContentType.generateFields(content.data, contentType.fields);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
const updatedFields = ContentType.generateFields(article.data, contentType.fields);
|
||||
|
||||
const contentTypes = ContentType.getAll() || [];
|
||||
const index = contentTypes.findIndex((ct) => ct.name === contentType.name);
|
||||
@@ -927,7 +933,8 @@ export class ContentType {
|
||||
titleValue,
|
||||
templateData?.data || {},
|
||||
newFilePath,
|
||||
!!contentType.clearEmpty
|
||||
!!contentType.clearEmpty,
|
||||
contentType
|
||||
);
|
||||
|
||||
const article: ParsedFrontMatter = {
|
||||
@@ -982,6 +989,7 @@ export class ContentType {
|
||||
data: any,
|
||||
filePath: string,
|
||||
clearEmpty: boolean,
|
||||
contentType: IContentType,
|
||||
isRoot: boolean = true
|
||||
): Promise<any> {
|
||||
if (obj.fields) {
|
||||
@@ -995,9 +1003,13 @@ export class ContentType {
|
||||
|
||||
if (field.name === 'title') {
|
||||
if (field.default) {
|
||||
data[field.name] = processKnownPlaceholders(
|
||||
field.default,
|
||||
titleValue,
|
||||
data[field.name] = processArticlePlaceholdersFromData(
|
||||
field.default as string,
|
||||
data,
|
||||
contentType
|
||||
);
|
||||
data[field.name] = processTimePlaceholders(
|
||||
data[field.name],
|
||||
field.dateFormat || dateFormat
|
||||
);
|
||||
data[field.name] = await ArticleHelper.processCustomPlaceholders(
|
||||
@@ -1018,6 +1030,7 @@ export class ContentType {
|
||||
{},
|
||||
filePath,
|
||||
clearEmpty,
|
||||
contentType,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -1028,16 +1041,30 @@ export class ContentType {
|
||||
const defaultValue = field.default;
|
||||
|
||||
if (typeof defaultValue === 'string') {
|
||||
data[field.name] = processKnownPlaceholders(
|
||||
data[field.name] = await ContentType.processFieldPlaceholders(
|
||||
defaultValue,
|
||||
titleValue,
|
||||
field.dateFormat || dateFormat
|
||||
);
|
||||
data[field.name] = await ArticleHelper.processCustomPlaceholders(
|
||||
data[field.name],
|
||||
data,
|
||||
contentType,
|
||||
field.dateFormat || dateFormat,
|
||||
titleValue,
|
||||
filePath
|
||||
);
|
||||
} else if (defaultValue && Array.isArray(defaultValue)) {
|
||||
let defaultValues = [];
|
||||
for (let value of defaultValue as string[]) {
|
||||
if (typeof value === 'string') {
|
||||
value = await ContentType.processFieldPlaceholders(
|
||||
value,
|
||||
data,
|
||||
contentType,
|
||||
field.dateFormat || dateFormat,
|
||||
titleValue,
|
||||
filePath
|
||||
);
|
||||
}
|
||||
defaultValues.push(value);
|
||||
}
|
||||
data[field.name] = defaultValues;
|
||||
} else if (typeof defaultValue !== 'undefined') {
|
||||
data[field.name] = defaultValue;
|
||||
} else {
|
||||
@@ -1082,6 +1109,7 @@ export class ContentType {
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
case 'slug':
|
||||
case 'image':
|
||||
case 'file':
|
||||
default:
|
||||
@@ -1100,6 +1128,32 @@ export class ContentType {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the field placeholders in the given value.
|
||||
*
|
||||
* @param defaultValue - The default value for the field.
|
||||
* @param data - The data object containing the field values.
|
||||
* @param contentType - The content type object.
|
||||
* @param dateFormat - The date format string.
|
||||
* @param title - The title string.
|
||||
* @param filePath - The file path string.
|
||||
* @returns The processed value with field placeholders replaced.
|
||||
*/
|
||||
private static async processFieldPlaceholders(
|
||||
defaultValue: string,
|
||||
data: any,
|
||||
contentType: IContentType,
|
||||
dateFormat: string,
|
||||
title: string,
|
||||
filePath: string
|
||||
) {
|
||||
let value = processArticlePlaceholdersFromData(defaultValue, data, contentType);
|
||||
value = processTimePlaceholders(value, dateFormat);
|
||||
value = await ArticleHelper.processCustomPlaceholders(value, title, filePath);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the content type feature is enabled
|
||||
* @returns
|
||||
|
||||
@@ -30,14 +30,23 @@ import {
|
||||
SETTING_DASHBOARD_CONTENT_CARD_TITLE,
|
||||
SETTING_DASHBOARD_CONTENT_CARD_STATE,
|
||||
SETTING_DASHBOARD_CONTENT_CARD_DESCRIPTION,
|
||||
SETTING_WEBSITE_URL
|
||||
SETTING_WEBSITE_URL,
|
||||
SETTING_MEDIA_CONTENTTYPES
|
||||
} from '../constants';
|
||||
import {
|
||||
DashboardViewType,
|
||||
SortingOption,
|
||||
Settings as ISettings
|
||||
} from '../dashboardWebView/models';
|
||||
import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from '../models';
|
||||
import {
|
||||
CustomScript,
|
||||
DEFAULT_MEDIA_CONTENT_TYPE,
|
||||
DraftField,
|
||||
MediaContentType,
|
||||
Snippets,
|
||||
SortingSetting,
|
||||
TaxonomyType
|
||||
} from '../models';
|
||||
import { DataFile } from '../models/DataFile';
|
||||
import { DataFolder } from '../models/DataFolder';
|
||||
import { DataType } from '../models/DataType';
|
||||
@@ -79,16 +88,12 @@ export class DashboardSettings {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = await Project.isInitialized();
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
const pagination = Settings.get<boolean | number>(SETTING_DASHBOARD_CONTENT_PAGINATION);
|
||||
|
||||
const settings = {
|
||||
projects: Settings.getProjects(),
|
||||
project: Settings.getProject(),
|
||||
git: {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false
|
||||
},
|
||||
git: await GitListener.getSettings(),
|
||||
beta: ext.isBetaVersion(),
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: Folders.getStaticFolderRelativePath(),
|
||||
@@ -152,6 +157,11 @@ export class DashboardSettings {
|
||||
snippetsWrapper: Settings.get<boolean>(SETTING_SNIPPETS_WRAPPER),
|
||||
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global'),
|
||||
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL),
|
||||
media: {
|
||||
contentTypes: Settings.get<MediaContentType[]>(SETTING_MEDIA_CONTENTTYPES) || [
|
||||
DEFAULT_MEDIA_CONTENT_TYPE
|
||||
]
|
||||
},
|
||||
lastUpdated: new Date().getTime()
|
||||
} as ISettings;
|
||||
|
||||
|
||||
@@ -424,6 +424,14 @@ export class Extension {
|
||||
}
|
||||
}
|
||||
|
||||
public async getSecret(key: string): Promise<string | undefined> {
|
||||
return this.ctx.secrets.get(key);
|
||||
}
|
||||
|
||||
public async setSecret(key: string, value: string): Promise<void> {
|
||||
return this.ctx.secrets.store(key, value);
|
||||
}
|
||||
|
||||
public isBetaVersion() {
|
||||
return basename(this.ctx.globalStorageUri.fsPath) === EXTENSION_BETA_ID;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,60 @@ export class FrameworkDetector {
|
||||
return parseWinPath(relAssetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path by combining the relative path and the file path.
|
||||
* If a static folder is configured, it will be taken into account.
|
||||
* @param relAssetPath The relative path.
|
||||
* @param filePath The file path.
|
||||
* @returns The absolute path.
|
||||
*/
|
||||
public static getAbsPathByFile(relAssetPath: string, filePath: string): string {
|
||||
const staticFolderValue = Settings.get<string | StaticFolder>(SETTING_CONTENT_STATIC_FOLDER);
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
|
||||
if (
|
||||
staticFolderValue &&
|
||||
staticFolder &&
|
||||
typeof staticFolderValue !== 'string' &&
|
||||
staticFolderValue.relative
|
||||
) {
|
||||
const fileDir = dirname(filePath);
|
||||
return parseWinPath(join(fileDir, relAssetPath));
|
||||
}
|
||||
|
||||
return relAssetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path of an asset file based on the provided absolute asset path and file path.
|
||||
* If the static folder setting is configured and the static folder is available, the relative path is calculated based on the file and asset directories.
|
||||
* Otherwise, the absolute asset path is returned as is.
|
||||
*
|
||||
* @param absAssetPath The absolute path of the asset file.
|
||||
* @param fileDir The path of the directory
|
||||
* @returns The relative path of the asset file.
|
||||
*/
|
||||
public static getRelPathByFileDir(absAssetPath: string, fileDir: string): string {
|
||||
const staticFolderValue = Settings.get<string | StaticFolder>(SETTING_CONTENT_STATIC_FOLDER);
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
|
||||
if (
|
||||
staticFolderValue &&
|
||||
staticFolder &&
|
||||
typeof staticFolderValue !== 'string' &&
|
||||
staticFolderValue.relative
|
||||
) {
|
||||
const assetDir = dirname(absAssetPath);
|
||||
const fileName = parse(absAssetPath);
|
||||
|
||||
let relAssetPath = relative(fileDir, assetDir);
|
||||
relAssetPath = join(relAssetPath, `${fileName.name}${fileName.ext}`);
|
||||
return parseWinPath(relAssetPath);
|
||||
}
|
||||
|
||||
return absAssetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the default settings for Hexo
|
||||
*/
|
||||
|
||||
@@ -161,7 +161,9 @@ export class MediaHelpers {
|
||||
dimensions:
|
||||
mimeType && mimeType.startsWith('image/') ? imageSize(file.fsPath) : undefined,
|
||||
mimeType: lookup(file.fsPath) || '',
|
||||
...metadata
|
||||
metadata: {
|
||||
...metadata
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return { ...file };
|
||||
@@ -478,15 +480,11 @@ export class MediaHelpers {
|
||||
const {
|
||||
file,
|
||||
filename,
|
||||
page,
|
||||
folder,
|
||||
...metadata
|
||||
metadata
|
||||
}: {
|
||||
file: string;
|
||||
filename: string;
|
||||
page: number;
|
||||
folder: string | null;
|
||||
metadata: any;
|
||||
metadata: { [fieldName: string]: string | string[] | Date | number | undefined };
|
||||
} = data;
|
||||
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
@@ -522,7 +520,8 @@ export class MediaHelpers {
|
||||
filename: basename(file.fsPath),
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.getWebview()?.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
stats: undefined,
|
||||
metadata: {}
|
||||
} as MediaInfo)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
SETTING_SLUG_UPDATE_FILE_NAME,
|
||||
SETTING_TAXONOMY_CUSTOM,
|
||||
SETTING_TAXONOMY_FIELD_GROUPS,
|
||||
SETTING_GIT_ENABLED,
|
||||
SETTING_SEO_TITLE_FIELD
|
||||
} from '../constants';
|
||||
import { GitListener } from '../listeners/general';
|
||||
@@ -49,14 +48,9 @@ import { Folders } from '../commands';
|
||||
|
||||
export class PanelSettings {
|
||||
public static async get(): Promise<IPanelSettings> {
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
|
||||
return {
|
||||
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
|
||||
git: {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false
|
||||
},
|
||||
git: await GitListener.getSettings(),
|
||||
seo: {
|
||||
title: (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1,
|
||||
slug: (Settings.get(SETTING_SEO_SLUG_LENGTH) as number) || -1,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import {
|
||||
EXTENSION_NAME,
|
||||
SETTING_CONFIG_DYNAMIC_FILE_PATH,
|
||||
SETTING_PROJECTS
|
||||
} from './../constants/settings';
|
||||
import { parseWinPath } from './parseWinPath';
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { Notifications } from './Notifications';
|
||||
import { commands, Uri, workspace, window } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
commands,
|
||||
Uri,
|
||||
workspace,
|
||||
window,
|
||||
WorkspaceConfiguration,
|
||||
FileSystemWatcher,
|
||||
Disposable,
|
||||
ProgressLocation
|
||||
} from 'vscode';
|
||||
import { ContentType, CustomTaxonomy, Project } from '../models';
|
||||
import {
|
||||
SETTING_TAXONOMY_TAGS,
|
||||
SETTING_TAXONOMY_CATEGORIES,
|
||||
EXTENSION_NAME,
|
||||
CONFIG_KEY,
|
||||
CONTEXT,
|
||||
ExtensionState,
|
||||
@@ -35,8 +37,13 @@ import {
|
||||
SETTING_GLOBAL_NOTIFICATIONS,
|
||||
SETTING_GLOBAL_NOTIFICATIONS_DISABLED,
|
||||
SETTING_MEDIA_SUPPORTED_MIMETYPES,
|
||||
SETTING_MEDIA_CONTENTTYPES,
|
||||
SETTING_COMMA_SEPARATED_FIELDS,
|
||||
SETTING_REMOVE_QUOTES
|
||||
SETTING_REMOVE_QUOTES,
|
||||
SETTING_CONFIG_DYNAMIC_FILE_PATH,
|
||||
SETTING_PROJECTS,
|
||||
SETTING_TAXONOMY_TAGS,
|
||||
SETTING_TAXONOMY_CATEGORIES
|
||||
} from '../constants';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { join, basename, dirname, parse } from 'path';
|
||||
@@ -59,13 +66,13 @@ export class Settings {
|
||||
public static globalConfigFolder = '.frontmatter/config';
|
||||
public static globalConfigPath: string | undefined = undefined;
|
||||
public static globalConfig: any;
|
||||
private static config: vscode.WorkspaceConfiguration;
|
||||
private static config: WorkspaceConfiguration;
|
||||
private static isInitialized: boolean = false;
|
||||
private static listeners: { id: string; callback: (global?: any) => void }[] = [];
|
||||
private static fileCreationWatcher: vscode.FileSystemWatcher | undefined;
|
||||
private static fileChangeWatcher: vscode.FileSystemWatcher | undefined;
|
||||
private static fileSaveListener: vscode.Disposable;
|
||||
private static fileDeleteListener: vscode.Disposable;
|
||||
private static fileCreationWatcher: FileSystemWatcher | undefined;
|
||||
private static fileChangeWatcher: FileSystemWatcher | undefined;
|
||||
private static fileSaveListener: Disposable;
|
||||
private static fileDeleteListener: Disposable;
|
||||
private static readConfigPromise: Promise<void> | undefined = undefined;
|
||||
private static project: Project | undefined = undefined;
|
||||
private static configDebouncer = debounceCallback();
|
||||
@@ -109,10 +116,10 @@ export class Settings {
|
||||
commands.registerCommand(COMMAND_NAME.settingsRefresh, Settings.refreshConfig);
|
||||
}
|
||||
|
||||
Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY);
|
||||
Settings.config = workspace.getConfiguration(CONFIG_KEY);
|
||||
|
||||
Settings.attachListener('settings-init', async () => {
|
||||
Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY);
|
||||
Settings.config = workspace.getConfiguration(CONFIG_KEY);
|
||||
});
|
||||
|
||||
Settings.onConfigChange();
|
||||
@@ -674,7 +681,7 @@ export class Settings {
|
||||
try {
|
||||
await window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
location: ProgressLocation.Notification,
|
||||
title: l10n.t(
|
||||
LocalizationKey.helpersSettingsHelperReadConfigProgressTitle,
|
||||
EXTENSION_NAME
|
||||
@@ -866,6 +873,10 @@ export class Settings {
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CONTENT_TYPES, 'name', configJson);
|
||||
}
|
||||
// Media Content types
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_MEDIA_CONTENTTYPES)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_MEDIA_CONTENTTYPES, 'name', configJson);
|
||||
}
|
||||
// Data files
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FILES)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_DATA_FILES, 'id', configJson);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { stopWords, charMap } from '../constants';
|
||||
import { Settings } from '.';
|
||||
import { stopWords, charMap, SETTING_DATE_FORMAT, SETTING_SLUG_TEMPLATE } from '../constants';
|
||||
import { processTimePlaceholders, processFmPlaceholders } from '.';
|
||||
|
||||
export class SlugHelper {
|
||||
/**
|
||||
@@ -6,13 +8,41 @@ export class SlugHelper {
|
||||
*
|
||||
* @param articleTitle
|
||||
*/
|
||||
public static createSlug(articleTitle: string): string | null {
|
||||
public static createSlug(
|
||||
articleTitle: string,
|
||||
articleData: { [key: string]: any },
|
||||
slugTemplate?: string
|
||||
): string | null {
|
||||
if (!articleTitle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove punctuation from input string, and split it into words.
|
||||
let cleanTitle = this.removePunctuation(articleTitle).trim();
|
||||
if (!slugTemplate) {
|
||||
slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE) || undefined;
|
||||
}
|
||||
|
||||
if (slugTemplate) {
|
||||
if (slugTemplate.includes('{{title}}')) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
slugTemplate = slugTemplate.replace(regex, SlugHelper.slugify(articleTitle));
|
||||
}
|
||||
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
articleTitle = processTimePlaceholders(slugTemplate, dateFormat);
|
||||
articleTitle = processFmPlaceholders(articleTitle, articleData);
|
||||
return articleTitle;
|
||||
}
|
||||
|
||||
return SlugHelper.slugify(articleTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a title into a slug by removing punctuation, stop words, and replacing characters.
|
||||
* @param title - The title to be slugified.
|
||||
* @returns The slugified version of the title.
|
||||
*/
|
||||
public static slugify(title: string): string {
|
||||
let cleanTitle = this.removePunctuation(title).trim();
|
||||
if (cleanTitle) {
|
||||
cleanTitle = cleanTitle.toLowerCase();
|
||||
// Split into words
|
||||
@@ -23,8 +53,7 @@ export class SlugHelper {
|
||||
cleanTitle = this.replaceCharacters(cleanTitle);
|
||||
return cleanTitle;
|
||||
}
|
||||
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,6 @@ export * from './MediaHelpers';
|
||||
export * from './MediaLibrary';
|
||||
export * from './Notifications';
|
||||
export * from './PanelSettings';
|
||||
export * from './PlaceholderHelper';
|
||||
export * from './Questions';
|
||||
export * from './Sanitize';
|
||||
export * from './SeoHelper';
|
||||
@@ -32,5 +31,7 @@ export * from './getTaxonomyField';
|
||||
export * from './isValidFile';
|
||||
export * from './openFileInEditor';
|
||||
export * from './parseWinPath';
|
||||
export * from './processArticlePlaceholders';
|
||||
export * from './processFmPlaceholders';
|
||||
export * from './processPathPlaceholders';
|
||||
export * from './processTimePlaceholders';
|
||||
|
||||
53
src/helpers/processArticlePlaceholders.ts
Normal file
53
src/helpers/processArticlePlaceholders.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { ContentType } from '../models';
|
||||
import { ArticleHelper } from './ArticleHelper';
|
||||
import { SlugHelper } from './SlugHelper';
|
||||
|
||||
export const processArticlePlaceholdersFromData = (
|
||||
value: string,
|
||||
data: { [key: string]: any },
|
||||
contentType: ContentType
|
||||
): string => {
|
||||
if (value.includes('{{title}}') && data.title) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
value = value.replace(regex, data.title || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{slug}}')) {
|
||||
const regex = new RegExp('{{slug}}', 'g');
|
||||
value = value.replace(
|
||||
regex,
|
||||
SlugHelper.createSlug(data.title || '', data, contentType.slugTemplate) || ''
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const processArticlePlaceholdersFromPath = async (
|
||||
value: string,
|
||||
filePath: string
|
||||
): Promise<string> => {
|
||||
const article = await ArticleHelper.getFrontMatterByPath(filePath);
|
||||
if (!article) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.includes('{{title}}')) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
value = value.replace(regex, article.data.title || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{slug}}') && filePath) {
|
||||
const contentType = article ? ArticleHelper.getContentType(article) : undefined;
|
||||
if (contentType) {
|
||||
const regex = new RegExp('{{slug}}', 'g');
|
||||
value = value.replace(
|
||||
regex,
|
||||
SlugHelper.createSlug(article.data.title || '', article.data, contentType.slugTemplate) ||
|
||||
''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export const processFmPlaceholders = (value: string, fmData: any) => {
|
||||
export const processFmPlaceholders = (value: string, fmData: { [key: string]: any }) => {
|
||||
// Example: {{fm.date}} or {{fm.date | dateFormat 'DD.MM.YYYY'}}
|
||||
if (value && value.includes('{{fm.')) {
|
||||
const regex = /{{fm.[^}]*}}/g;
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
import { format } from 'date-fns';
|
||||
import { DateHelper } from './DateHelper';
|
||||
import { SlugHelper } from './SlugHelper';
|
||||
|
||||
/**
|
||||
* Replace the known placeholders
|
||||
* Replace the time placeholders
|
||||
* @param value
|
||||
* @param title
|
||||
* @returns
|
||||
*/
|
||||
export const processKnownPlaceholders = (
|
||||
value: string,
|
||||
title: string | undefined,
|
||||
dateFormat: string
|
||||
) => {
|
||||
export const processTimePlaceholders = (value: string, dateFormat?: string) => {
|
||||
if (value && typeof value === 'string') {
|
||||
if (value.includes('{{title}}')) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
value = value.replace(regex, title || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{slug}}')) {
|
||||
const regex = new RegExp('{{slug}}', 'g');
|
||||
value = value.replace(regex, SlugHelper.createSlug(title || '') || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{now}}')) {
|
||||
const regex = new RegExp('{{now}}', 'g');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Logger } from '../../helpers/Logger';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
@@ -20,7 +21,11 @@ export abstract class BaseListener {
|
||||
});
|
||||
}
|
||||
|
||||
public static sendRequest(command: DashboardCommand, requestId: string, payload: any) {
|
||||
public static sendRequest(
|
||||
command: DashboardCommand | DashboardMessage,
|
||||
requestId: string,
|
||||
payload: any
|
||||
) {
|
||||
Dashboard.postWebviewMessage({
|
||||
command,
|
||||
requestId,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Folders } from '../../commands/Folders';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
ExtensionState,
|
||||
GeneralCommands,
|
||||
SETTING_CONTENT_STATIC_FOLDER,
|
||||
SETTING_FRAMEWORK_ID,
|
||||
SETTING_PREVIEW_HOST
|
||||
@@ -61,9 +62,21 @@ export class SettingsListener extends BaseListener {
|
||||
case DashboardMessage.setSettings:
|
||||
this.setConfigSettings(msg);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.secrets.get:
|
||||
this.getSecretValue(msg.command, msg.payload, msg.requestId);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.secrets.set:
|
||||
this.setSecretValue(msg.command, msg.payload, msg.requestId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration settings based on the provided payload.
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The ID of the request.
|
||||
* @param payload - The payload containing the settings to retrieve.
|
||||
*/
|
||||
public static async getConfigSettings({ command, requestId, payload }: PostMessageData) {
|
||||
if (!command || !requestId || !payload) {
|
||||
return;
|
||||
@@ -96,6 +109,10 @@ export class SettingsListener extends BaseListener {
|
||||
this.sendRequest(command as any, requestId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the current project to the specified project.
|
||||
* @param project - The name of the project to switch to.
|
||||
*/
|
||||
public static async switchProject(project: string) {
|
||||
if (project) {
|
||||
this.sendMsg(DashboardCommand.loading, 'loading' as LoadingType);
|
||||
@@ -123,6 +140,50 @@ export class SettingsListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the secret value for a given command and value.
|
||||
* @param command - The command to retrieve the secret value for.
|
||||
* @param key - The key associated with the secret.
|
||||
* @param requestId - Optional. The ID of the request.
|
||||
* @returns A Promise that resolves to the secret value.
|
||||
*/
|
||||
private static async getSecretValue(command: string, key: string, requestId?: string) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = Extension.getInstance();
|
||||
const value = await extension.getSecret(key);
|
||||
|
||||
this.sendRequest(command as any, requestId, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secret value for a given key.
|
||||
* @param command - The command to execute.
|
||||
* @param key - The key for the secret value.
|
||||
* @param value - The secret value to set.
|
||||
* @param requestId - Optional. The request ID.
|
||||
*/
|
||||
private static async setSecretValue(
|
||||
command: string,
|
||||
{ key, value }: { key: string; value: string },
|
||||
requestId?: string
|
||||
) {
|
||||
if (!command || !requestId || !key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = Extension.getInstance();
|
||||
await extension.setSecret(key, value || '');
|
||||
|
||||
Notifications.info(
|
||||
l10n.t(LocalizationKey.listenersDashboardSettingsListenerSetSecretValueMessage)
|
||||
);
|
||||
|
||||
this.sendRequest(command as any, requestId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a setting from the dashboard
|
||||
* @param data
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { EditorHelper } from '@estruyf/vscode';
|
||||
import { window, Range, Position } from 'vscode';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { SETTING_CONTENT_SNIPPETS, TelemetryEvent } from '../../constants';
|
||||
import { SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, TelemetryEvent } from '../../constants';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Notifications, Settings, Telemetry } from '../../helpers';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Notifications,
|
||||
Settings,
|
||||
Telemetry,
|
||||
processArticlePlaceholdersFromPath,
|
||||
processTimePlaceholders
|
||||
} from '../../helpers';
|
||||
import { PostMessageData, Snippets } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { SettingsListener } from './SettingsListener';
|
||||
@@ -25,6 +32,9 @@ export class SnippetListener extends BaseListener {
|
||||
Telemetry.send(TelemetryEvent.insertContentSnippet);
|
||||
this.insertSnippet(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.updateSnippetPlaceholders:
|
||||
this.updateSnippetPlaceholders(msg.command, msg.payload, msg.requestId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,4 +134,25 @@ export class SnippetListener extends BaseListener {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async updateSnippetPlaceholders(
|
||||
command: DashboardMessage,
|
||||
data: { value: string; filePath: string },
|
||||
requestId?: string
|
||||
) {
|
||||
if (!data.value || !command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = data.value;
|
||||
|
||||
if (data.filePath) {
|
||||
value = await processArticlePlaceholdersFromPath(data.value, data.filePath);
|
||||
}
|
||||
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
value = processTimePlaceholders(value, dateFormat);
|
||||
|
||||
this.sendRequest(command, requestId, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Dashboard } from '../../commands/Dashboard';
|
||||
import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { ArticleHelper, Extension } from '../../helpers';
|
||||
import { Logger } from '../../helpers/Logger';
|
||||
import { commands, Uri, window } from 'vscode';
|
||||
import { commands, Uri, window, workspace } from 'vscode';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { Preview } from '../../commands';
|
||||
import { urlJoin } from 'url-join-ts';
|
||||
@@ -19,6 +19,12 @@ export abstract class BaseListener {
|
||||
case GeneralCommands.toVSCode.openOnWebsite:
|
||||
this.openOnWebsite(msg.payload);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.runCommand:
|
||||
if (msg.payload) {
|
||||
const { command, args } = msg.payload;
|
||||
commands.executeCommand(command, args);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import {
|
||||
GIT_CONFIG,
|
||||
SETTING_DATE_FORMAT,
|
||||
SETTING_GIT_COMMIT_MSG,
|
||||
SETTING_GIT_DISABLED_BRANCHES,
|
||||
SETTING_GIT_ENABLED,
|
||||
SETTING_GIT_REQUIRES_COMMIT_MSG,
|
||||
SETTING_GIT_SUBMODULE_BRANCH,
|
||||
SETTING_GIT_SUBMODULE_FOLDER,
|
||||
SETTING_GIT_SUBMODULE_PULL,
|
||||
@@ -19,21 +21,58 @@ import {
|
||||
Extension,
|
||||
Logger,
|
||||
Notifications,
|
||||
processKnownPlaceholders,
|
||||
parseWinPath,
|
||||
processTimePlaceholders,
|
||||
Telemetry
|
||||
} from '../../helpers';
|
||||
import { GeneralCommands } from './../../constants/GeneralCommands';
|
||||
import simpleGit, { SimpleGit } from 'simple-git';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { commands } from 'vscode';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { Event, commands, extensions } from 'vscode';
|
||||
import { GitAPIState, GitRepository, PostMessageData } from '../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
|
||||
export class GitListener {
|
||||
private static gitAPI: {
|
||||
onDidChangeState: Event<GitAPIState>;
|
||||
onDidOpenRepository: Event<GitRepository>;
|
||||
onDidCloseRepository: Event<GitRepository>;
|
||||
getAPI: (version: number) => any;
|
||||
repositories: GitRepository[];
|
||||
} | null = null;
|
||||
private static isRegistered: boolean = false;
|
||||
private static client: SimpleGit | null = null;
|
||||
private static subClient: SimpleGit | null = null;
|
||||
private static repository: GitRepository | null = null;
|
||||
private static branchName: string | null = null;
|
||||
|
||||
/**
|
||||
* Retrieves the Git settings.
|
||||
* @returns {Promise<{
|
||||
* isGitRepo: boolean,
|
||||
* actions: boolean,
|
||||
* disabledBranches: string[],
|
||||
* requiresCommitMessage: string[]
|
||||
* }>} The Git settings.
|
||||
*/
|
||||
public static async getSettings() {
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
if (gitActions) {
|
||||
return {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false,
|
||||
disabledBranches: gitActions
|
||||
? Settings.get<string[]>(SETTING_GIT_DISABLED_BRANCHES) || []
|
||||
: [],
|
||||
requiresCommitMessage: gitActions
|
||||
? Settings.get<string[]>(SETTING_GIT_REQUIRES_COMMIT_MSG) || []
|
||||
: []
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the listener
|
||||
@@ -64,11 +103,19 @@ export class GitListener {
|
||||
*/
|
||||
public static process(msg: PostMessageData) {
|
||||
switch (msg.command) {
|
||||
case GeneralCommands.toVSCode.gitIsRepo:
|
||||
this.checkIsGitRepo(msg.command, msg.requestId);
|
||||
case GeneralCommands.toVSCode.git.sync:
|
||||
this.sync(msg.payload);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.gitSync:
|
||||
this.sync();
|
||||
case GeneralCommands.toVSCode.git.fetch:
|
||||
this.sync(undefined, false);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.git.getBranch:
|
||||
this.getBranch(msg.command, msg.requestId);
|
||||
break;
|
||||
case GeneralCommands.toVSCode.git.selectBranch:
|
||||
this.selectBranch();
|
||||
case GeneralCommands.toVSCode.git.isRepo:
|
||||
this.checkIsGitRepo(msg.command, msg.requestId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -83,27 +130,41 @@ export class GitListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the sync
|
||||
* Selects the current branch in the Git repository.
|
||||
* @returns {Promise<void>} A promise that resolves when the branch command has been executed.
|
||||
*/
|
||||
public static async sync() {
|
||||
try {
|
||||
this.sendMsg(GeneralCommands.toWebview.gitSyncingStart, {});
|
||||
public static async selectBranch(): Promise<void> {
|
||||
const workspaceFolder = Folders.getWorkspaceFolder();
|
||||
await commands.executeCommand('git.checkout', workspaceFolder);
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.gitSync);
|
||||
/**
|
||||
* Synchronizes the local repository with the remote repository.
|
||||
* @param commitMsg The commit message for the push operation.
|
||||
* @param isSync Determines whether to perform a sync operation (default: true) or a fetch operation.
|
||||
*/
|
||||
public static async sync(commitMsg?: string, isSync: boolean = true) {
|
||||
try {
|
||||
this.sendMsg(GeneralCommands.toWebview.git.syncingStart, isSync ? 'syncing' : 'fetching');
|
||||
|
||||
Telemetry.send(isSync ? TelemetryEvent.gitSync : TelemetryEvent.gitFetch);
|
||||
|
||||
await this.pull();
|
||||
await this.push();
|
||||
|
||||
this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {});
|
||||
if (isSync) {
|
||||
await this.push(commitMsg);
|
||||
}
|
||||
|
||||
this.sendMsg(GeneralCommands.toWebview.git.syncingEnd, {});
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
this.sendMsg(GeneralCommands.toWebview.gitSyncingEnd, {});
|
||||
this.sendMsg(GeneralCommands.toWebview.git.syncingEnd, {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current workspace is a git repository
|
||||
* @returns
|
||||
* Checks if the current workspace is a Git repository.
|
||||
* @returns A boolean indicating whether the current workspace is a Git repository.
|
||||
*/
|
||||
public static async isGitRepository() {
|
||||
const git = this.getClient();
|
||||
@@ -117,12 +178,15 @@ export class GitListener {
|
||||
Logger.warning(`Current workspace is not a GIT repository`);
|
||||
}
|
||||
|
||||
GitListener.vscodeGitProvider();
|
||||
|
||||
return isRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the changes from the remote
|
||||
* @returns
|
||||
* Pulls the latest changes from the remote repository.
|
||||
* If submoduleFolder is specified, it checks out the submoduleBranch for the submodule located in that folder.
|
||||
* If submodulePull is true, it also updates the submodules with the latest changes from the remote repository.
|
||||
*/
|
||||
private static async pull() {
|
||||
const git = this.getClient();
|
||||
@@ -157,15 +221,18 @@ export class GitListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the changes to the remote
|
||||
* @returns
|
||||
* Pushes the changes to the remote repository.
|
||||
*
|
||||
* @param commitMsg The commit message to use. If not provided, it will use the default commit message or the one specified in the settings.
|
||||
* @returns A promise that resolves when the push operation is completed.
|
||||
*/
|
||||
private static async push() {
|
||||
let commitMsg = Settings.get<string>(SETTING_GIT_COMMIT_MSG);
|
||||
private static async push(commitMsg?: string) {
|
||||
commitMsg =
|
||||
commitMsg || Settings.get<string>(SETTING_GIT_COMMIT_MSG) || 'Synced by Front Matter';
|
||||
|
||||
if (commitMsg) {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
commitMsg = processKnownPlaceholders(commitMsg, undefined, dateFormat);
|
||||
commitMsg = processTimePlaceholders(commitMsg, dateFormat);
|
||||
commitMsg = await ArticleHelper.processCustomPlaceholders(commitMsg, undefined, undefined);
|
||||
}
|
||||
|
||||
@@ -240,9 +307,11 @@ export class GitListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the git client
|
||||
* @param submoduleFolder
|
||||
* @returns
|
||||
* Retrieves the Git client instance based on the provided submodule folder.
|
||||
* If no submodule folder is provided, it returns the main Git client instance.
|
||||
* If a submodule folder is provided, it returns the submodule-specific Git client instance.
|
||||
* @param submoduleFolder The path to the submodule folder.
|
||||
* @returns The Git client instance or null if it cannot be retrieved.
|
||||
*/
|
||||
private static getClient(submoduleFolder: string = ''): SimpleGit | null {
|
||||
if (!submoduleFolder && this.client) {
|
||||
@@ -269,9 +338,100 @@ export class GitListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message to the webview
|
||||
* @param command
|
||||
* @param payload
|
||||
* Initializes the VS Code Git provider and sets up event listeners for repository changes.
|
||||
* @returns {Promise<void>} A promise that resolves when the Git provider is initialized.
|
||||
*/
|
||||
private static async vscodeGitProvider(): Promise<void> {
|
||||
if (!GitListener.gitAPI) {
|
||||
const extension = extensions.getExtension('vscode.git');
|
||||
|
||||
/**
|
||||
* Logic from: https://github.com/microsoft/vscode/blob/main/extensions/github/src/extension.ts
|
||||
* initializeGitExtension
|
||||
*/
|
||||
if (extension) {
|
||||
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
|
||||
|
||||
// Get version 1 of the API
|
||||
GitListener.gitAPI = gitExtension.getAPI(1);
|
||||
|
||||
if (!GitListener.gitAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
GitListener.listenToRepo(GitListener.gitAPI?.repositories);
|
||||
|
||||
GitListener.gitAPI.onDidChangeState(() => {
|
||||
GitListener.listenToRepo(GitListener.gitAPI?.repositories);
|
||||
});
|
||||
|
||||
GitListener.gitAPI.onDidOpenRepository((repo: GitRepository) => {
|
||||
GitListener.triggerBranchChange(repo);
|
||||
});
|
||||
|
||||
GitListener.gitAPI.onDidCloseRepository((repo: GitRepository) => {
|
||||
Logger.info(`Closed repo: ${repo?.state?.HEAD?.name}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the branch name and sends a request.
|
||||
* @param command - The command to send.
|
||||
* @param requestId - The ID of the request.
|
||||
*/
|
||||
private static async getBranch(command: string, requestId?: string) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendRequest(command, requestId, GitListener.repository?.state?.HEAD?.name);
|
||||
}
|
||||
|
||||
private static listenToRepo(repositories: GitRepository[] | undefined) {
|
||||
if (!repositories) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (repositories && repositories.length === 1) {
|
||||
GitListener.triggerBranchChange(repositories[0]);
|
||||
} else if (repositories && repositories.length > 1) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const repo = repositories.find(
|
||||
(repo) => parseWinPath(repo.rootUri.fsPath) === parseWinPath(wsFolder.fsPath)
|
||||
);
|
||||
if (repo) {
|
||||
GitListener.triggerBranchChange(repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a branch change event for the specified Git repository.
|
||||
* @param repo The Git repository to monitor for branch changes.
|
||||
*/
|
||||
private static async triggerBranchChange(repo: GitRepository | null) {
|
||||
if (repo && repo.state) {
|
||||
if (repo.state?.HEAD?.name && repo.state.HEAD.name !== GitListener.branchName) {
|
||||
GitListener.branchName = repo.state.HEAD.name;
|
||||
GitListener.repository = repo;
|
||||
|
||||
this.sendMsg(GeneralCommands.toWebview.git.branchName, GitListener.branchName);
|
||||
|
||||
repo.state.onDidChange(() => {
|
||||
GitListener.triggerBranchChange(repo);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the panel and the dashboard.
|
||||
* @param command - The command to send.
|
||||
* @param payload - The payload to send with the command.
|
||||
*/
|
||||
private static sendMsg(command: string, payload: any) {
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
@@ -281,4 +441,23 @@ export class GitListener {
|
||||
|
||||
Dashboard.postWebviewMessage({ command: command as any, payload });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the webview panel.
|
||||
* @param command - The command to send.
|
||||
* @param requestId - The unique identifier for the request.
|
||||
* @param payload - The payload to send with the request.
|
||||
*/
|
||||
private static sendRequest(command: string, requestId: string, payload: any) {
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = PanelProvider.getInstance(extPath);
|
||||
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
payload
|
||||
});
|
||||
|
||||
Dashboard.postWebviewMessage({ command: command as any, requestId, payload });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Article } from '../../commands';
|
||||
import { ArticleHelper } from '../../helpers';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
@@ -17,7 +17,7 @@ export class ArticleListener extends BaseListener {
|
||||
Article.updateSlug();
|
||||
break;
|
||||
case CommandToCode.generateSlug:
|
||||
this.generateSlug(msg.payload);
|
||||
this.generateSlug(msg.command, msg.payload, msg.requestId);
|
||||
break;
|
||||
case CommandToCode.updateLastMod:
|
||||
Article.setLastModifiedDate();
|
||||
@@ -32,10 +32,19 @@ export class ArticleListener extends BaseListener {
|
||||
* Generate a slug
|
||||
* @param title
|
||||
*/
|
||||
private static generateSlug(title: string) {
|
||||
const slug = Article.generateSlug(title);
|
||||
private static generateSlug(
|
||||
command: CommandToCode,
|
||||
{ title, slugTemplate }: { title: string; slugTemplate?: string },
|
||||
requestId?: string
|
||||
) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatterFromCurrentDocument();
|
||||
const slug = Article.generateSlug(title, article, slugTemplate);
|
||||
if (slug) {
|
||||
this.sendMsg(Command.updatedSlug, slug);
|
||||
this.sendRequest(command, requestId, slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,16 @@ import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { authentication, commands, window } from 'vscode';
|
||||
import { ArticleHelper, ContentType, Extension, Logger, Settings } from '../../helpers';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
Logger,
|
||||
Settings,
|
||||
ContentType,
|
||||
processArticlePlaceholdersFromData,
|
||||
processTimePlaceholders,
|
||||
processFmPlaceholders
|
||||
} from '../../helpers';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
DefaultFields,
|
||||
@@ -20,8 +29,7 @@ import {
|
||||
} from '../../constants';
|
||||
import { Article, Preview } from '../../commands';
|
||||
import { ParsedFrontMatter } from '../../parsers';
|
||||
import { processKnownPlaceholders } from '../../helpers/PlaceholderHelper';
|
||||
import { Field, Mode, PostMessageData } from '../../models';
|
||||
import { Field, Mode, PostMessageData, ContentType as IContentType } from '../../models';
|
||||
import { encodeEmoji, fieldWhenClause } from '../../utils';
|
||||
import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
@@ -66,7 +74,11 @@ export class DataListener extends BaseListener {
|
||||
this.isServerStarted(msg.command, msg?.requestId);
|
||||
break;
|
||||
case CommandToCode.updatePlaceholder:
|
||||
this.updatePlaceholder(msg?.payload?.field, msg?.payload?.value, msg?.payload?.title);
|
||||
this.updatePlaceholder(
|
||||
msg.command,
|
||||
msg.payload as { field: string; value: string; data: { [key: string]: any } },
|
||||
msg.requestId
|
||||
);
|
||||
break;
|
||||
case CommandToCode.generateContentType:
|
||||
commands.executeCommand(COMMAND_NAME.generateContentType);
|
||||
@@ -211,7 +223,11 @@ export class DataListener extends BaseListener {
|
||||
|
||||
if (keys.length > 0 && contentTypes && wsFolder) {
|
||||
// Get the current content type
|
||||
const contentType = ArticleHelper.getContentType(updatedMetadata);
|
||||
const contentType = ArticleHelper.getContentType({
|
||||
content: '',
|
||||
data: updatedMetadata,
|
||||
path: filePath
|
||||
});
|
||||
let slugField;
|
||||
if (contentType) {
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields);
|
||||
@@ -597,18 +613,37 @@ export class DataListener extends BaseListener {
|
||||
* @param value
|
||||
* @param title
|
||||
*/
|
||||
private static async updatePlaceholder(field: string, value: string, title: string) {
|
||||
if (field && value) {
|
||||
private static async updatePlaceholder(
|
||||
command: CommandToCode,
|
||||
articleData: {
|
||||
field: string;
|
||||
value: string;
|
||||
data: { [key: string]: any };
|
||||
contentType?: IContentType;
|
||||
},
|
||||
requestId?: string
|
||||
) {
|
||||
if (!command || !requestId || !articleData) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { field, value, data, contentType } = articleData;
|
||||
|
||||
value = value || '';
|
||||
if (field) {
|
||||
const crntFile = window.activeTextEditor?.document;
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
value = processKnownPlaceholders(value, title || '', dateFormat);
|
||||
value =
|
||||
data && contentType ? processArticlePlaceholdersFromData(value, data, contentType) : value;
|
||||
value = processTimePlaceholders(value, dateFormat);
|
||||
value = processFmPlaceholders(value, data);
|
||||
value = await ArticleHelper.processCustomPlaceholders(
|
||||
value,
|
||||
title || '',
|
||||
data.title || '',
|
||||
crntFile?.uri.fsPath || ''
|
||||
);
|
||||
}
|
||||
|
||||
this.sendMsg(Command.updatePlaceholder, { field, value });
|
||||
this.sendRequest(Command.updatePlaceholder, requestId, { field, value });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,14 @@ export enum LocalizationKey {
|
||||
* Back
|
||||
*/
|
||||
commonBack = 'common.back',
|
||||
/**
|
||||
* Open
|
||||
*/
|
||||
commonOpen = 'common.open',
|
||||
/**
|
||||
* Open: {0}
|
||||
*/
|
||||
commonOpenWithValue = 'common.openWithValue',
|
||||
/**
|
||||
* Loading content
|
||||
*/
|
||||
@@ -167,6 +175,10 @@ export enum LocalizationKey {
|
||||
* Astro
|
||||
*/
|
||||
settingsViewAstro = 'settings.view.astro',
|
||||
/**
|
||||
* Integration
|
||||
*/
|
||||
settingsViewIntegration = 'settings.view.integration',
|
||||
/**
|
||||
* Open dashboard on startup
|
||||
*/
|
||||
@@ -211,6 +223,10 @@ export enum LocalizationKey {
|
||||
* Read more about Git submodules
|
||||
*/
|
||||
settingsGitSubmoduleLink = 'settings.git.submoduleLink',
|
||||
/**
|
||||
* Integration
|
||||
*/
|
||||
settingsIntegrationTitle = 'settings.integration.title',
|
||||
/**
|
||||
* Website and SSG settings
|
||||
*/
|
||||
@@ -227,6 +243,18 @@ export enum LocalizationKey {
|
||||
* SSG/Framework start command
|
||||
*/
|
||||
settingsCommonSettingsStartCommand = 'settings.commonSettings.startCommand',
|
||||
/**
|
||||
* DeepL
|
||||
*/
|
||||
settingsIntegrationsViewDeeplTitle = 'settings.integrationsView.deepl.title',
|
||||
/**
|
||||
* Authentication key
|
||||
*/
|
||||
settingsIntegrationsViewDeeplIntputLabel = 'settings.integrationsView.deepl.intput.label',
|
||||
/**
|
||||
* Enter your DeepL authentication key
|
||||
*/
|
||||
settingsIntegrationsViewDeeplIntputPlaceholder = 'settings.integrationsView.deepl.intput.placeholder',
|
||||
/**
|
||||
* Developer mode
|
||||
*/
|
||||
@@ -307,6 +335,14 @@ export enum LocalizationKey {
|
||||
* Are you sure you want to delete the "{0}" content?
|
||||
*/
|
||||
dashboardContentsContentActionsAlertDescription = 'dashboard.contents.contentActions.alert.description',
|
||||
/**
|
||||
* Create translation
|
||||
*/
|
||||
dashboardContentsContentActionsTranslationsCreate = 'dashboard.contents.contentActions.translations.create',
|
||||
/**
|
||||
* Translations
|
||||
*/
|
||||
dashboardContentsContentActionsTranslationsMenu = 'dashboard.contents.contentActions.translations.menu',
|
||||
/**
|
||||
* <invalid title>
|
||||
*/
|
||||
@@ -419,6 +455,14 @@ export enum LocalizationKey {
|
||||
* Please close the dashboard and try again.
|
||||
*/
|
||||
dashboardErrorViewDescription = 'dashboard.errorView.description',
|
||||
/**
|
||||
* Locale
|
||||
*/
|
||||
dashboardFiltersLanguageFilterLabel = 'dashboard.filters.languageFilter.label',
|
||||
/**
|
||||
* All
|
||||
*/
|
||||
dashboardFiltersLanguageFilterAll = 'dashboard.filters.languageFilter.all',
|
||||
/**
|
||||
* Home
|
||||
*/
|
||||
@@ -675,6 +719,14 @@ export enum LocalizationKey {
|
||||
* Create new folder
|
||||
*/
|
||||
dashboardMediaFolderCreationFolderCreate = 'dashboard.media.folderCreation.folder.create',
|
||||
/**
|
||||
* Insert image
|
||||
*/
|
||||
dashboardMediaItemButtomInsertImage = 'dashboard.media.item.buttom.insert.image',
|
||||
/**
|
||||
* Insert snippet
|
||||
*/
|
||||
dashboardMediaItemButtomInsertSnippet = 'dashboard.media.item.buttom.insert.snippet',
|
||||
/**
|
||||
* Insert image for your "{0}" field
|
||||
*/
|
||||
@@ -691,6 +743,10 @@ export enum LocalizationKey {
|
||||
* Delete media file
|
||||
*/
|
||||
dashboardMediaItemQuickActionDelete = 'dashboard.media.item.quickAction.delete',
|
||||
/**
|
||||
* View media details
|
||||
*/
|
||||
dashboardMediaItemMenuItemView = 'dashboard.media.item.menuItem.view',
|
||||
/**
|
||||
* Edit metadata
|
||||
*/
|
||||
@@ -975,6 +1031,10 @@ export enum LocalizationKey {
|
||||
* Add {0} to taxonomy settings
|
||||
*/
|
||||
dashboardTaxonomyViewButtonAddTitle = 'dashboard.taxonomyView.button.add.title',
|
||||
/**
|
||||
* Tag content
|
||||
*/
|
||||
dashboardTaxonomyViewButtonTagTitle = 'dashboard.taxonomyView.button.tag.title',
|
||||
/**
|
||||
* Edit {0}
|
||||
*/
|
||||
@@ -1123,6 +1183,22 @@ export enum LocalizationKey {
|
||||
* The following Astro Content Collections can be used to generate a content-type.
|
||||
*/
|
||||
dashboardConfigurationAstroAstroContentTypesDescription = 'dashboard.configuration.astro.astroContentTypes.description',
|
||||
/**
|
||||
* Publish changes
|
||||
*/
|
||||
panelGitGitActionTitle = 'panel.git.gitAction.title',
|
||||
/**
|
||||
* Select branch
|
||||
*/
|
||||
panelGitGitActionBranchSelect = 'panel.git.gitAction.branch.select',
|
||||
/**
|
||||
* Commit message
|
||||
*/
|
||||
panelGitGitActionInputPlaceholder = 'panel.git.gitAction.input.placeholder',
|
||||
/**
|
||||
* Fetch
|
||||
*/
|
||||
panelGitGitActionButtonFetch = 'panel.git.gitAction.button.fetch',
|
||||
/**
|
||||
* Content-type
|
||||
*/
|
||||
@@ -1632,6 +1708,46 @@ export enum LocalizationKey {
|
||||
* Create folder
|
||||
*/
|
||||
commandsFoldersGetNotificationErrorCreateAction = 'commands.folders.get.notificationError.create.action',
|
||||
/**
|
||||
* No file selected.
|
||||
*/
|
||||
commandsI18nCreateWarningNoFileSelected = 'commands.i18n.create.warning.noFileSelected',
|
||||
/**
|
||||
* The file could not be retrieved.
|
||||
*/
|
||||
commandsI18nCreateWarningNoFile = 'commands.i18n.create.warning.noFile',
|
||||
/**
|
||||
* Content type could not be retrieved for the current file.
|
||||
*/
|
||||
commandsI18nCreateWarningNoContentType = 'commands.i18n.create.warning.noContentType',
|
||||
/**
|
||||
* No i18n configuration found.
|
||||
*/
|
||||
commandsI18nCreateWarningNoConfig = 'commands.i18n.create.warning.noConfig',
|
||||
/**
|
||||
* The current file cannot be used for i18n content creation.
|
||||
*/
|
||||
commandsI18nCreateWarningNotDefaultLocale = 'commands.i18n.create.warning.notDefaultLocale',
|
||||
/**
|
||||
* The i18n translation already exists.
|
||||
*/
|
||||
commandsI18nCreateErrorFileExists = 'commands.i18n.create.error.fileExists',
|
||||
/**
|
||||
* Created "{0}" i18n content file.
|
||||
*/
|
||||
commandsI18nCreateSuccessCreated = 'commands.i18n.create.success.created',
|
||||
/**
|
||||
* Create content for locale
|
||||
*/
|
||||
commandsI18nCreateQuickPickTitle = 'commands.i18n.create.quickPick.title',
|
||||
/**
|
||||
* To which locale do you want to create a new content?
|
||||
*/
|
||||
commandsI18nCreateQuickPickPlaceHolder = 'commands.i18n.create.quickPick.placeHolder',
|
||||
/**
|
||||
* Translating content...
|
||||
*/
|
||||
commandsI18nTranslateProgressTitle = 'commands.i18n.translate.progress.title',
|
||||
/**
|
||||
* Preview: {0}
|
||||
*/
|
||||
@@ -2312,6 +2428,10 @@ export enum LocalizationKey {
|
||||
* Failed to initialize the template.
|
||||
*/
|
||||
listenersDashboardSettingsListenerTriggerTemplateInitError = 'listeners.dashboard.settingsListener.triggerTemplate.init.error',
|
||||
/**
|
||||
* Setting has been updated.
|
||||
*/
|
||||
listenersDashboardSettingsListenerSetSecretValueMessage = 'listeners.dashboard.settingsListener.setSecretValue.message',
|
||||
/**
|
||||
* Snippet missing title or body
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { I18nConfig } from './i18nConfig';
|
||||
|
||||
export interface ContentFolder {
|
||||
title: string;
|
||||
path: string;
|
||||
@@ -10,4 +12,6 @@ export interface ContentFolder {
|
||||
originalPath?: string;
|
||||
$schema?: string;
|
||||
extended?: boolean;
|
||||
defaultLocale?: string;
|
||||
locales: I18nConfig[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Position } from 'vscode';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { BlockFieldData } from './BlockFieldData';
|
||||
import { ContentType } from '.';
|
||||
|
||||
export interface DashboardData {
|
||||
type: NavigationType;
|
||||
@@ -12,6 +13,7 @@ export interface ViewData {
|
||||
fieldName?: string;
|
||||
position?: Position;
|
||||
fileTitle?: string;
|
||||
contentType?: ContentType;
|
||||
selection?: string;
|
||||
range?: SnippetRange;
|
||||
snippetInfo?: SnippetInfo;
|
||||
|
||||
34
src/models/GitRepository.ts
Normal file
34
src/models/GitRepository.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Event } from 'vscode';
|
||||
|
||||
export type GitAPIState = 'uninitialized' | 'initialized';
|
||||
|
||||
export interface GitRepository {
|
||||
state: GitRepositoryState;
|
||||
rootUri: {
|
||||
fsPath: string;
|
||||
path: string;
|
||||
};
|
||||
repository: {
|
||||
getBranches: () => Promise<GitBranch[]>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GitRepositoryState {
|
||||
HEAD?: GitBranch;
|
||||
onDidChange: Event<void>;
|
||||
}
|
||||
|
||||
export interface GitBranch {
|
||||
type: number;
|
||||
name?: string;
|
||||
upstream: Upstream;
|
||||
commit: string;
|
||||
ahead: number;
|
||||
behind: number;
|
||||
}
|
||||
|
||||
export interface Upstream {
|
||||
name: string;
|
||||
remote: string;
|
||||
commit: string;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface GitSettings {
|
||||
isGitRepo: boolean;
|
||||
actions: boolean;
|
||||
disabledBranches: string[];
|
||||
requiresCommitMessage: string[];
|
||||
}
|
||||
|
||||
32
src/models/MediaContentType.ts
Normal file
32
src/models/MediaContentType.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Field } from '.';
|
||||
|
||||
export interface MediaContentType {
|
||||
name: string;
|
||||
fileTypes: string[] | null | undefined;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
export const DEFAULT_MEDIA_CONTENT_TYPE: MediaContentType = {
|
||||
name: 'default',
|
||||
fileTypes: null,
|
||||
fields: [
|
||||
{
|
||||
title: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
title: 'Caption',
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
title: 'Alt text',
|
||||
name: 'alt',
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -14,11 +14,15 @@ export interface MediaInfo {
|
||||
fsPath: string;
|
||||
vsPath: string | undefined;
|
||||
dimensions?: ISizeCalculationResult | undefined;
|
||||
title?: string | undefined;
|
||||
caption?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
mimeType?: string | undefined;
|
||||
mtime?: Date;
|
||||
ctime?: Date;
|
||||
size?: number;
|
||||
|
||||
metadata: {
|
||||
title?: string | undefined;
|
||||
caption?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
[fieldName: string]: string | string[] | Date | number | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DashboardData } from './DashboardData';
|
||||
import { DataType } from './DataType';
|
||||
|
||||
export interface PanelSettings {
|
||||
git: GitSettings;
|
||||
git: GitSettings | undefined;
|
||||
seo: SEO;
|
||||
slug: Slug;
|
||||
tags: string[];
|
||||
@@ -59,6 +59,7 @@ export interface ContentType {
|
||||
|
||||
fileType?: 'md' | 'mdx' | string;
|
||||
previewPath?: string | null;
|
||||
slugTemplate?: string;
|
||||
pageBundle?: boolean;
|
||||
defaultFileName?: string;
|
||||
template?: string;
|
||||
@@ -105,7 +106,7 @@ export interface Field {
|
||||
isPreviewImage?: boolean;
|
||||
hidden?: boolean;
|
||||
taxonomyId?: string;
|
||||
default?: string;
|
||||
default?: string | number | string[] | boolean;
|
||||
fields?: Field[];
|
||||
fieldGroup?: string | string[];
|
||||
dataType?: string | string[];
|
||||
|
||||
5
src/models/i18nConfig.ts
Normal file
5
src/models/i18nConfig.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface I18nConfig {
|
||||
locale: string;
|
||||
title?: string;
|
||||
path?: string;
|
||||
}
|
||||
@@ -12,8 +12,10 @@ export * from './DataFolder';
|
||||
export * from './DataType';
|
||||
export * from './DraftField';
|
||||
export * from './Framework';
|
||||
export * from './GitRepository';
|
||||
export * from './GitSettings';
|
||||
export * from './LoadingType';
|
||||
export * from './MediaContentType';
|
||||
export * from './MediaPaths';
|
||||
export * from './Mode';
|
||||
export * from './PanelSettings';
|
||||
@@ -29,3 +31,4 @@ export * from './TaxonomyType';
|
||||
export * from './Template';
|
||||
export * from './UnmappedMedia';
|
||||
export * from './VersionInfo';
|
||||
export * from './i18nConfig';
|
||||
|
||||
@@ -10,6 +10,5 @@ export enum Command {
|
||||
sendMediaUrl = 'sendMediaUrl',
|
||||
updatePlaceholder = 'updatePlaceholder',
|
||||
dataFileEntries = 'dataFileEntries',
|
||||
updatedSlug = 'updatedSlug',
|
||||
serverStarted = 'server-started'
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !localeReady) {
|
||||
if (loading && !localeReady) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
@@ -116,37 +116,44 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
|
||||
<div className={`ext_actions`}>
|
||||
<GitAction settings={settings} />
|
||||
|
||||
<CustomView metadata={metadata} />
|
||||
{!loading && (<CustomView metadata={metadata} />)}
|
||||
|
||||
<FeatureFlag features={mode?.features || [...allPanelValues]} flag={FEATURE_FLAG.panel.globalSettings}>
|
||||
<GlobalSettings settings={settings} />
|
||||
</FeatureFlag>
|
||||
|
||||
{settings && settings.seo && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.seo}>
|
||||
<SeoStatus
|
||||
seo={settings.seo}
|
||||
data={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
)}
|
||||
{settings && metadata && (
|
||||
{
|
||||
!loading && settings && settings.seo && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.seo}>
|
||||
<SeoStatus
|
||||
seo={settings.seo}
|
||||
data={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
{!loading && settings && metadata && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.actions}>
|
||||
<Actions metadata={metadata} settings={settings} scripts={settings.scripts} />
|
||||
</FeatureFlag>
|
||||
)}
|
||||
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.metadata}>
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
features={mode?.features || []}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
{
|
||||
!loading && (
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.metadata}>
|
||||
<Metadata
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
features={mode?.features || []}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
|
||||
<FolderAndFiles data={folderAndFiles} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IActionButtonProps {
|
||||
title: JSX.Element | string;
|
||||
title: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
|
||||
@@ -11,12 +11,13 @@ const ActionButton: React.FunctionComponent<IActionButtonProps> = ({
|
||||
className,
|
||||
onClick,
|
||||
disabled,
|
||||
title
|
||||
title,
|
||||
children
|
||||
}: React.PropsWithChildren<IActionButtonProps>) => {
|
||||
return (
|
||||
<div className={`article__action`}>
|
||||
<button onClick={onClick} className={className || ''} disabled={disabled}>
|
||||
{title}
|
||||
<div className={`article__action w-full`}>
|
||||
<button type="button" title={title} onClick={onClick} className={className || ''} disabled={disabled}>
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user