mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d51531d59 | ||
|
|
0cb7d2463b | ||
|
|
ceeb1bf9a7 | ||
|
|
c11efa56f1 | ||
|
|
fa3215fa64 | ||
|
|
305c95fa86 | ||
|
|
3b7671afc9 | ||
|
|
8660f5f680 | ||
|
|
2269994b43 | ||
|
|
bdcd901e51 | ||
|
|
6d7df4266d | ||
|
|
8c2d243777 | ||
|
|
4282ec83e5 | ||
|
|
0f3c43e0fc | ||
|
|
a377f27765 | ||
|
|
17860a18f4 | ||
|
|
73609ca346 | ||
|
|
d6dfa8c9cf | ||
|
|
1c00362b1c | ||
|
|
63ea564734 | ||
|
|
38f128e1b6 | ||
|
|
39704f3a55 | ||
|
|
2020198e90 | ||
|
|
ba1cf95ffd | ||
|
|
aea87a6168 | ||
|
|
179a71db39 | ||
|
|
8d8e3fe3cc | ||
|
|
3d8c550f60 | ||
|
|
6fd526e962 | ||
|
|
788d0241fd | ||
|
|
017a2d7597 | ||
|
|
3019ba1dff | ||
|
|
13e58d26a1 | ||
|
|
634196b056 | ||
|
|
8b95468c78 | ||
|
|
dc23aba128 | ||
|
|
a778be9737 | ||
|
|
b9508df4f8 | ||
|
|
0110b7365c | ||
|
|
6588b90e7d | ||
|
|
47dba5f510 | ||
|
|
121a84659f | ||
|
|
620966c08e | ||
|
|
06718c3577 | ||
|
|
178207fd82 | ||
|
|
657e9054f6 | ||
|
|
36a8002cea | ||
|
|
07f124dcf5 | ||
|
|
ff1d4487f4 | ||
|
|
66151083c0 | ||
|
|
83abff67ac | ||
|
|
431a83b882 | ||
|
|
d240e8fdc8 | ||
|
|
e95e9a8fc7 | ||
|
|
d8e3338abe | ||
|
|
6f6b97e6ca | ||
|
|
3f8665cadf | ||
|
|
8cc68be4da | ||
|
|
27f2b57c24 | ||
|
|
9b1be1a6c1 | ||
|
|
d0b7af5c86 | ||
|
|
f13058c59b | ||
|
|
cf28e5fc85 | ||
|
|
cf787ab0f6 | ||
|
|
c7424a6d73 |
@@ -12,6 +12,9 @@
|
||||
"no-throw-literal": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"curly": "error",
|
||||
"class-methods-use-this": "warn"
|
||||
"class-methods-use-this": "warn",
|
||||
"no-console": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"no-extra-boolean-cast": "off"
|
||||
}
|
||||
}
|
||||
|
||||
3
.github/actions/localization/action.yml
vendored
3
.github/actions/localization/action.yml
vendored
@@ -20,7 +20,7 @@ runs:
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -42,5 +42,6 @@ runs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
include-hidden-files: true
|
||||
name: ${{ inputs.PACKAGE_NAME }}
|
||||
path: .
|
||||
4
.github/workflows/release-beta.yml
vendored
4
.github/workflows/release-beta.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,5 +1,49 @@
|
||||
# Change Log
|
||||
|
||||
## [10.5.0] - 2024-10-21 - [Release notes](https://beta.frontmatter.codes/updates/v10.5.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#840](https://github.com/estruyf/vscode-front-matter/issues/840): Added the `excludePaths` option for the content folder settings
|
||||
- [#850](https://github.com/estruyf/vscode-front-matter/issues/850): Extended the i18n/language button to open or create new language files (thanks to [Dennis Zoma](https://github.com/wottpal))
|
||||
- [#851](https://github.com/estruyf/vscode-front-matter/issues/851): Added `sameContentLocale` option to `contentRelationship` field (thanks to [Dennis Zoma](https://github.com/wottpal))
|
||||
- [#866](https://github.com/estruyf/vscode-front-matter/issues/866): Support Markdown in the WYSIWYG `string` field
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#858](https://github.com/estruyf/vscode-front-matter/issues/858): Fix button styling on the data screen
|
||||
- [#860](https://github.com/estruyf/vscode-front-matter/issues/860): Fix typo on the data screen
|
||||
- [#870](https://github.com/estruyf/vscode-front-matter/issues/870): Fix data number field styling
|
||||
|
||||
## [10.4.1] - 2024-09-27
|
||||
|
||||
- [#855](https://github.com/estruyf/vscode-front-matter/issues/855): Fix in panel sections
|
||||
|
||||
## [10.4.0] - 2024-09-25 - [Release notes](https://beta.frontmatter.codes/updates/v10.4.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#844](https://github.com/estruyf/vscode-front-matter/issues/844): New `{{filePrefix.index}}` placeholder to add the index number of the file in the folder
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#833](https://github.com/estruyf/vscode-front-matter/issues/833): Added support for Asciidoc files
|
||||
- [#834](https://github.com/estruyf/vscode-front-matter/issues/834): Added the ability to create new data files for a data folder
|
||||
- [#841](https://github.com/estruyf/vscode-front-matter/issues/841): Enable placeholders for file prefixes
|
||||
- [#846](https://github.com/estruyf/vscode-front-matter/issues/846): Added GitHub Copilot action for title field
|
||||
- [#848](https://github.com/estruyf/vscode-front-matter/issues/848): Set the default GitHub Copilot model to `gpt-4o-mini`
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#842](https://github.com/estruyf/vscode-front-matter/issues/842): Allow to set the `frontMatter.taxonomy.slugTemplate` setting to an empty string
|
||||
- [#845](https://github.com/estruyf/vscode-front-matter/issues/845): Fix empty values for number fields
|
||||
- [#849](https://github.com/estruyf/vscode-front-matter/issues/849): Show fields which are not empty in the metadata panel
|
||||
- [#853](https://github.com/estruyf/vscode-front-matter/issues/853): Allow empty values in date fields
|
||||
|
||||
### 🚧 Work in progress
|
||||
|
||||
- [#837](https://github.com/estruyf/vscode-front-matter/issues/837): Replacing the VSCode Webview UI Toolkit with [vscrui](https://github.com/estruyf/vscrui) due to the deprecation of the VSCode Webview UI Toolkit library
|
||||
|
||||
## [10.3.0] - 2024-08-13 - [Release notes](https://beta.frontmatter.codes/updates/v10.3.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
@@ -182,7 +182,7 @@ You can open showcase issues for the following things:
|
||||
## 💚 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
|
||||
<img src="https://api.frontmatter.codes/img-sponsors" alt="Front Matter sponsors" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -180,7 +180,7 @@ You can open showcase issues for the following things:
|
||||
## 💚 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
|
||||
<img src="https://api.frontmatter.codes/img-sponsors" alt="Front Matter sponsors" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
.frontmatter h3 {
|
||||
margin-bottom: 1rem;
|
||||
/* margin-bottom: 1rem; */
|
||||
}
|
||||
|
||||
.frontmatter p,
|
||||
@@ -224,6 +224,7 @@
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.ext_link_block button.active {
|
||||
|
||||
@@ -140,8 +140,12 @@
|
||||
"dashboard.dataView.dataView.noDataFiles": "No data files found",
|
||||
"dashboard.dataView.dataView.getStarted.link": "Read more to get started using data files",
|
||||
"dashboard.dataView.dataView.update.message": "Updated your data entries",
|
||||
"dashboard.dataView.dataView.createNew": "Create new data file",
|
||||
"dashboard.dataView.dataView.selectDataFolder": "Select data folder",
|
||||
"dashboard.dataView.dataView.closeSelectedDataFile": "Close data file",
|
||||
|
||||
"dashboard.dataView.emptyView.heading": "Select your date type first",
|
||||
"dashboard.dataView.emptyView.heading": "Select your data type first",
|
||||
"dashboard.dataView.emptyView.heading.create": "Start by creating a new data file",
|
||||
|
||||
"dashboard.dataView.sortableItem.editButton.title": "Edit \"{0}\"",
|
||||
"dashboard.dataView.sortableItem.deleteButton.title": "Delete \"{0}\"",
|
||||
@@ -579,8 +583,13 @@
|
||||
"commands.i18n.create.success.created": "Created \"{0}\" i18n content file.",
|
||||
"commands.i18n.create.quickPick.title": "Create content for locale",
|
||||
"commands.i18n.create.quickPick.placeHolder": "To which locale do you want to create a new content?",
|
||||
"commands.i18n.createOrOpen.quickPick.title": "Open or create translation",
|
||||
"commands.i18n.createOrOpen.quickPick.category.existing": "Existing translations",
|
||||
"commands.i18n.createOrOpen.quickPick.action.open": "Open \"{0}\"",
|
||||
"commands.i18n.createOrOpen.quickPick.category.new": "New translations",
|
||||
"commands.i18n.createOrOpen.quickPick.action.create": "Create \"{0}\"",
|
||||
"commands.i18n.translate.progress.title": "Translating content...",
|
||||
|
||||
|
||||
"commands.preview.panel.title": "Preview: {0}",
|
||||
"commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview",
|
||||
|
||||
@@ -783,6 +792,9 @@
|
||||
"listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "No article data",
|
||||
"listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "Couldn't find data file entries",
|
||||
"listeners.panel.dataListener.pushMetadata.frontMatter.error": "Something went wrong while parsing your front matter. Please check the contents of your file.",
|
||||
"listeners.panel.dataListener.createDataFile.inputTitle": "What is the name of the data file?",
|
||||
"listeners.panel.dataListener.createDataFile.error": "No data file id or path defined.",
|
||||
"listeners.panel.dataListener.createDataFile.noFileName": "No filename provided.",
|
||||
|
||||
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
|
||||
|
||||
6327
package-lock.json
generated
6327
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter CMS",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "10.3.0",
|
||||
"version": "10.5.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -283,6 +283,14 @@
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description%"
|
||||
},
|
||||
"excludePaths": {
|
||||
"type": "array",
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludePaths.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"previewPath": {
|
||||
"type": [
|
||||
"null",
|
||||
@@ -859,6 +867,20 @@
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.singleEntry.description%",
|
||||
"default": false
|
||||
},
|
||||
"enableFileCreation": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.enableFileCreation.description%",
|
||||
"default": false
|
||||
},
|
||||
"fileType": {
|
||||
"type": "string",
|
||||
"default": "json",
|
||||
"enum": [
|
||||
"json",
|
||||
"yaml"
|
||||
],
|
||||
"description": "%setting.frontMatter.data.folders.items.properties.fileType.description%"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -1344,7 +1366,14 @@
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description%"
|
||||
},
|
||||
"wysiwyg": {
|
||||
"type": "boolean",
|
||||
"type": [
|
||||
"boolean",
|
||||
"string"
|
||||
],
|
||||
"enum": [
|
||||
"html",
|
||||
"markdown"
|
||||
],
|
||||
"default": false,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description%"
|
||||
},
|
||||
@@ -1512,6 +1541,11 @@
|
||||
"default": "path",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description%"
|
||||
},
|
||||
"sameContentLocale": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description%"
|
||||
},
|
||||
"when": {
|
||||
"type": "object",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description%",
|
||||
@@ -1979,7 +2013,11 @@
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.slugTemplate": {
|
||||
"type": "string",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "%setting.frontMatter.taxonomy.slugTemplate.markdownDescription%",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
@@ -2005,7 +2043,7 @@
|
||||
},
|
||||
"frontMatter.templates.prefix": {
|
||||
"type": "string",
|
||||
"default": "yyyy-MM-dd",
|
||||
"default": "{{date|yyyy-MM-dd}}",
|
||||
"markdownDescription": "%setting.frontMatter.templates.prefix.markdownDescription%",
|
||||
"scope": "Templates"
|
||||
},
|
||||
@@ -2025,7 +2063,7 @@
|
||||
},
|
||||
"frontMatter.copilot.family": {
|
||||
"type": "string",
|
||||
"default": "gpt-3.5-turbo",
|
||||
"default": "gpt-4o-mini",
|
||||
"markdownDescription": "%setting.frontMatter.copilot.family.markdownDescription%"
|
||||
}
|
||||
}
|
||||
@@ -2352,6 +2390,15 @@
|
||||
"title": "%command.frontMatter.cache.clear%",
|
||||
"category": "Front Matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.createOrOpen",
|
||||
"title": "%command.frontMatter.i18n.createOrOpen%",
|
||||
"category": "Front Matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/i18n-light.svg",
|
||||
"dark": "assets/icons/i18n-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"title": "%command.frontMatter.i18n.create%",
|
||||
@@ -2402,7 +2449,7 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.i18n.create",
|
||||
"command": "frontMatter.i18n.createOrOpen",
|
||||
"group": "navigation@-127",
|
||||
"when": "frontMatter:file:isValid && frontMatter:i18n:enabled"
|
||||
},
|
||||
@@ -2779,6 +2826,7 @@
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@iarna/toml": "2.2.3",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -2795,11 +2843,10 @@
|
||||
"@types/react-datepicker": "^4.8.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@types/webpack-bundle-analyzer": "^4.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
"@vscode/l10n": "^0.0.14",
|
||||
"@vscode/webview-ui-toolkit": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.7.0",
|
||||
"ajv": "^8.12.0",
|
||||
"array-move": "^4.0.0",
|
||||
@@ -2812,6 +2859,7 @@
|
||||
"dotenv": "^16.3.1",
|
||||
"downshift": "6.0.6",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-webpack-plugin": "^4.2.0",
|
||||
"fuse.js": "6.5.3",
|
||||
"github-directory-downloader": "^1.3.6",
|
||||
"glob": "^10.3.12",
|
||||
@@ -2846,7 +2894,14 @@
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
"simple-git": "^3.16.0",
|
||||
@@ -2856,24 +2911,24 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"unified": "^11.0.5",
|
||||
"uniforms": "^3.10.2",
|
||||
"uniforms-antd": "^3.10.2",
|
||||
"uniforms-bridge-json-schema": "^3.10.2",
|
||||
"uniforms-unstyled": "^3.10.2",
|
||||
"url-join-ts": "^1.0.5",
|
||||
"vscrui": "^0.1.0-beta.1094721",
|
||||
"wc-react": "github:estruyf/wc-react",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.7.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-ignore-dynamic-require": "^1.0.0",
|
||||
"webpack-manifest-plugin": "^5.0.0",
|
||||
"yaml": "^2.2.1",
|
||||
"yawn-yaml": "^1.5.0"
|
||||
},
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
"command.frontMatter.git.sync": "Sync",
|
||||
"command.frontMatter.cache.clear": "Clear cache",
|
||||
"command.frontMatter.i18n.create": "Create new translation",
|
||||
"command.frontMatter.i18n.createOrOpen": "Create or open translation",
|
||||
"settings.configuration.title": "Front Matter: use frontmatter.json for shared team settings",
|
||||
"setting.frontMatter.projects.markdownDescription": "Specify the list of projects to load in the Front Matter CMS. [Local](https://file%2B.vscode-resource.vscode-cdn.net/Users/eliostruyf/nodejs/frontmatter-test-projects/astro-blog/test.html) - [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.projects) - [View in VS Code](vscode://simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.projects%22%5D)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Specify the name of the project.",
|
||||
@@ -73,6 +74,7 @@
|
||||
"setting.frontMatter.content.pageFolders.items.properties.title.description": "Name of the folder",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.path.description": "Path of the folder",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description": "Exclude sub-directories",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.excludePaths.description": "Exclude paths (e.g. api, _*.*)",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.previewPath.description": "Defines a custom preview path for the folder.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "Defines a prefix for the file name.",
|
||||
@@ -145,6 +147,8 @@
|
||||
"setting.frontMatter.data.folders.items.properties.path.description": "Path to the folder to load files.",
|
||||
"setting.frontMatter.data.folders.items.properties.type.description": "If you are using data types, you can specify your type ID.",
|
||||
"setting.frontMatter.data.folders.items.properties.singleEntry.description": "If you want to use a single entry for your data files in the folder.",
|
||||
"setting.frontMatter.data.folders.items.properties.enableFileCreation.description": "Enable the creation of new data files in the folder.",
|
||||
"setting.frontMatter.data.folders.items.properties.fileType.description": "Defines the file type for when the file creation is enabled. JSON is the default.",
|
||||
"setting.frontMatter.data.types.markdownDescription": "Specify the data types. These types can be used in for your data files. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.data.types) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.data.types%22%5D)",
|
||||
"setting.frontMatter.data.types.items.properties.id.description": "Your unique ID you want to use for your data type.",
|
||||
"setting.frontMatter.file.preserveCasing.markdownDescription": "Specify if you want to preserve the casing of your file names from the title. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.file.preservecasing) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.file.preservecasing%22%5D)",
|
||||
@@ -200,7 +204,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "The choice ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "The choice title",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "Is a single line field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field (HTML output)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field. You can set it to markdown or HTML.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "Do you allow to select multiple values?",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "Specify if the image field can be used as preview. Be aware, you can only have one preview image per content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "Do you want to hide the field from the metadata section?",
|
||||
@@ -226,6 +230,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.required.description": "Specify if the field is required",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeName.description": "Specify the content type name to filter content for the contentRelationship field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description": "Specify the value to insert for the contentRelationship field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description": "Specify if you only want to show the content with the same locale",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description": "Specify the conditions to show the field",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.fieldRef.description": "The field ID to use",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.operator.description": "The operator to use",
|
||||
|
||||
@@ -38,6 +38,8 @@ fs.writeFileSync(path.join(__dirname, '../README.md'), readme);
|
||||
|
||||
// Update the .vscodeignore file
|
||||
const ignoreFilePath = path.join(path.resolve('.'), '.vscodeignore');
|
||||
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
|
||||
vscodeignore = vscodeignore.replace(`**/*.map`, '');
|
||||
fs.writeFileSync(ignoreFilePath, vscodeignore);
|
||||
if (fs.existsSync(ignoreFilePath)) {
|
||||
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
|
||||
vscodeignore = vscodeignore.replace(`**/*.map`, '');
|
||||
fs.writeFileSync(ignoreFilePath, vscodeignore);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export class Article {
|
||||
if (article?.data) {
|
||||
const slug = SlugHelper.createSlug(title, article?.data, slugTemplate);
|
||||
|
||||
if (slug) {
|
||||
if (typeof slug === 'string') {
|
||||
return {
|
||||
slug,
|
||||
slugWithPrefixAndSuffix: `${prefix}${slug}${suffix}`
|
||||
@@ -202,19 +202,27 @@ export class Article {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = getTitleField();
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const articleDate = await ArticleHelper.getDate(article);
|
||||
|
||||
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
filePrefix = await ArticleHelper.getFilePrefix(
|
||||
filePrefix,
|
||||
editor.document.uri.fsPath,
|
||||
contentType
|
||||
contentType,
|
||||
articleTitle,
|
||||
articleDate
|
||||
);
|
||||
|
||||
const titleField = getTitleField();
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const slugInfo = Article.generateSlug(articleTitle, article, contentType.slugTemplate);
|
||||
|
||||
if (slugInfo && slugInfo.slug && slugInfo.slugWithPrefixAndSuffix) {
|
||||
if (
|
||||
slugInfo &&
|
||||
typeof slugInfo.slug === 'string' &&
|
||||
typeof slugInfo.slugWithPrefixAndSuffix === 'string'
|
||||
) {
|
||||
article.data['slug'] = slugInfo.slugWithPrefixAndSuffix;
|
||||
|
||||
if (contentType) {
|
||||
@@ -264,7 +272,11 @@ export class Article {
|
||||
|
||||
let newFileName = `${slugName}${ext}`;
|
||||
if (filePrefix && typeof filePrefix === 'string') {
|
||||
newFileName = `${filePrefix}-${newFileName}`;
|
||||
if (filePrefix.endsWith('/')) {
|
||||
newFileName = `${filePrefix}${newFileName}`;
|
||||
} else {
|
||||
newFileName = `${filePrefix}-${newFileName}`;
|
||||
}
|
||||
}
|
||||
|
||||
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
|
||||
@@ -363,7 +375,7 @@ export class Article {
|
||||
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
|
||||
|
||||
// Is article located in one of the content folders
|
||||
const folders = Folders.getCached();
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
const documentPath = parseWinPath(document.fileName);
|
||||
const folder = folders.find((f) => documentPath.startsWith(f.path));
|
||||
if (!folder) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { getWebviewJsFiles } from '../utils';
|
||||
|
||||
export class Chatbot {
|
||||
/**
|
||||
@@ -32,31 +33,36 @@ export class Chatbot {
|
||||
|
||||
const cspSource = webView.webview.cspSource;
|
||||
|
||||
const fetchLocalization = async (requestId: string) => {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
};
|
||||
|
||||
webView.webview.onDidReceiveMessage(async (message) => {
|
||||
switch (message.command) {
|
||||
const { command, requestId, payload, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (message.data) {
|
||||
commands.executeCommand('vscode.open', message.data);
|
||||
if (payload || data) {
|
||||
commands.executeCommand('vscode.open', payload || data);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case GeneralCommands.toVSCode.getLocalization:
|
||||
const { requestId } = message;
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
fetchLocalization(requestId);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
@@ -66,7 +72,6 @@ export class Chatbot {
|
||||
const isProd = ext.isProductionMode;
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const extensionUri = ext.extensionPath;
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -82,13 +87,11 @@ export class Chatbot {
|
||||
}`
|
||||
];
|
||||
|
||||
let scriptUri = '';
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView.webview
|
||||
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView.webview);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
// By default, the chatbot is seen as experimental
|
||||
@@ -111,7 +114,11 @@ export class Chatbot {
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} style="width:100%;height:100%;margin:0;padding:0;"></div>
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`chatbot-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
@@ -30,7 +30,7 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { getExtensibilityScripts, ignoreMsgCommand } from '../utils';
|
||||
import { getExtensibilityScripts, getWebviewJsFiles, ignoreMsgCommand } from '../utils';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -274,18 +274,17 @@ export class Dashboard {
|
||||
* @param webView
|
||||
*/
|
||||
private static async getWebviewContent(webView: Webview, extensionPath: Uri): Promise<string> {
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
let scriptUri = '';
|
||||
const isProd = Extension.getInstance().isProductionMode;
|
||||
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView
|
||||
.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
@@ -351,7 +350,9 @@ export class Dashboard {
|
||||
})
|
||||
.join('')}
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
|
||||
@@ -39,7 +39,7 @@ import { Preview } from './Preview';
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
export class Folders {
|
||||
private static _folders: ContentFolder[] = [];
|
||||
private static _folders: ContentFolder[] | undefined = undefined;
|
||||
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
@@ -50,7 +50,7 @@ export class Folders {
|
||||
|
||||
public static clearCached() {
|
||||
Logger.verbose(`Folders:clearCached`);
|
||||
Folders._folders = [];
|
||||
Folders._folders = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,7 +327,7 @@ export class Folders {
|
||||
public static async get(): Promise<ContentFolder[]> {
|
||||
Logger.verbose('Folders:get:start');
|
||||
|
||||
if (Folders._folders.length > 0) {
|
||||
if (Folders._folders && Folders._folders.length > 0) {
|
||||
Logger.verbose('Folders:get:end - cached folders');
|
||||
return Folders._folders;
|
||||
}
|
||||
@@ -401,8 +401,8 @@ export class Folders {
|
||||
folder.locales && folder.locales.length > 0 ? folder.locales : i18nSettings;
|
||||
|
||||
let defaultLocale;
|
||||
let sourcePath = folderPath;
|
||||
let localeFolders: ContentFolder[] = [];
|
||||
const sourcePath = folderPath;
|
||||
const localeFolders: ContentFolder[] = [];
|
||||
|
||||
if (i18nConfig && i18nConfig.length > 0) {
|
||||
for (const i18n of i18nConfig) {
|
||||
@@ -452,10 +452,23 @@ export class Folders {
|
||||
* Get the cached folder settings
|
||||
* @returns {ContentFolder[]} - The cached folder settings
|
||||
*/
|
||||
public static getCached(): ContentFolder[] {
|
||||
public static getCached(): ContentFolder[] | undefined {
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cached content folders if available, otherwise fetches fresh content folders.
|
||||
*
|
||||
* @returns {Promise<ContentFolder[]>} A promise that resolves to an array of content folders.
|
||||
*/
|
||||
public static async getCachedOrFresh(): Promise<ContentFolder[]> {
|
||||
if (Folders._folders && Folders._folders.length > 0) {
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
return await Folders.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the folder settings
|
||||
* @param folders
|
||||
@@ -557,6 +570,22 @@ export class Folders {
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given file path to a workspace-relative path.
|
||||
*
|
||||
* @param path - The file path to convert.
|
||||
* @returns The workspace-relative path.
|
||||
*/
|
||||
public static wsPath(path: string) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let absPath = parseWinPath(path).replace(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
WORKSPACE_PLACEHOLDER
|
||||
);
|
||||
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate relative folder path
|
||||
* @param folder
|
||||
@@ -672,7 +701,7 @@ export class Folders {
|
||||
public static async getPageFolderByFilePath(
|
||||
filePath: string
|
||||
): Promise<ContentFolder | undefined> {
|
||||
const folders = Folders.getCached();
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
const parsedPath = parseWinPath(filePath);
|
||||
const pageFolderMatches = folders
|
||||
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
|
||||
@@ -703,7 +732,7 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = Folders.getCached();
|
||||
const folders = await Folders.getCachedOrFresh();
|
||||
let selectedFolder: ContentFolder | undefined;
|
||||
|
||||
// Try to find the folder by content type
|
||||
@@ -772,7 +801,11 @@ export class Folders {
|
||||
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
|
||||
}
|
||||
|
||||
let foundFiles = await Folders.findFiles(filePath);
|
||||
let foundFiles = await Folders.findFiles(
|
||||
filePath,
|
||||
join(folderPath, folder.excludeSubdir ? '/' : '**'),
|
||||
folder.excludePaths
|
||||
);
|
||||
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) =>
|
||||
@@ -860,12 +893,27 @@ export class Folders {
|
||||
* @param pattern
|
||||
* @returns
|
||||
*/
|
||||
private static async findFiles(pattern: string): Promise<Uri[]> {
|
||||
private static async findFiles(
|
||||
pattern: string,
|
||||
folderPath: string,
|
||||
excludePaths: string[] = []
|
||||
): Promise<Uri[]> {
|
||||
Logger.verbose(`Folders:findFiles:start - ${pattern}`);
|
||||
|
||||
try {
|
||||
pattern = isWindows() ? parseWinPath(pattern) : pattern;
|
||||
const files = await glob(pattern, { ignore: 'node_modules/**', dot: true });
|
||||
const files = await glob(pattern, {
|
||||
ignore: [
|
||||
'node_modules/**',
|
||||
...excludePaths.map((path) => {
|
||||
// path can be a folder name or a wildcard.
|
||||
// If its a folder name, we need to add a wildcard to the end
|
||||
path = path.includes('*') ? path : join(path, '**');
|
||||
return parseWinPath(join(folderPath, path));
|
||||
})
|
||||
],
|
||||
dot: true
|
||||
});
|
||||
const allFiles = (files || []).map((file) => Uri.file(file));
|
||||
Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`);
|
||||
return allFiles;
|
||||
|
||||
@@ -29,7 +29,7 @@ import { ParsedFrontMatter } from '../parsers';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { getTitleField, joinUrl } from '../utils';
|
||||
import { getTitleField, getWebviewJsFiles, joinUrl } from '../utils';
|
||||
import { i18n } from './i18n';
|
||||
|
||||
export class Preview {
|
||||
@@ -110,31 +110,36 @@ export class Preview {
|
||||
webView.dispose();
|
||||
});
|
||||
|
||||
const fetchLocalization = async (requestId: string) => {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
};
|
||||
|
||||
webView.webview.onDidReceiveMessage(async (message) => {
|
||||
switch (message.command) {
|
||||
const { command, payload, requestId } = message;
|
||||
|
||||
switch (command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (message.payload) {
|
||||
commands.executeCommand('vscode.open', message.payload);
|
||||
if (payload) {
|
||||
commands.executeCommand('vscode.open', payload);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case GeneralCommands.toVSCode.getLocalization:
|
||||
const { requestId } = message;
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
return;
|
||||
fetchLocalization(requestId);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardFile = 'dashboardWebView.js';
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
@@ -144,7 +149,6 @@ export class Preview {
|
||||
const isProd = ext.isProductionMode;
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const extensionUri = ext.extensionPath;
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -161,13 +165,11 @@ export class Preview {
|
||||
`frame-src ${localhostUrl} ${cspSource} http: https:;`
|
||||
];
|
||||
|
||||
let scriptUri = '';
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUri = webView.webview
|
||||
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
|
||||
.toString();
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView.webview);
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
// Get experimental setting
|
||||
@@ -193,7 +195,11 @@ export class Preview {
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} style="width:100%;height:100%;margin:0;padding:0;"></div>
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`preview-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
@@ -310,7 +316,7 @@ export class Preview {
|
||||
|
||||
try {
|
||||
const articleDate = await ArticleHelper.getDate(article);
|
||||
pathname = processDateTimePlaceholders(pathname, dateFormat, articleDate);
|
||||
pathname = processDateTimePlaceholders(pathname, articleDate);
|
||||
slug = join(pathname, slug);
|
||||
} catch (error) {
|
||||
slug = join(pathname, slug);
|
||||
|
||||
@@ -42,7 +42,7 @@ categories: []
|
||||
|
||||
// Initialize command
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
|
||||
commands.registerCommand(COMMAND_NAME.init, async (cb: () => void) => {
|
||||
await Project.init();
|
||||
|
||||
if (cb) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { commands, window, Selection, QuickPickItem, TextEditor } from 'vscode';
|
||||
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { LocalizationKey, localize } from '../localization';
|
||||
|
||||
enum MarkupType {
|
||||
bold = 1,
|
||||
@@ -18,6 +17,8 @@ enum MarkupType {
|
||||
hyperlink
|
||||
}
|
||||
|
||||
type DocType = 'markdown' | 'asciidoc';
|
||||
|
||||
export class Wysiwyg {
|
||||
/**
|
||||
* Registers the markup commands for the WYSIWYG controls
|
||||
@@ -83,45 +84,45 @@ export class Wysiwyg {
|
||||
commands.registerCommand(COMMAND_NAME.options, async () => {
|
||||
const qpItems: QuickPickItem[] = [
|
||||
{
|
||||
label: `$(list-unordered) ${LocalizationKey.commandsWysiwygCommandUnorderedListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandUnorderedListDetail,
|
||||
label: `$(list-unordered) ${localize(LocalizationKey.commandsWysiwygCommandUnorderedListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandUnorderedListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(list-ordered) ${LocalizationKey.commandsWysiwygCommandOrderedListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandOrderedListDetail,
|
||||
label: `$(list-ordered) ${localize(LocalizationKey.commandsWysiwygCommandOrderedListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandOrderedListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(tasklist) ${LocalizationKey.commandsWysiwygCommandTaskListLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandTaskListDetail,
|
||||
label: `$(tasklist) ${localize(LocalizationKey.commandsWysiwygCommandTaskListLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandTaskListDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(code) ${LocalizationKey.commandsWysiwygCommandCodeLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandCodeDetail,
|
||||
label: `$(code) ${localize(LocalizationKey.commandsWysiwygCommandCodeLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandCodeDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(symbol-namespace) ${LocalizationKey.commandsWysiwygCommandCodeblockLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandCodeblockDetail,
|
||||
label: `$(symbol-namespace) ${localize(LocalizationKey.commandsWysiwygCommandCodeblockLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandCodeblockDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(quote) ${LocalizationKey.commandsWysiwygCommandBlockquoteLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandBlockquoteDetail,
|
||||
label: `$(quote) ${localize(LocalizationKey.commandsWysiwygCommandBlockquoteLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandBlockquoteDetail),
|
||||
alwaysShow: true
|
||||
},
|
||||
{
|
||||
label: `$(symbol-text) ${LocalizationKey.commandsWysiwygCommandStrikethroughLabel}`,
|
||||
detail: LocalizationKey.commandsWysiwygCommandStrikethroughDetail,
|
||||
label: `$(symbol-text) ${localize(LocalizationKey.commandsWysiwygCommandStrikethroughLabel)}`,
|
||||
detail: localize(LocalizationKey.commandsWysiwygCommandStrikethroughDetail),
|
||||
alwaysShow: true
|
||||
}
|
||||
];
|
||||
|
||||
const option = await window.showQuickPick([...qpItems], {
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygQuickPickTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygQuickPickPlaceholder),
|
||||
title: localize(LocalizationKey.commandsWysiwygQuickPickTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygQuickPickPlaceholder),
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
@@ -147,6 +148,15 @@ export class Wysiwyg {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the document type based on the file extension.
|
||||
* @param filePath - The path of the file.
|
||||
* @returns The document type ('asciidoc' or 'markdown').
|
||||
*/
|
||||
public static getDocType(filePath: string): DocType {
|
||||
return filePath.endsWith('.adoc') ? 'asciidoc' : 'markdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the markup to the content
|
||||
* @param type
|
||||
@@ -161,11 +171,12 @@ export class Wysiwyg {
|
||||
const selection = editor.selection;
|
||||
const hasTextSelection = !selection.isEmpty;
|
||||
|
||||
const docType: DocType = Wysiwyg.getDocType(editor.document.fileName);
|
||||
if (type === MarkupType.hyperlink) {
|
||||
return this.addHyperlink(editor, selection);
|
||||
return this.addHyperlink(editor, selection, docType);
|
||||
}
|
||||
|
||||
const markers = this.getMarkers(type);
|
||||
const markers = this.getMarkers(type, docType);
|
||||
if (!markers) {
|
||||
return;
|
||||
}
|
||||
@@ -175,13 +186,13 @@ export class Wysiwyg {
|
||||
if (hasTextSelection) {
|
||||
// Replace the selection and surround with the markup
|
||||
const selectionText = editor.document.getText(selection);
|
||||
const txt = await this.insertText(markers, type, selectionText);
|
||||
const txt = await this.insertText(markers, type, selectionText, docType);
|
||||
|
||||
editor.edit((builder) => {
|
||||
builder.replace(selection, txt);
|
||||
});
|
||||
} else {
|
||||
const txt = await this.insertText(markers, type);
|
||||
const txt = await this.insertText(markers, type, null, docType);
|
||||
|
||||
// Insert the markers where cursor is located.
|
||||
const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length;
|
||||
@@ -198,6 +209,10 @@ export class Wysiwyg {
|
||||
newPosition = crntSelection.with(crntSelection.line + 1, 0);
|
||||
}
|
||||
|
||||
if (type === MarkupType.blockquote && docType === 'asciidoc') {
|
||||
newPosition = crntSelection.with(crntSelection.line + 1, 0);
|
||||
}
|
||||
|
||||
editor.selection = new Selection(newPosition, newPosition);
|
||||
}
|
||||
}
|
||||
@@ -206,28 +221,39 @@ export class Wysiwyg {
|
||||
* Add a hyperlink to the content
|
||||
* @returns void
|
||||
*/
|
||||
private static async addHyperlink(editor: TextEditor, selection: Selection) {
|
||||
private static async addHyperlink(
|
||||
editor: TextEditor,
|
||||
selection: Selection,
|
||||
docType: DocType = 'markdown'
|
||||
) {
|
||||
const hasTextSelection = !selection.isEmpty;
|
||||
const linkText = hasTextSelection ? editor.document.getText(selection) : '';
|
||||
|
||||
const link = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
title: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
prompt: localize(LocalizationKey.commandsWysiwygAddHyperlinkHyperlinkInputPrompt),
|
||||
value: linkText,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
const text = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
title: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputTitle),
|
||||
prompt: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygAddHyperlinkTextInputPrompt),
|
||||
value: linkText,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (link) {
|
||||
const txt = `[${text || link}](${link})`;
|
||||
let txt = `[${text || link}](${link})`;
|
||||
|
||||
if (docType === 'asciidoc') {
|
||||
txt = !link.startsWith('http') ? `link:${link}` : link;
|
||||
if (text) {
|
||||
txt = `${txt}[${text}]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTextSelection) {
|
||||
editor.edit((builder) => {
|
||||
@@ -255,14 +281,23 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static isMarkupWrapping(type: MarkupType) {
|
||||
return (
|
||||
type === MarkupType.blockquote ||
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
private static isMarkupWrapping(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (docType === 'markdown') {
|
||||
return (
|
||||
type === MarkupType.blockquote ||
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
} else if (docType === 'asciidoc') {
|
||||
return (
|
||||
type === MarkupType.heading ||
|
||||
type === MarkupType.unorderedList ||
|
||||
type === MarkupType.orderedList ||
|
||||
type === MarkupType.taskList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,17 +306,18 @@ export class Wysiwyg {
|
||||
private static async insertText(
|
||||
marker: string | undefined,
|
||||
type: MarkupType,
|
||||
text: string | null = null
|
||||
text: string | null = null,
|
||||
docType: DocType = 'markdown'
|
||||
) {
|
||||
const crntText = text || this.lineBreak(type);
|
||||
const crntText = text || this.lineBreak(type, docType);
|
||||
|
||||
if (this.isMarkupWrapping(type)) {
|
||||
if (this.isMarkupWrapping(type, docType)) {
|
||||
if (type === MarkupType.heading) {
|
||||
const headingLvl = await window.showQuickPick(
|
||||
['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'],
|
||||
{
|
||||
title: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsWysiwygInsertTextHeadingInputPlaceholder),
|
||||
title: localize(LocalizationKey.commandsWysiwygInsertTextHeadingInputTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsWysiwygInsertTextHeadingInputPlaceholder),
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
@@ -298,9 +334,12 @@ export class Wysiwyg {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
if (type === MarkupType.orderedList) {
|
||||
if (type === MarkupType.orderedList && docType === 'markdown') {
|
||||
const lines = crntText.split('\n').map((line, idx) => `${idx + 1}. ${line}`);
|
||||
return lines.join('\n');
|
||||
} else if (type === MarkupType.orderedList && docType === 'asciidoc') {
|
||||
const lines = crntText.split('\n').map((line) => `${marker} ${line}`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
return `${marker} ${crntText}`;
|
||||
@@ -314,9 +353,11 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static lineBreak(type: MarkupType) {
|
||||
private static lineBreak(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (type === MarkupType.codeblock) {
|
||||
return `\n\n`;
|
||||
} else if (type === MarkupType.blockquote && docType === 'asciidoc') {
|
||||
return `\n\n`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -326,30 +367,57 @@ export class Wysiwyg {
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
private static getMarkers(type: MarkupType) {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `**`;
|
||||
case MarkupType.italic:
|
||||
return `*`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '```';
|
||||
case MarkupType.blockquote:
|
||||
return '>';
|
||||
case MarkupType.heading:
|
||||
return '#';
|
||||
case MarkupType.unorderedList:
|
||||
return '-';
|
||||
case MarkupType.orderedList:
|
||||
return '1.';
|
||||
case MarkupType.taskList:
|
||||
return '- [ ]';
|
||||
default:
|
||||
return;
|
||||
private static getMarkers(type: MarkupType, docType: DocType = 'markdown') {
|
||||
if (docType === 'markdown') {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `**`;
|
||||
case MarkupType.italic:
|
||||
return `*`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '```';
|
||||
case MarkupType.blockquote:
|
||||
return '>';
|
||||
case MarkupType.heading:
|
||||
return '#';
|
||||
case MarkupType.unorderedList:
|
||||
return '-';
|
||||
case MarkupType.orderedList:
|
||||
return '1.';
|
||||
case MarkupType.taskList:
|
||||
return '- [ ]';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else if (docType === 'asciidoc') {
|
||||
switch (type) {
|
||||
case MarkupType.bold:
|
||||
return `*`;
|
||||
case MarkupType.italic:
|
||||
return `_`;
|
||||
case MarkupType.strikethrough:
|
||||
return `~`;
|
||||
case MarkupType.code:
|
||||
return '`';
|
||||
case MarkupType.codeblock:
|
||||
return '----';
|
||||
case MarkupType.blockquote:
|
||||
return '____';
|
||||
case MarkupType.heading:
|
||||
return '=';
|
||||
case MarkupType.unorderedList:
|
||||
return '*';
|
||||
case MarkupType.orderedList:
|
||||
return '.';
|
||||
case MarkupType.taskList:
|
||||
return '* [ ]';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { ProgressLocation, Uri, commands, window, workspace } from 'vscode';
|
||||
import {
|
||||
ProgressLocation,
|
||||
QuickPickItem,
|
||||
QuickPickItemKind,
|
||||
QuickPickOptions,
|
||||
Uri,
|
||||
commands,
|
||||
window,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
ContentType,
|
||||
@@ -16,8 +25,7 @@ import { existsAsync, getDescriptionField, getTitleField } from '../utils';
|
||||
import { Folders } from '.';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { LocalizationKey, localize } from '../localization';
|
||||
import { Translations } from '../services/Translations';
|
||||
|
||||
export class i18n {
|
||||
@@ -32,6 +40,7 @@ export class i18n {
|
||||
const subscriptions = Extension.getInstance().subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.create, i18n.create));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.createOrOpen, i18n.createOrOpen));
|
||||
|
||||
i18n.clearFiles();
|
||||
}
|
||||
@@ -264,7 +273,7 @@ export class i18n {
|
||||
}
|
||||
|
||||
if (!fileUri) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,19 +283,19 @@ export class i18n {
|
||||
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
if (!pageFolder || !pageFolder.localeSourcePath) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
return;
|
||||
}
|
||||
|
||||
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
|
||||
if (!i18nSettings) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
|
||||
if (!sourceLocale || !sourceLocale.locale) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,15 +309,15 @@ export class i18n {
|
||||
});
|
||||
|
||||
if (targetLocales.length === 0) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
return;
|
||||
}
|
||||
|
||||
const locale = await window.showQuickPick(
|
||||
targetLocales.map((i18n) => i18n.title || i18n.locale),
|
||||
{
|
||||
title: l10n.t(LocalizationKey.commandsI18nCreateQuickPickTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
|
||||
title: localize(LocalizationKey.commandsI18nCreateQuickPickTitle),
|
||||
placeHolder: localize(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
|
||||
ignoreFocusOut: true
|
||||
}
|
||||
);
|
||||
@@ -321,19 +330,19 @@ export class i18n {
|
||||
(i18n) => i18n.title === locale || i18n.locale === locale
|
||||
);
|
||||
if (!targetLocale || !targetLocale.path) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
|
||||
if (!article) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +374,7 @@ export class i18n {
|
||||
|
||||
const newFilePath = join(i18nDir, fileInfo.base);
|
||||
if (await existsAsync(newFilePath)) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorFileExists));
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorFileExists));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -384,7 +393,188 @@ export class i18n {
|
||||
PagesListener.refresh();
|
||||
|
||||
Notifications.info(
|
||||
l10n.t(
|
||||
localize(
|
||||
LocalizationKey.commandsI18nCreateSuccessCreated,
|
||||
sourceLocale.title || sourceLocale.locale
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the process of creating a new translation file if it doesn't exist,
|
||||
* or opening an existing translation file if it's already present.
|
||||
* @param filePath The path of the file where the new content file should be created or being switched to. Behaves like `create` if not provided.
|
||||
*/
|
||||
private static async createOrOpen(fileUri?: Uri | string) {
|
||||
if (!fileUri) {
|
||||
const filePath = ArticleHelper.getActiveFile();
|
||||
fileUri = filePath ? Uri.file(filePath) : undefined;
|
||||
}
|
||||
|
||||
if (!fileUri) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof fileUri === 'string') {
|
||||
fileUri = Uri.file(fileUri);
|
||||
}
|
||||
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
if (!pageFolder || !pageFolder.localeSourcePath) {
|
||||
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
return;
|
||||
}
|
||||
|
||||
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
|
||||
if (!article) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
}
|
||||
|
||||
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
|
||||
if (!i18nSettings) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
|
||||
if (!sourceLocale || !sourceLocale.locale) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine translation file paths
|
||||
const fileInfo = parse(fileUri.fsPath);
|
||||
let pageBundleDir = '';
|
||||
if (await ArticleHelper.isPageBundle(fileUri.fsPath)) {
|
||||
const dir = ArticleHelper.getPageFolderFromBundlePath(fileUri.fsPath);
|
||||
pageBundleDir = fileUri.fsPath.replace(dir, '');
|
||||
pageBundleDir = join(parse(pageBundleDir).dir);
|
||||
}
|
||||
|
||||
// Gather target locales & metadata
|
||||
const translations = (await i18n.getTranslations(fileUri.fsPath)) || {};
|
||||
const targetLocales = i18nSettings
|
||||
.filter((i18n) => {
|
||||
return i18n.path && i18n.locale !== sourceLocale.locale;
|
||||
})
|
||||
.map((i18n) => {
|
||||
return {
|
||||
...i18n,
|
||||
dir: join(pageFolder.localeSourcePath!, i18n.path!, pageBundleDir),
|
||||
absolutePath: join(
|
||||
pageFolder.localeSourcePath!,
|
||||
i18n.path!,
|
||||
pageBundleDir,
|
||||
fileInfo.base
|
||||
),
|
||||
relativePath: join(i18n.path!, pageBundleDir, fileInfo.base)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => (a.title || a.locale).localeCompare(b.title || b.locale));
|
||||
|
||||
if (targetLocales.length === 0) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure quick pick items & options
|
||||
const existingTargetLocales = targetLocales.filter((i18n) => translations[i18n.locale]);
|
||||
const newTargetLocales = targetLocales.filter((i18n) => !translations[i18n.locale]);
|
||||
const quickPickItems: QuickPickItem[] = [
|
||||
...(existingTargetLocales.length
|
||||
? [
|
||||
{
|
||||
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryExisting),
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...existingTargetLocales.map((i18n) => ({
|
||||
label: i18n.title || i18n.locale,
|
||||
detail: localize(
|
||||
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionOpen,
|
||||
i18n.relativePath
|
||||
)
|
||||
}))
|
||||
]
|
||||
: []),
|
||||
...(newTargetLocales.length
|
||||
? [
|
||||
{
|
||||
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryNew),
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...newTargetLocales.map((i18n) => ({
|
||||
label: i18n.title || i18n.locale,
|
||||
detail: `$(file-add) ${localize(
|
||||
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionCreate,
|
||||
i18n.relativePath
|
||||
)}`
|
||||
}))
|
||||
]
|
||||
: [])
|
||||
];
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
title: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickTitle),
|
||||
ignoreFocusOut: true,
|
||||
matchOnDetail: true
|
||||
};
|
||||
|
||||
const localeItem = await window.showQuickPick<QuickPickItem>(quickPickItems, quickPickOptions);
|
||||
const locale = localeItem?.label;
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetLocale = targetLocales.find(
|
||||
(i18n) => i18n.title === locale || i18n.locale === locale
|
||||
);
|
||||
if (!targetLocale || !targetLocale.path) {
|
||||
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
// If it exists, open the translation file
|
||||
if (await existsAsync(targetLocale.absolutePath)) {
|
||||
await openFileInEditor(targetLocale.absolutePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it doesn't exist, create the new translation file & update front matter
|
||||
if (!(await existsAsync(targetLocale.dir))) {
|
||||
await workspace.fs.createDirectory(Uri.file(targetLocale.dir));
|
||||
}
|
||||
|
||||
article = await i18n.updateFrontMatter(
|
||||
article,
|
||||
fileUri.fsPath,
|
||||
contentType,
|
||||
sourceLocale,
|
||||
targetLocale,
|
||||
targetLocale.dir
|
||||
);
|
||||
if (sourceLocale?.locale) {
|
||||
article = await i18n.translate(article, sourceLocale, targetLocale);
|
||||
}
|
||||
|
||||
const newFileUri = Uri.file(targetLocale.absolutePath);
|
||||
await workspace.fs.writeFile(
|
||||
newFileUri,
|
||||
Buffer.from(ArticleHelper.stringifyFrontMatter(article.content, article.data))
|
||||
);
|
||||
|
||||
await openFileInEditor(targetLocale.absolutePath);
|
||||
|
||||
PagesListener.refresh();
|
||||
|
||||
Notifications.info(
|
||||
localize(
|
||||
LocalizationKey.commandsI18nCreateSuccessCreated,
|
||||
sourceLocale.title || sourceLocale.locale
|
||||
)
|
||||
@@ -403,11 +593,11 @@ export class i18n {
|
||||
sourceLocale: I18nConfig,
|
||||
targetLocale: I18nConfig
|
||||
) {
|
||||
return new Promise<ParsedFrontMatter>(async (resolve) => {
|
||||
await window.withProgress(
|
||||
return new Promise<ParsedFrontMatter>((resolve) => {
|
||||
window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: l10n.t(LocalizationKey.commandsI18nTranslateProgressTitle),
|
||||
title: localize(LocalizationKey.commandsI18nTranslateProgressTitle),
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
|
||||
@@ -30,6 +30,7 @@ function Num({
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<input
|
||||
className='block w-full py-2 pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0'
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
max={max}
|
||||
|
||||
@@ -70,7 +70,8 @@ export const COMMAND_NAME = {
|
||||
|
||||
// i18n
|
||||
i18n: {
|
||||
create: getCommandName('i18n.create')
|
||||
create: getCommandName('i18n.create'),
|
||||
createOrOpen: getCommandName('i18n.createOrOpen')
|
||||
},
|
||||
|
||||
// Project
|
||||
|
||||
@@ -50,6 +50,7 @@ export enum DashboardMessage {
|
||||
// Data dashboard
|
||||
getDataEntries = 'getDataEntries',
|
||||
putDataEntries = 'putDataEntries',
|
||||
createDataFile = 'createDataFile',
|
||||
|
||||
// Snippets dashboard
|
||||
insertSnippet = 'insertSnippet',
|
||||
|
||||
@@ -54,7 +54,7 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view);
|
||||
}, [mode?.features]);
|
||||
|
||||
const checkDevMode = (retry: number = 0) => {
|
||||
const checkDevMode = (retry = 0) => {
|
||||
if (!window.fmExternal) {
|
||||
if (retry < 5) {
|
||||
setTimeout(() => checkDevMode(retry + 1), 150);
|
||||
|
||||
@@ -11,10 +11,11 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
|
||||
import { l10nJsonFormat } from '@vscode/l10n';
|
||||
|
||||
export interface IChatbotProps { }
|
||||
|
||||
export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.PropsWithChildren<IChatbotProps>) => {
|
||||
export const Chatbot: React.FunctionComponent<IChatbotProps> = () => {
|
||||
const { aiUrl } = useSettingsContext();
|
||||
const [company, setCompany] = React.useState<string | undefined>(undefined);
|
||||
const [chatId, setChatId] = React.useState<number | undefined>(undefined);
|
||||
@@ -27,7 +28,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
|
||||
const init = async () => {
|
||||
setLoading(true);
|
||||
messageHandler.request<any>(GeneralCommands.toVSCode.getLocalization).then((data) => {
|
||||
messageHandler.request<l10nJsonFormat>(GeneralCommands.toVSCode.getLocalization).then((data) => {
|
||||
if (data) {
|
||||
l10n.config({
|
||||
contents: data
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IButtonProps {
|
||||
secondary?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const Button: React.FunctionComponent<IButtonProps> = ({
|
||||
onClick,
|
||||
className,
|
||||
disabled,
|
||||
secondary,
|
||||
children
|
||||
}: React.PropsWithChildren<IButtonProps>) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`${className || ''
|
||||
} inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium focus:outline-none rounded disabled:opacity-50 ${secondary ?
|
||||
`bg-[var(--vscode-button-secondaryBackground)] text-[--vscode-button-secondaryForeground] hover:bg-[var(--vscode-button-secondaryHoverBackground)]` :
|
||||
`bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)] hover:bg-[var(--frontmatter-button-hoverBackground)]`
|
||||
}
|
||||
`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemSelectionProps {
|
||||
@@ -24,11 +24,8 @@ export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
|
||||
return (
|
||||
<div className={`${cssNames} group-hover:block`}>
|
||||
<VSCodeCheckbox
|
||||
style={{
|
||||
boxShadow: show ? "" : "0 0 3px var(--frontmatter-border-preserve)"
|
||||
}}
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
className={show ? "" : " shadow-[0_0_3px_var(--frontmatter-border-preserve)]"}
|
||||
onChange={() => {
|
||||
onMultiSelect(filePath);
|
||||
}}
|
||||
checked={selectedFiles.includes(filePath)} />
|
||||
|
||||
@@ -53,7 +53,7 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
};
|
||||
} catch (error) {
|
||||
setError((error as Error).message);
|
||||
return () => { };
|
||||
return () => void 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { useForm } from 'uniforms';
|
||||
import { Button } from '../Common/Button';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { SubmitField } from '../../../components/uniforms-frontmatter';
|
||||
import { Button } from 'vscrui';
|
||||
|
||||
export interface IDataFormControlsProps {
|
||||
model: any | null;
|
||||
@@ -21,8 +21,8 @@ export const DataFormControls: React.FunctionComponent<IDataFormControlsProps> =
|
||||
<SubmitField value={model ? `Update` : `Add`} />
|
||||
|
||||
<Button
|
||||
className="ml-4"
|
||||
secondary
|
||||
className="ml-4 !py-2"
|
||||
appearance="secondary"
|
||||
onClick={() => {
|
||||
if (onClear) {
|
||||
onClear();
|
||||
|
||||
@@ -5,34 +5,36 @@ import { SettingsSelector } from '../../state';
|
||||
import { DataForm } from './DataForm';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { DataFile } from '../../../models/DataFile';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { messageHandler, Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SponsorMsg } from '../Layout/SponsorMsg';
|
||||
import { EventData } from '@estruyf/vscode';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { Button } from '../Common/Button';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
import { EmptyView } from './EmptyView';
|
||||
import { Container } from './SortableContainer';
|
||||
import { SortableItem } from './SortableItem';
|
||||
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
|
||||
import { ChevronRightIcon, CircleStackIcon, EyeIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { DataType } from '../../../models/DataType';
|
||||
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
|
||||
import { NavigationItem } from '../Layout';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { DataFolder } from '../../../models';
|
||||
import { ActionsBarItem } from '../Header/ActionsBarItem';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
import { openFile } from '../../utils/MessageHandlers';
|
||||
import { Button } from 'vscrui';
|
||||
|
||||
export interface IDataViewProps { }
|
||||
|
||||
export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
_: React.PropsWithChildren<IDataViewProps>
|
||||
) => {
|
||||
export const DataView: React.FunctionComponent<IDataViewProps> = () => {
|
||||
const [selectedData, setSelectedData] = useState<DataFile | null>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
const [dataEntries, setDataEntries] = useState<any | any[] | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const setSchema = (dataFile: DataFile) => {
|
||||
@@ -64,14 +66,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(data: any) => {
|
||||
(data: unknown) => {
|
||||
if (selectedData?.singleEntry) {
|
||||
// Needs to add a single entry
|
||||
updateData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataClone: any[] = Object.assign([], dataEntries);
|
||||
const dataClone: unknown[] = Object.assign([], dataEntries);
|
||||
if (selectedIndex !== null && selectedIndex !== undefined) {
|
||||
dataClone[selectedIndex] = data;
|
||||
} else {
|
||||
@@ -110,11 +112,23 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
entries: data
|
||||
});
|
||||
|
||||
Messenger.send(DashboardMessage.showNotification, l10n.t(LocalizationKey.dashboardDataViewDataViewUpdateMessage));
|
||||
Messenger.send(DashboardMessage.showNotification, localize(LocalizationKey.dashboardDataViewDataViewUpdateMessage));
|
||||
},
|
||||
[selectedData]
|
||||
);
|
||||
|
||||
const createDataFile = (folder: DataFolder) => {
|
||||
setLoading(true);
|
||||
messageHandler.request<DataFile>(DashboardMessage.createDataFile, folder).then(dataFile => {
|
||||
if (dataFile) {
|
||||
setSchema(dataFile);
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch((_: any) => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
const dataEntry = useMemo(() => {
|
||||
if (selectedData?.singleEntry) {
|
||||
return dataEntries || {};
|
||||
@@ -123,12 +137,44 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
return dataEntries && selectedIndex !== null && selectedIndex !== undefined
|
||||
? dataEntries[selectedIndex]
|
||||
: null;
|
||||
}, [selectedData, , dataEntries, selectedIndex]);
|
||||
}, [selectedData, dataEntries, selectedIndex]);
|
||||
|
||||
// Retrieve the data files, check if they have a schema or ID, if not, they shouldn't be shown
|
||||
const dataFiles = useMemo(() => {
|
||||
return (settings?.dataFiles || [])
|
||||
.map((dataFile: DataFile) => {
|
||||
if (!dataFile.schema && !dataFile.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clonedFile = Object.assign({}, dataFile);
|
||||
|
||||
if (clonedFile.type) {
|
||||
const dataType = settings?.dataTypes?.find(
|
||||
(dataType: DataType) => dataType.id === clonedFile.type
|
||||
);
|
||||
if (!dataType) {
|
||||
return null;
|
||||
}
|
||||
clonedFile.schema = Object.assign({}, dataType.schema);
|
||||
}
|
||||
|
||||
return clonedFile;
|
||||
})
|
||||
.filter((d) => d !== null) as DataFile[];
|
||||
}, [settings?.dataFiles]);
|
||||
|
||||
const fileCreationFolders = useMemo(() => {
|
||||
return (settings?.dataFolders || [])
|
||||
.filter((folder) => folder.enableFileCreation);
|
||||
}, [settings?.dataFolders]);
|
||||
|
||||
const hasOnlyDataCreationFolders = useMemo(() => (!dataFiles || dataFiles.length === 0) && (fileCreationFolders && fileCreationFolders.length > 0), [dataFiles, fileCreationFolders]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsData));
|
||||
Messenger.send(DashboardMessage.setTitle, localize(LocalizationKey.dashboardHeaderTabsData));
|
||||
|
||||
Messenger.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: 'Data view loaded',
|
||||
@@ -140,49 +186,27 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Retrieve the data files, check if they have a schema or ID, if not, they shouldn't be shown
|
||||
const dataFiles = (settings?.dataFiles || [])
|
||||
.map((dataFile: DataFile) => {
|
||||
if (!dataFile.schema && !dataFile.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clonedFile = Object.assign({}, dataFile);
|
||||
|
||||
if (clonedFile.type) {
|
||||
const dataType = settings?.dataTypes?.find(
|
||||
(dataType: DataType) => dataType.id === clonedFile.type
|
||||
);
|
||||
if (!dataType) {
|
||||
return null;
|
||||
}
|
||||
clonedFile.schema = Object.assign({}, dataType.schema);
|
||||
}
|
||||
|
||||
return clonedFile;
|
||||
})
|
||||
.filter((d) => d !== null) as DataFile[];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto inset-y-0">
|
||||
<Header settings={settings} />
|
||||
|
||||
{dataFiles && dataFiles.length > 0 ? (
|
||||
{(dataFiles && dataFiles.length > 0) || (fileCreationFolders && fileCreationFolders.length > 0) ? (
|
||||
<div className={`relative w-full flex-grow mx-auto overflow-hidden`}>
|
||||
{
|
||||
!selectedData && (
|
||||
!selectedData && (dataFiles && dataFiles.length > 0) && (
|
||||
<div className={`flex w-64 flex-col absolute inset-y-0`}>
|
||||
<aside
|
||||
className={`flex flex-col flex-grow overflow-y-auto border-r py-6 px-4 overflow-auto border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewSelect)}
|
||||
</h2>
|
||||
|
||||
<nav className={`flex-1 py-4 -mx-4`}>
|
||||
<div
|
||||
className={`divide-y border-t border-b divide-[var(--frontmatter-border)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
|
||||
{dataFiles &&
|
||||
dataFiles.length > 0 &&
|
||||
dataFiles.map((dataFile, idx) => (
|
||||
@@ -208,30 +232,82 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100">
|
||||
<div className={`w-full px-4 py-2 border-b border-[var(--frontmatter-border)]`}>
|
||||
{selectedData && (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
|
||||
title={selectedData.title}
|
||||
/>
|
||||
<div className={`flex justify-between`}>
|
||||
<div className={`flex gap-4`}>
|
||||
{
|
||||
selectedData && (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(LocalizationKey.dashboardDataViewDataViewSelect)}
|
||||
title={selectedData.title}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{dataFiles.map((dataFile) => (
|
||||
<MenuItem
|
||||
key={dataFile.id}
|
||||
title={dataFile.title}
|
||||
value={dataFile}
|
||||
isCurrent={selectedData.id === dataFile.id}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<DropdownMenuContent>
|
||||
{dataFiles.map((dataFile) => (
|
||||
<MenuItem
|
||||
key={dataFile.id}
|
||||
title={dataFile.title}
|
||||
value={dataFile}
|
||||
isCurrent={selectedData.id === dataFile.id}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
fileCreationFolders && fileCreationFolders.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(LocalizationKey.dashboardDataViewDataViewCreateNew)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewSelectDataFolder)}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{fileCreationFolders.map((folder) => (
|
||||
<MenuItem
|
||||
key={folder.id}
|
||||
title={folder.id}
|
||||
value={folder}
|
||||
onClick={() => createDataFile(folder)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedData && (
|
||||
<div className={`flex gap-2`}>
|
||||
<ActionsBarItem
|
||||
className='flex items-center'
|
||||
onClick={() => openFile(selectedData.file)}
|
||||
title={localize(LocalizationKey.commonView)}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{localize(LocalizationKey.commonView)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
<ActionsBarItem
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedData(null)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewCloseSelectedDataFile)}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{localize(LocalizationKey.dashboardDataViewDataViewCloseSelectedDataFile)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : "pl-64"}`}>
|
||||
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : hasOnlyDataCreationFolders ? "" : "pl-64"}`}>
|
||||
{selectedData ? (
|
||||
<>
|
||||
{!selectedData.singleEntry && (
|
||||
@@ -239,7 +315,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
className={`w-1/3 py-6 px-4 flex-1 border-r overflow-auto border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewTitle, selectedData?.title?.toLowerCase() || '')}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewTitle, selectedData?.title?.toLowerCase() || '')}
|
||||
</h2>
|
||||
|
||||
<div className="py-4">
|
||||
@@ -258,14 +334,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
<Button className="mt-4" onClick={() => setSelectedIndex(null)}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewAdd)}
|
||||
<Button className="mt-4 !py-2" onClick={() => setSelectedIndex(null)}>
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewAdd)}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className={`flex flex-col items-center justify-center`}>
|
||||
<p className={`text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewEmpty, selectedData.title.toLowerCase())}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewEmpty, selectedData.title.toLowerCase())}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -277,7 +353,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
} py-6 px-4 overflow-auto`}
|
||||
>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewCreateOrModify, selectedData.title.toLowerCase())}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewCreateOrModify, selectedData.title.toLowerCase())}
|
||||
</h2>
|
||||
{selectedData ? (
|
||||
<DataForm
|
||||
@@ -287,12 +363,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
onClear={() => setSelectedIndex(null)}
|
||||
/>
|
||||
) : (
|
||||
<p>{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStarted)}</p>
|
||||
<p>{localize(LocalizationKey.dashboardDataViewDataViewGetStarted)}</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<EmptyView />
|
||||
<EmptyView
|
||||
folders={fileCreationFolders}
|
||||
onCreate={createDataFile} />
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
@@ -300,14 +378,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
|
||||
<CircleStackIcon className="w-32 h-32" />
|
||||
<p className="text-3xl mt-2">{l10n.t(LocalizationKey.dashboardDataViewDataViewNoDataFiles)}</p>
|
||||
<p className="text-3xl mt-2">{localize(LocalizationKey.dashboardDataViewDataViewNoDataFiles)}</p>
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
|
||||
href={WEBSITE_LINKS.docs.dataDashboard}
|
||||
title={l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
{localize(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -315,6 +393,8 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
)
|
||||
}
|
||||
|
||||
{loading && <Spinner />}
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
|
||||
@@ -1,20 +1,56 @@
|
||||
import { ExclamationCircleIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DataFolder } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
|
||||
export interface IEmptyViewProps { }
|
||||
export interface IEmptyViewProps {
|
||||
folders: DataFolder[];
|
||||
onCreate: (folder: DataFolder) => void;
|
||||
}
|
||||
|
||||
export const EmptyView: React.FunctionComponent<IEmptyViewProps> = (
|
||||
props: React.PropsWithChildren<IEmptyViewProps>
|
||||
{ folders, onCreate }: React.PropsWithChildren<IEmptyViewProps>
|
||||
) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full">
|
||||
<div className="flex flex-col items-center justify-center w-full space-y-2">
|
||||
<ExclamationCircleIcon className={`w-1/12 opacity-90 text-[var(--frontmatter-secondary-text)]`} />
|
||||
<h2 className={`text-xl text-[var(--frontmatter-secondary-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewEmptyViewHeading)}
|
||||
{
|
||||
(folders && folders.length > 0) ?
|
||||
localize(LocalizationKey.dashboardDataViewEmptyViewHeadingCreate) :
|
||||
l10n.t(LocalizationKey.dashboardDataViewEmptyViewHeading)
|
||||
}
|
||||
</h2>
|
||||
|
||||
{
|
||||
onCreate && folders && folders.length > 0 && (
|
||||
<div className=''>
|
||||
<DropdownMenu>
|
||||
<MenuButton
|
||||
label={localize(LocalizationKey.dashboardDataViewDataViewCreateNew)}
|
||||
title={localize(LocalizationKey.dashboardDataViewDataViewSelectDataFolder)}
|
||||
className={`text-lg`}
|
||||
labelClass={`font-normal text-[var(--frontmatter-secondary-text)]`}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{folders.map((folder) => (
|
||||
<MenuItem
|
||||
key={folder.id}
|
||||
title={folder.id}
|
||||
value={folder}
|
||||
onClick={() => onCreate(folder)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ILanguageFilterProps { }
|
||||
|
||||
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = ({ }: React.PropsWithChildren<ILanguageFilterProps>) => {
|
||||
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = () => {
|
||||
const locales = useRecoilValue(LocalesAtom);
|
||||
const [crntLocale, setCrntLocale] = useRecoilState(LocaleAtom);
|
||||
|
||||
|
||||
@@ -21,16 +21,16 @@ import { useEffect, useMemo } from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export const guardRecoilDefaultValue = (candidate: any): candidate is DefaultValue => {
|
||||
if (candidate instanceof DefaultValue) return true;
|
||||
export const guardRecoilDefaultValue = (candidate: unknown): candidate is DefaultValue => {
|
||||
if (candidate instanceof DefaultValue) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export interface IClearFiltersProps { }
|
||||
|
||||
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
_: React.PropsWithChildren<IClearFiltersProps>
|
||||
) => {
|
||||
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = () => {
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
@@ -75,7 +75,9 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
}
|
||||
}, [folder, tag, category, locale, hasCustomFilters]);
|
||||
|
||||
if (!show) return null;
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWi
|
||||
activeItem={crntFilters[filterName]}
|
||||
items={values}
|
||||
onClick={(value) => setCrntFilters((prev) => {
|
||||
let clone = Object.assign({}, prev);
|
||||
const clone = Object.assign({}, prev);
|
||||
if (!clone[filterName] && value) {
|
||||
clone[filterName] = value;
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface IFoldersFilterProps { }
|
||||
|
||||
export const FoldersFilter: React.FunctionComponent<
|
||||
IFoldersFilterProps
|
||||
> = ({ }: React.PropsWithChildren<IFoldersFilterProps>) => {
|
||||
> = () => {
|
||||
const DEFAULT_TYPE = l10n.t(LocalizationKey.dashboardHeaderFoldersDefault);
|
||||
const [crntFolder, setCrntFolder] = useRecoilState(FolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
@@ -11,12 +11,12 @@ export interface IGroupingProps { }
|
||||
|
||||
export const Grouping: React.FunctionComponent<
|
||||
IGroupingProps
|
||||
> = ({ }: React.PropsWithChildren<IGroupingProps>) => {
|
||||
> = () => {
|
||||
const [group, setGroup] = useRecoilState(GroupingAtom);
|
||||
const pages = useRecoilValue(AllPagesAtom);
|
||||
|
||||
const GROUP_OPTIONS = React.useMemo(() => {
|
||||
let options: { name: string, id: GroupOption }[] = [];
|
||||
const options: { name: string, id: GroupOption }[] = [];
|
||||
|
||||
if (pages.length > 0) {
|
||||
if (pages.some((x) => x.fmYear)) {
|
||||
|
||||
@@ -37,9 +37,7 @@ const NavigationItem: React.FunctionComponent<INavigationItemProps> = ({
|
||||
)
|
||||
};
|
||||
|
||||
export const Navigation: React.FunctionComponent<INavigationProps> = ({
|
||||
|
||||
}: React.PropsWithChildren<INavigationProps>) => {
|
||||
export const Navigation: React.FunctionComponent<INavigationProps> = () => {
|
||||
const [crntTab, setCrntTab] = useRecoilState(TabAtom);
|
||||
const tabInfo = useRecoilValue(TabInfoAtom);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
@@ -21,9 +21,7 @@ import { ArrowClockwiseIcon } from '../../../components/icons/ArrowClockwiseIcon
|
||||
|
||||
export interface IRefreshDashboardDataProps { }
|
||||
|
||||
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = (
|
||||
{ }: React.PropsWithChildren<IRefreshDashboardDataProps>
|
||||
) => {
|
||||
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = () => {
|
||||
const view = useRecoilValue(DashboardViewAtom);
|
||||
const [, setLoading] = useRecoilState(LoadingAtom);
|
||||
const resetSearch = useResetRecoilState(SearchAtom);
|
||||
|
||||
@@ -165,7 +165,7 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
let sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
|
||||
const sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
|
||||
setCrntSort(sort);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SETTING_DASHBOARD_OPENONSTART } from '../../../constants';
|
||||
import * as l10n from "@vscode/l10n"
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface IStartupProps {
|
||||
settings: Settings | null;
|
||||
@@ -31,7 +30,7 @@ export const Startup: React.FunctionComponent<IStartupProps> = ({
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.checked)}
|
||||
onChange={onChange}
|
||||
checked={isChecked}>
|
||||
{l10n.t(LocalizationKey.dashboardHeaderStartupLabel)}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
return [];
|
||||
}
|
||||
|
||||
let groupedFolders = [];
|
||||
const groupedFolders = [];
|
||||
|
||||
for (const cFolder of settings?.contentFolders || []) {
|
||||
const foldersPath = parseWinPath(cFolder.path);
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface IMediaHeaderTopProps { }
|
||||
|
||||
export const MediaHeaderTop: React.FunctionComponent<
|
||||
IMediaHeaderTopProps
|
||||
> = ({ }: React.PropsWithChildren<IMediaHeaderTopProps>) => {
|
||||
> = () => {
|
||||
const [lastUpdated, setLastUpdated] = React.useState<string | null>(null);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const crntSorting = useRecoilValue(SortingSelector);
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IMenuButtonProps {
|
||||
label: string | JSX.Element;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
labelClass?: string;
|
||||
buttonClass?: string;
|
||||
}
|
||||
|
||||
export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
label,
|
||||
title,
|
||||
disabled
|
||||
disabled,
|
||||
className,
|
||||
labelClass,
|
||||
buttonClass,
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>
|
||||
<div className={cn(`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''} ${className || ""}`)}>
|
||||
<div className={cn(`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)] ${labelClass || ""}`)}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
<DropdownMenuTrigger
|
||||
className='text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)] flex items-center focus:outline-none'
|
||||
className={cn(`text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)] flex items-center focus:outline-none ${buttonClass || ""}`)}
|
||||
disabled={disabled}>
|
||||
<span>{title}</span>
|
||||
<ChevronDownIcon className={`-mr-1 ml-1 h-4 w-4`} aria-hidden="true" />
|
||||
|
||||
@@ -20,7 +20,8 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
|
||||
|
||||
const onRefresh = () => {
|
||||
if (iframeRef.current?.src) {
|
||||
iframeRef.current.src = iframeRef.current.src;
|
||||
const url = iframeRef.current.src;
|
||||
iframeRef.current.src = url;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,10 +35,12 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
|
||||
navUrl = `https://${navUrl}`;
|
||||
setCrntUrl(navUrl);
|
||||
}
|
||||
iframeRef.current!.src = navUrl;
|
||||
if (iframeRef.current) {
|
||||
iframeRef.current.src = navUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const msgListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const msgListener = (message: MessageEvent<EventData<string>>) => {
|
||||
if (message.data.command === PreviewCommands.toWebview.updateUrl) {
|
||||
setCrntUrl(message.data.payload);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Startup } from '../Header/Startup';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { SettingsInput } from './SettingsInput';
|
||||
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Button as VSCodeButton } from 'vscrui';
|
||||
import { DOCS_SUBMODULES, FrameworkDetectors, GIT_CONFIG, SETTING_FRAMEWORK_START, SETTING_GIT_COMMIT_MSG, SETTING_GIT_ENABLED, SETTING_PREVIEW_HOST, SETTING_WEBSITE_URL } from '../../../constants';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
|
||||
@@ -4,11 +4,11 @@ import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { GeneralCommands, ExtensionState } from '../../../constants';
|
||||
import { SettingsInput } from './SettingsInput';
|
||||
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Button as VSCodeButton } from 'vscrui';
|
||||
|
||||
export interface IIntegrationsViewProps { }
|
||||
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = ({ }: React.PropsWithChildren<IIntegrationsViewProps>) => {
|
||||
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = () => {
|
||||
const [deeplApiKey, setDeeplApiKey] = React.useState<string>('');
|
||||
const [azureApiKey, setAzureApiKey] = React.useState<string>('');
|
||||
const [azureRegion, setAzureRegion] = React.useState<string>('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface ISettingsCheckboxProps {
|
||||
label: string;
|
||||
@@ -27,7 +27,7 @@ export const SettingsCheckbox: React.FunctionComponent<ISettingsCheckboxProps> =
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateValue(e.target.checked)}
|
||||
onChange={updateValue}
|
||||
checked={isEnabled}>
|
||||
{label}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { TextField as VSCodeTextField } from 'vscrui';
|
||||
|
||||
export interface ISettingsInputProps {
|
||||
label: string;
|
||||
@@ -21,13 +21,10 @@ export const SettingsInput: React.FunctionComponent<ISettingsInputProps> = ({
|
||||
|
||||
return (
|
||||
<VSCodeTextField
|
||||
className='w-full p-0 m-0 bg-inherit border-0 focus:border-0 outline-none focus:outline-none shadow-none'
|
||||
style={{
|
||||
boxShadow: 'none'
|
||||
}}
|
||||
className='w-full p-0 m-0 bg-inherit'
|
||||
value={value || fallback || ""}
|
||||
placeholder={placeholder}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) => onChange(name, e.target.value)}>
|
||||
onChange={(value: string) => onChange(name, value)}>
|
||||
{label}
|
||||
</VSCodeTextField>
|
||||
);
|
||||
|
||||
@@ -10,12 +10,12 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { COMMAND_NAME } from '../../../constants';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react';
|
||||
import { CommonSettings } from './CommonSettings';
|
||||
import { IntegrationsView } from './IntegrationsView';
|
||||
import { useEffect } from 'react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ITab, IView, Panels as VSCodePanels } from 'vscrui';
|
||||
|
||||
export interface ISettingsViewProps { }
|
||||
|
||||
@@ -23,6 +23,74 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const tabs: ITab[] = React.useMemo(() => {
|
||||
const temp = [
|
||||
{ id: "view-1", label: l10n.t(LocalizationKey.settingsViewCommon) },
|
||||
{ id: "view-2", label: l10n.t(LocalizationKey.settingsViewContentFolders) }
|
||||
];
|
||||
|
||||
if (settings?.crntFramework === 'astro') {
|
||||
temp.push({ id: "view-3", label: l10n.t(LocalizationKey.settingsViewAstro) });
|
||||
}
|
||||
|
||||
return [
|
||||
...temp,
|
||||
{ id: "view-4", label: l10n.t(LocalizationKey.settingsViewIntegration) }
|
||||
];
|
||||
}, [settings]);
|
||||
|
||||
const views: IView[] = React.useMemo(() => {
|
||||
if (!settings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const temp = [
|
||||
{
|
||||
id: "view-1",
|
||||
content: <CommonSettings />
|
||||
},
|
||||
{
|
||||
id: "view-2",
|
||||
content: (
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentFolders)}</h2>
|
||||
|
||||
<ContentFolders
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
if (settings?.crntFramework === 'astro') {
|
||||
temp.push({
|
||||
id: "view-3",
|
||||
content: (
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentTypes)}</h2>
|
||||
|
||||
<AstroContentTypes
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)}
|
||||
setStatus={_ => null} />
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
temp.push({
|
||||
id: "view-4",
|
||||
content: (
|
||||
<IntegrationsView />
|
||||
)
|
||||
});
|
||||
|
||||
return [
|
||||
...temp
|
||||
];
|
||||
}, [settings]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.commonSettings));
|
||||
}, []);
|
||||
@@ -52,51 +120,11 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<VSCodePanels className={`mt-4`}>
|
||||
<VSCodePanelTab id="view-1">{l10n.t(LocalizationKey.settingsViewCommon)}</VSCodePanelTab>
|
||||
<VSCodePanelTab id="view-2">{l10n.t(LocalizationKey.settingsViewContentFolders)}</VSCodePanelTab>
|
||||
|
||||
{
|
||||
settings?.crntFramework === 'astro' && (
|
||||
<VSCodePanelTab id="view-3">{l10n.t(LocalizationKey.settingsViewAstro)}</VSCodePanelTab>
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelTab id="view-4">{l10n.t(LocalizationKey.settingsViewIntegration)}</VSCodePanelTab>
|
||||
|
||||
<VSCodePanelView id="view-1">
|
||||
<CommonSettings />
|
||||
</VSCodePanelView>
|
||||
|
||||
<VSCodePanelView id="view-2">
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentFolders)}</h2>
|
||||
|
||||
<ContentFolders
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)} />
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
|
||||
{
|
||||
settings?.crntFramework === 'astro' && (
|
||||
<VSCodePanelView id="view-3">
|
||||
<div className='py-4'>
|
||||
<h2 className='text-xl mb-2'>{l10n.t(LocalizationKey.settingsContentTypes)}</h2>
|
||||
|
||||
<AstroContentTypes
|
||||
settings={settings}
|
||||
triggerLoading={(isLoading) => setLoading(isLoading)}
|
||||
setStatus={_ => null} />
|
||||
</div>
|
||||
</VSCodePanelView>
|
||||
)
|
||||
}
|
||||
|
||||
<VSCodePanelView id="view-4">
|
||||
<IntegrationsView />
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
<VSCodePanels
|
||||
className={`mt-4`}
|
||||
tabs={tabs}
|
||||
views={views}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
let snippets: Snippets = Object.assign({}, settings?.snippets || {});
|
||||
const snippets: Snippets = Object.assign({}, settings?.snippets || {});
|
||||
const snippetLines = snippetOriginalBody.split('\n');
|
||||
|
||||
const crntSnippet = Object.assign({}, snippets[snippetKey]);
|
||||
|
||||
@@ -79,11 +79,11 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
|
||||
);
|
||||
|
||||
const snippetBody = useMemo(() => {
|
||||
let body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
||||
const body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
||||
|
||||
const obj: any = {};
|
||||
const obj: { [key: string]: string } = {};
|
||||
for (const field of fields) {
|
||||
obj[field.name] = field.value;
|
||||
obj[field.name] = field.value as string;
|
||||
}
|
||||
|
||||
return SnippetParser.render(body, obj, snippet.openingTags, snippet.closingTags);
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
<div className="relative">
|
||||
<select
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
|
||||
style={{
|
||||
boxShadow: "none"
|
||||
@@ -69,7 +69,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
return (
|
||||
<TextField
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
description={field.description}
|
||||
onChange={(e) => onValueChange(field, e)}
|
||||
rows={4}
|
||||
@@ -81,7 +81,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
return (
|
||||
<TextField
|
||||
name={field.name}
|
||||
value={field.value || ''}
|
||||
value={field.value as string || ''}
|
||||
description={field.description}
|
||||
onChange={(e) => onValueChange(field, e)}
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TemplateItem } from './TemplateItem';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
|
||||
import { ContentFolders } from '../Configuration/Common/ContentFolders';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IStepsToGetStartedProps {
|
||||
@@ -239,7 +239,7 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
|
||||
description: (
|
||||
<div className='mt-1'>
|
||||
<VSCodeCheckbox
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateSetting(SETTING_GIT_ENABLED, e.target.checked)}
|
||||
onChange={(value) => updateSetting(SETTING_GIT_ENABLED, value)}
|
||||
checked={isGitEnabled}>
|
||||
{l10n.t(LocalizationKey.dashboardStepsStepsToGetStartedGitDescription)}
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TaxonomyLookup: React.FunctionComponent<ITaxonomyLookupProps> = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
return fieldName && page[fieldName] ? page[fieldName].includes(value) : false;
|
||||
}).length;
|
||||
|
||||
@@ -80,7 +80,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
}, [data, taxonomy, debounceFilterValue]);
|
||||
|
||||
const unmappedItems = useMemo(() => {
|
||||
let unmapped: string[] = [];
|
||||
const unmapped: string[] = [];
|
||||
|
||||
if (!pages || !settings?.contentTypes || !taxonomy) {
|
||||
return unmapped;
|
||||
@@ -100,7 +100,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
|
||||
return false;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
if (fieldName && page[fieldName]) {
|
||||
values = page[fieldName];
|
||||
|
||||
@@ -5,18 +5,18 @@ import { SettingsSelector } from '../../state';
|
||||
import { getTaxonomyField } from '../../../helpers/getTaxonomyField';
|
||||
import { Sorting } from '../../../helpers/Sorting';
|
||||
import { ArrowLeftIcon, EyeIcon } from '@heroicons/react/24/outline';
|
||||
import { Button } from '../Common/Button';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { Button } from 'vscrui';
|
||||
import { FilterInput } from './FilterInput';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { sortPages } from '../../../utils/sortPages';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ExtensionState } from '../../../constants';
|
||||
import { LinkButton } from '../Common/LinkButton';
|
||||
import { openFile } from '../../utils';
|
||||
import { Checkbox as VSCodeCheckbox } from 'vscrui';
|
||||
|
||||
export interface ITaxonomyTaggingProps {
|
||||
taxonomy: string | null;
|
||||
@@ -70,7 +70,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
continue;
|
||||
}
|
||||
|
||||
let fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
const fieldName = getTaxonomyField(taxonomy, contentType);
|
||||
|
||||
if (fieldName && (!page[fieldName] || page[fieldName].indexOf(value) === -1)) {
|
||||
untagged.push(page);
|
||||
@@ -78,7 +78,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
}
|
||||
}
|
||||
|
||||
untagged = untagged.sort(Sorting.number('fmPublished')).reverse();
|
||||
untagged = untagged.sort(Sorting.numerically('fmPublished')).reverse();
|
||||
|
||||
if (debounceFilterValue) {
|
||||
return untagged.filter((p) => p.title.toLowerCase().includes(debounceFilterValue.toLowerCase()));
|
||||
@@ -122,8 +122,12 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
}, [pageMappings, untaggedPages]);
|
||||
|
||||
const checkIfChecked = React.useCallback((page: Page) => {
|
||||
return (!untaggedPages.find((p: Page) => p.fmFilePath === page.fmFilePath) && !pageMappings.untagged.find((p: Page) => p.fmFilePath === page.fmFilePath)) || pageMappings.tagged.find((p: Page) => p.fmFilePath === page.fmFilePath);
|
||||
}, [untaggedPages, pageMappings.tagged]);
|
||||
const isUntagged = untaggedPages.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
const isTagged = pageMappings.tagged.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
const isInUntagged = pageMappings.untagged.some((p) => p.fmFilePath === page.fmFilePath);
|
||||
|
||||
return (!isUntagged && !isInUntagged) || isTagged;
|
||||
}, [untaggedPages, pageMappings.tagged, pageMappings.untagged]);
|
||||
|
||||
const onFileView = (filePath: string) => {
|
||||
openFile(filePath);
|
||||
@@ -193,7 +197,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
<td className={`pl-6 py-2 w-[25px]`}>
|
||||
<VSCodeCheckbox
|
||||
title={l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
|
||||
onClick={() => onCheckboxClick(page)}
|
||||
onChange={() => onCheckboxClick(page)}
|
||||
checked={checkIfChecked(page)}>
|
||||
<span className='sr-only'>
|
||||
{l10n.t(LocalizationKey.dashboardTaxonomyViewTaxonomyTaggingCheckbox, value)}
|
||||
@@ -225,8 +229,8 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end space-x-2'>
|
||||
<Button onClick={onDismiss} secondary>{l10n.t(LocalizationKey.commonCancel)}</Button>
|
||||
<Button onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
|
||||
<Button className='!py-2' onClick={onDismiss} appearance='secondary'>{l10n.t(LocalizationKey.commonCancel)}</Button>
|
||||
<Button className='!py-2' onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function useMediaInfo(media?: MediaInfo) {
|
||||
}, [media]);
|
||||
|
||||
const mediaDetails = useMemo(() => {
|
||||
let sizeDetails = [];
|
||||
const sizeDetails = [];
|
||||
|
||||
if (mediaDimensions) {
|
||||
sizeDetails.push(mediaDimensions);
|
||||
|
||||
@@ -159,8 +159,6 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
if (tab !== Tab.All) {
|
||||
crntPages = crntPages.filter((page) => page.fmDraft === tab);
|
||||
} else {
|
||||
crntPages = crntPages;
|
||||
}
|
||||
} else {
|
||||
// Draft field is a boolean field
|
||||
@@ -194,8 +192,6 @@ export default function usePages(pages: Page[]) {
|
||||
crntPages = drafts;
|
||||
} else if (tab === Tab.Scheduled) {
|
||||
crntPages = scheduled;
|
||||
} else {
|
||||
crntPages = crntPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,11 +236,11 @@ export default function usePages(pages: Page[]) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let usedSorting = sorting;
|
||||
const usedSorting = sorting;
|
||||
|
||||
const startPageProcessing = () => {
|
||||
// Check if search needs to be performed
|
||||
let searchedPages = pages;
|
||||
const searchedPages = pages;
|
||||
if (search) {
|
||||
Messenger.send(DashboardMessage.searchPages, { query: search });
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { I10nProvider } from './providers/I10nProvider';
|
||||
import { SentryInit } from '../utils/sentryInit';
|
||||
import { WEBSITE_LINKS } from '../constants';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
setState: (data: T) => void;
|
||||
@@ -119,4 +120,8 @@ if (elm) {
|
||||
}
|
||||
|
||||
// Webpack HMR
|
||||
if ((module as any).hot) (module as any).hot.accept();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((module as any).hot) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(module as any).hot.accept();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ContentType,
|
||||
CustomScript,
|
||||
CustomTaxonomy,
|
||||
DataFolder,
|
||||
DraftField,
|
||||
FilterType,
|
||||
Framework,
|
||||
@@ -43,6 +44,7 @@ export interface Settings {
|
||||
dashboardState: DashboardState;
|
||||
scripts: CustomScript[];
|
||||
dataFiles: DataFile[] | undefined;
|
||||
dataFolders: DataFolder[];
|
||||
dataTypes: DataType[] | undefined;
|
||||
isBacker: boolean | undefined;
|
||||
snippets: Snippets | undefined;
|
||||
|
||||
@@ -115,7 +115,9 @@
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
@apply mt-4 inline-flex w-auto items-center rounded border border-transparent bg-[var(--frontmatter-button-background)] px-3 py-2 text-sm font-medium leading-4 text-[var(--vscode-button-foreground)];
|
||||
@apply mt-4 inline-flex w-auto items-center rounded-[2px] border border-transparent bg-[var(--frontmatter-button-background)] px-[11px] py-[4px] text-[var(--vscode-button-foreground)];
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size, 13px);
|
||||
|
||||
&:hover {
|
||||
@apply bg-[var(--frontmatter-button-hoverBackground)];
|
||||
@@ -310,7 +312,7 @@
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
@apply rounded text-vulcan-500;
|
||||
@apply rounded-[2px] text-vulcan-500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export const darkenColor = (color: string | undefined, percentage: number) => {
|
||||
// Check if the color is in rgba format
|
||||
if (color.startsWith('rgba')) {
|
||||
// Extract the alpha value
|
||||
const alphaMatch = color.match(/[\d\.]+(?=\))/);
|
||||
const alphaMatch = color.match(/[\d.]+(?=\))/);
|
||||
const alpha = alphaMatch ? Number(alphaMatch[0]) : 1;
|
||||
|
||||
return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { darkenColor, opacityColor, preserveColor } from '.';
|
||||
|
||||
export const updateCssVariables = (isDarkTheme: boolean = true) => {
|
||||
export const updateCssVariables = (isDarkTheme = true) => {
|
||||
const styles = getComputedStyle(document.documentElement);
|
||||
|
||||
// Lightbox
|
||||
|
||||
@@ -39,6 +39,7 @@ import { i18n } from './commands/i18n';
|
||||
import { UriHandler } from './providers/UriHandler';
|
||||
|
||||
let pageUpdateDebouncer: { (fnc: any, time: number): void };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let editDebounce: { (fnc: any, time: number): void };
|
||||
let collection: vscode.DiagnosticCollection;
|
||||
|
||||
@@ -56,7 +57,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// Sponsor check
|
||||
Backers.init(context).then(() => {});
|
||||
Backers.init(context);
|
||||
|
||||
// Make sure the EN language file is loaded
|
||||
if (!vscode.l10n.uri) {
|
||||
@@ -143,7 +144,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
SettingsHelper.startListening();
|
||||
|
||||
// Create the status bar
|
||||
let fmStatusBarItem = vscode.window.createStatusBarItem(
|
||||
const fmStatusBarItem = vscode.window.createStatusBarItem(
|
||||
'fm-statusBarItem',
|
||||
vscode.StatusBarAlignment.Right,
|
||||
-100
|
||||
@@ -243,6 +244,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
console.log(`𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲 𝖺𝖼𝗍𝗂𝗏𝖺𝗍𝖾𝖽! 𝖱𝖾𝖺𝖽𝗒 𝗍𝗈 𝗌𝗍𝖺𝗋𝗍 𝗐𝗋𝗂𝗍𝗂𝗇𝗀... 👩💻🧑💻👨💻`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
export function deactivate() {}
|
||||
|
||||
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
|
||||
|
||||
@@ -31,6 +31,9 @@ import {
|
||||
isValidFile,
|
||||
parseWinPath,
|
||||
processArticlePlaceholdersFromPath,
|
||||
processDateTimePlaceholders,
|
||||
processFilePrefixPlaceholders,
|
||||
processI18nPlaceholders,
|
||||
processTimePlaceholders
|
||||
} from '.';
|
||||
import { format, parse } from 'date-fns';
|
||||
@@ -39,8 +42,7 @@ import { Article } from '../commands';
|
||||
import { dirname, join, parse as parseFile } from 'path';
|
||||
import { EditorHelper } from '@estruyf/vscode';
|
||||
import sanitize from '../helpers/Sanitize';
|
||||
import { Field, ContentType as IContentType } from '../models';
|
||||
import { DateHelper } from './DateHelper';
|
||||
import { ContentFolder, Field, ContentType as IContentType } from '../models';
|
||||
import { DiagnosticSeverity, Position, window, Range } from 'vscode';
|
||||
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
@@ -354,7 +356,7 @@ export class ArticleHelper {
|
||||
* @returns A boolean indicating whether the file is a page bundle or not.
|
||||
*/
|
||||
public static async isPageBundle(filePath: string) {
|
||||
let article = await ArticleHelper.getFrontMatterByPath(filePath);
|
||||
const article = await ArticleHelper.getFrontMatterByPath(filePath);
|
||||
if (!article) {
|
||||
return false;
|
||||
}
|
||||
@@ -498,7 +500,7 @@ export class ArticleHelper {
|
||||
const dateFields = contentType.fields.filter((field) => field.type === 'datetime');
|
||||
|
||||
for (const dateField of dateFields) {
|
||||
if (typeof article?.data[dateField.name] !== 'undefined') {
|
||||
if (article?.data[dateField.name]) {
|
||||
article.data[dateField.name] = Article.formatDate(new Date(), dateField.dateFormat);
|
||||
}
|
||||
}
|
||||
@@ -534,7 +536,13 @@ export class ArticleHelper {
|
||||
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
|
||||
|
||||
let prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
prefix = await ArticleHelper.getFilePrefix(prefix, folderPath, contentType);
|
||||
prefix = await ArticleHelper.getFilePrefix(
|
||||
prefix,
|
||||
folderPath,
|
||||
contentType,
|
||||
titleValue,
|
||||
new Date()
|
||||
);
|
||||
|
||||
// Name of the file or folder to create
|
||||
let sanitizedName = ArticleHelper.sanitize(titleValue);
|
||||
@@ -543,7 +551,11 @@ export class ArticleHelper {
|
||||
// Create a folder with the `index.md` file
|
||||
if (contentType?.pageBundle) {
|
||||
if (prefix && typeof prefix === 'string') {
|
||||
sanitizedName = `${prefix}-${sanitizedName}`;
|
||||
if (prefix.endsWith('/')) {
|
||||
sanitizedName = `${prefix}${sanitizedName}`;
|
||||
} else {
|
||||
sanitizedName = `${prefix}-${sanitizedName}`;
|
||||
}
|
||||
}
|
||||
|
||||
const newFolder = join(folderPath, sanitizedName);
|
||||
@@ -596,12 +608,19 @@ export class ArticleHelper {
|
||||
public static async getFilePrefix(
|
||||
prefix: string | null | undefined,
|
||||
filePath?: string,
|
||||
contentType?: IContentType
|
||||
contentType?: IContentType,
|
||||
title?: string,
|
||||
articleDate?: Date
|
||||
): Promise<string | undefined> {
|
||||
if (!prefix) {
|
||||
prefix = undefined;
|
||||
}
|
||||
|
||||
// Replace the default date format
|
||||
if (prefix === 'yyyy-MM-dd') {
|
||||
prefix = '{{date|yyyy-MM-dd}}';
|
||||
}
|
||||
|
||||
// Retrieve the file prefix from the folder
|
||||
if (filePath) {
|
||||
const filePrefixOnFolder = await Folders.getFilePrefixBeFilePath(filePath);
|
||||
@@ -615,9 +634,27 @@ export class ArticleHelper {
|
||||
prefix = contentType.filePrefix;
|
||||
}
|
||||
|
||||
// Process the prefix date formatting
|
||||
if (prefix && typeof prefix === 'string') {
|
||||
prefix = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}`;
|
||||
prefix = await ArticleHelper.processCustomPlaceholders(prefix, title, filePath, true);
|
||||
prefix = await processFilePrefixPlaceholders(prefix, filePath);
|
||||
|
||||
let selectedFolder: ContentFolder | undefined | null = null;
|
||||
if (filePath) {
|
||||
// Get the folder of the article by the file path
|
||||
selectedFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
|
||||
if (!selectedFolder && contentType) {
|
||||
selectedFolder = await Folders.getFolderByContentType(contentType, filePath);
|
||||
}
|
||||
|
||||
if (selectedFolder) {
|
||||
prefix = processI18nPlaceholders(prefix, selectedFolder);
|
||||
}
|
||||
}
|
||||
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
prefix = processTimePlaceholders(prefix, dateFormat);
|
||||
prefix = processDateTimePlaceholders(prefix, articleDate);
|
||||
}
|
||||
|
||||
return prefix;
|
||||
@@ -667,7 +704,8 @@ export class ArticleHelper {
|
||||
public static async processCustomPlaceholders(
|
||||
value: string,
|
||||
title: string | undefined,
|
||||
filePath: string | undefined
|
||||
filePath: string | undefined,
|
||||
skipFileCheck = false
|
||||
) {
|
||||
if (value && typeof value === 'string') {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
@@ -711,7 +749,7 @@ export class ArticleHelper {
|
||||
let updatedValue = placeHolderValue;
|
||||
|
||||
// Check if the file already exists, during creation it might not exist yet
|
||||
if (filePath && (await existsAsync(filePath))) {
|
||||
if (filePath && (await existsAsync(filePath)) && !skipFileCheck) {
|
||||
updatedValue = await processArticlePlaceholdersFromPath(placeHolderValue, filePath);
|
||||
}
|
||||
|
||||
@@ -875,7 +913,7 @@ export class ArticleHelper {
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
|
||||
if (fileContents) {
|
||||
let article = FrontMatterParser.fromFile(fileContents);
|
||||
const article = FrontMatterParser.fromFile(fileContents);
|
||||
|
||||
if (article?.data) {
|
||||
if (commaSeparated) {
|
||||
|
||||
@@ -564,7 +564,7 @@ export class ContentType {
|
||||
|
||||
const allRequiredFields = ContentType.findRequiredFieldsDeep(contentType.fields);
|
||||
|
||||
let emptyFields: Field[][] = [];
|
||||
const emptyFields: Field[][] = [];
|
||||
|
||||
for (const fields of allRequiredFields) {
|
||||
const fieldValue = this.getFieldValue(
|
||||
@@ -656,7 +656,7 @@ export class ContentType {
|
||||
return [];
|
||||
}
|
||||
|
||||
let foundBlocks = [];
|
||||
const foundBlocks = [];
|
||||
for (const group of groups) {
|
||||
const block = blocks.find((block) => block.id === group);
|
||||
if (!block) {
|
||||
@@ -960,11 +960,12 @@ export class ContentType {
|
||||
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
|
||||
}
|
||||
|
||||
let newFilePath: string | undefined = await ArticleHelper.createContent(
|
||||
const newFilePath: string | undefined = await ArticleHelper.createContent(
|
||||
contentType,
|
||||
folderPath,
|
||||
titleValue
|
||||
);
|
||||
|
||||
if (!newFilePath) {
|
||||
return;
|
||||
}
|
||||
@@ -1045,7 +1046,7 @@ export class ContentType {
|
||||
filePath: string,
|
||||
clearEmpty: boolean,
|
||||
contentType: IContentType,
|
||||
isRoot: boolean = true
|
||||
isRoot = true
|
||||
): Promise<any> {
|
||||
if (obj.fields) {
|
||||
const titleField = getTitleField();
|
||||
@@ -1106,7 +1107,7 @@ export class ContentType {
|
||||
filePath
|
||||
);
|
||||
} else if (defaultValue && Array.isArray(defaultValue)) {
|
||||
let defaultValues = [];
|
||||
const defaultValues = [];
|
||||
for (let value of defaultValue as string[]) {
|
||||
if (typeof value === 'string') {
|
||||
value = await ContentType.processFieldPlaceholders(
|
||||
|
||||
@@ -72,7 +72,9 @@ export class CustomScript {
|
||||
|
||||
if (!path) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) return;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
articlePath = editor.document.uri.fsPath;
|
||||
article = ArticleHelper.getFrontMatter(editor);
|
||||
@@ -120,7 +122,7 @@ export class CustomScript {
|
||||
return;
|
||||
}
|
||||
|
||||
let output: string[] = [];
|
||||
const output: string[] = [];
|
||||
|
||||
window.withProgress(
|
||||
{
|
||||
@@ -283,7 +285,9 @@ export class CustomScript {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
if (!articlePath) {
|
||||
if (!editor) return;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
articlePath = editor.document.uri.fsPath;
|
||||
article = ArticleHelper.getFrontMatter(editor);
|
||||
@@ -469,7 +473,7 @@ export class CustomScript {
|
||||
* @returns
|
||||
*/
|
||||
private static async executeScriptAsync(fullScript: string, wsPath: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(fullScript, { cwd: wsPath }, (error, stdout) => {
|
||||
if (error) {
|
||||
Logger.error(error.message);
|
||||
|
||||
@@ -58,11 +58,12 @@ import { parseWinPath } from './parseWinPath';
|
||||
import { TaxonomyHelper } from './TaxonomyHelper';
|
||||
import { ContentType } from './ContentType';
|
||||
import { Logger } from './Logger';
|
||||
import { DataListener } from '../listeners/dashboard';
|
||||
|
||||
export class DashboardSettings {
|
||||
private static cachedSettings: ISettings | undefined = undefined;
|
||||
|
||||
public static async get(clear: boolean = false) {
|
||||
public static async get(clear = false) {
|
||||
if (!this.cachedSettings || clear) {
|
||||
this.cachedSettings = await this.getSettings();
|
||||
}
|
||||
@@ -158,6 +159,7 @@ export class DashboardSettings {
|
||||
}
|
||||
},
|
||||
dataFiles: await this.getDataFiles(),
|
||||
dataFolders: Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS) || [],
|
||||
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
|
||||
snippets: Settings.get<Snippets>(SETTING_CONTENT_SNIPPETS),
|
||||
snippetsWrapper: Settings.get<boolean>(SETTING_SNIPPETS_WRAPPER),
|
||||
@@ -190,9 +192,9 @@ export class DashboardSettings {
|
||||
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
|
||||
const folders = Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS);
|
||||
|
||||
let clonedFiles = Object.assign([], files);
|
||||
const clonedFiles = Object.assign([], files);
|
||||
if (folders) {
|
||||
for (let folder of folders) {
|
||||
for (const folder of folders) {
|
||||
if (!folder.path) {
|
||||
continue;
|
||||
}
|
||||
@@ -216,17 +218,8 @@ export class DashboardSettings {
|
||||
);
|
||||
|
||||
const dataFiles = [...dataJsonFiles, ...dataYmlFiles, ...dataYamlFiles];
|
||||
for (let dataFile of dataFiles) {
|
||||
clonedFiles.push({
|
||||
id: basename(dataFile.fsPath),
|
||||
title: basename(dataFile.fsPath),
|
||||
file: dataFile.fsPath,
|
||||
fileType: dataFile.fsPath.endsWith('.json') ? 'json' : 'yaml',
|
||||
labelField: folder.labelField,
|
||||
schema: folder.schema,
|
||||
type: folder.type,
|
||||
singleEntry: typeof folder.singleEntry === 'boolean' ? folder.singleEntry : false
|
||||
} as DataFile);
|
||||
for (const dataFile of dataFiles) {
|
||||
clonedFiles.push(DataListener.createDataFileObject(dataFile.fsPath, folder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ export class Extension {
|
||||
propKey: string,
|
||||
propValue: T,
|
||||
type: 'workspace' | 'global' = 'global',
|
||||
setState: boolean = false
|
||||
setState = false
|
||||
): Promise<void> {
|
||||
if (this.isFileStorageNeeded(propKey)) {
|
||||
let storageUri: Uri | undefined = undefined;
|
||||
|
||||
@@ -38,7 +38,7 @@ export class FilesHelper {
|
||||
*/
|
||||
public static relToAbsPath(filePath: string): string {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
|
||||
const absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export class ImageHelper {
|
||||
* @returns
|
||||
*/
|
||||
public static allRelToAbs(field: Field, value: string | string[] | undefined) {
|
||||
let filePath =
|
||||
const filePath =
|
||||
window.activeTextEditor?.document.uri.fsPath ||
|
||||
Preview.filePath ||
|
||||
ArticleHelper.getActiveFile();
|
||||
@@ -54,7 +54,7 @@ export class ImageHelper {
|
||||
*/
|
||||
public static relToAbs(filePath: string, value: string) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let staticFolder = Folders.getStaticFolderRelativePath();
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
|
||||
if (staticFolder === STATIC_FOLDER_PLACEHOLDER.hexo.placeholder) {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
@@ -32,6 +32,7 @@ import { lookup } from 'mime-types';
|
||||
import { existsAsync, readdirAsync, unlinkAsync, writeFileAsync } from '../utils';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { Wysiwyg } from '../commands';
|
||||
|
||||
export class MediaHelpers {
|
||||
private static media: MediaInfo[] = [];
|
||||
@@ -44,8 +45,9 @@ export class MediaHelpers {
|
||||
* @returns
|
||||
*/
|
||||
public static async getMedia(
|
||||
page: number = 0,
|
||||
requestedFolder: string = '',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
page = 0,
|
||||
requestedFolder = '',
|
||||
sort: SortingOption | null = null
|
||||
) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
@@ -439,12 +441,23 @@ export class MediaHelpers {
|
||||
|
||||
const caption = isFile ? `${data.title || ''}` : `${data.alt || data.caption || ''}`;
|
||||
|
||||
const snippet =
|
||||
data.snippet ||
|
||||
`${isFile ? '' : '!'}[${caption}](${FrameworkDetector.relAssetPathUpdate(
|
||||
relPath,
|
||||
editor.document.fileName
|
||||
).replace(/ /g, '%20')})`;
|
||||
const docType = Wysiwyg.getDocType(filePath);
|
||||
|
||||
let snippet = data.snippet || '';
|
||||
if (!data.Snippet) {
|
||||
if (docType === 'markdown') {
|
||||
snippet = `${isFile ? '' : '!'}[${caption}](${FrameworkDetector.relAssetPathUpdate(
|
||||
relPath,
|
||||
editor.document.fileName
|
||||
).replace(/ /g, '%20')})`;
|
||||
} else if (docType === 'asciidoc') {
|
||||
snippet = `${isFile ? 'link:' : 'image:'}${FrameworkDetector.relAssetPathUpdate(
|
||||
relPath,
|
||||
editor.document.fileName
|
||||
).replace(/ /g, '%20')}${caption ? `[${caption}]` : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (selection !== undefined) {
|
||||
builder.replace(selection, snippet);
|
||||
} else {
|
||||
|
||||
@@ -162,7 +162,7 @@ export class Notifications {
|
||||
* @returns
|
||||
*/
|
||||
private static shouldShow(level: NotificationType): boolean {
|
||||
let levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
|
||||
const levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
|
||||
|
||||
if (!levels) {
|
||||
return true;
|
||||
|
||||
@@ -39,7 +39,7 @@ export class Questions {
|
||||
* @param showWarning
|
||||
* @returns
|
||||
*/
|
||||
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
|
||||
public static async ContentTitle(showWarning = true): Promise<string | undefined> {
|
||||
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
|
||||
let title: string | undefined = '';
|
||||
const isCopilotInstalled = await Copilot.isInstalled();
|
||||
@@ -93,46 +93,12 @@ export class Questions {
|
||||
}
|
||||
}
|
||||
|
||||
if (title && aiTitles && aiTitles.length > 0) {
|
||||
const options: QuickPickItem[] = [
|
||||
{
|
||||
label: `✏️ ${l10n.t(
|
||||
LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator
|
||||
)}`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
{
|
||||
label: title
|
||||
},
|
||||
{
|
||||
label: `🤖 ${l10n.t(
|
||||
isCopilotInstalled
|
||||
? LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickCopilotSeparator
|
||||
: LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickAiSeparator
|
||||
)}`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...aiTitles.map((d: string) => ({
|
||||
label: d
|
||||
}))
|
||||
];
|
||||
|
||||
const selectedTitle = await window.showQuickPick(options, {
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedTitle) {
|
||||
title = selectedTitle.label;
|
||||
} else if (!selectedTitle) {
|
||||
// Reset the title, so the user can enter their own title
|
||||
title = undefined;
|
||||
}
|
||||
} else if (!title && showWarning) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning));
|
||||
return;
|
||||
}
|
||||
title = await this.pickTitleSuggestions(
|
||||
title,
|
||||
aiTitles || [],
|
||||
isCopilotInstalled,
|
||||
showWarning
|
||||
);
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
@@ -152,13 +118,63 @@ export class Questions {
|
||||
return title;
|
||||
}
|
||||
|
||||
public static async pickTitleSuggestions(
|
||||
title: string | undefined,
|
||||
aiTitles: string[],
|
||||
isCopilotInstalled: boolean,
|
||||
showWarning = true
|
||||
): Promise<string | undefined> {
|
||||
if (title && aiTitles && aiTitles.length > 0) {
|
||||
const options: QuickPickItem[] = [
|
||||
{
|
||||
label: `✏️ ${l10n.t(
|
||||
LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator
|
||||
)}`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
{
|
||||
label: title
|
||||
},
|
||||
{
|
||||
label: `🤖 ${l10n.t(
|
||||
isCopilotInstalled
|
||||
? LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickCopilotSeparator
|
||||
: LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickAiSeparator
|
||||
)}`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...aiTitles.map((d: string) => ({
|
||||
label: d
|
||||
}))
|
||||
];
|
||||
|
||||
const selectedTitle = await window.showQuickPick(options, {
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectTitle),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedTitle) {
|
||||
title = selectedTitle.label;
|
||||
} else if (!selectedTitle) {
|
||||
// Reset the title, so the user can enter their own title
|
||||
title = undefined;
|
||||
}
|
||||
} else if (!title && showWarning) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning));
|
||||
return;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the folder for your content creation
|
||||
* @param showWarning
|
||||
* @returns
|
||||
*/
|
||||
public static async SelectContentFolder(
|
||||
showWarning: boolean = true
|
||||
showWarning = true
|
||||
): Promise<FolderQuickPickItem | undefined> {
|
||||
let folders = await Folders.get();
|
||||
folders = folders.filter((f) => !f.disableCreation);
|
||||
@@ -214,7 +230,7 @@ export class Questions {
|
||||
*/
|
||||
public static async SelectContentType(
|
||||
allowedCts: string[],
|
||||
showWarning: boolean = true
|
||||
showWarning = true
|
||||
): Promise<string | undefined> {
|
||||
let contentTypes = ContentType.getAll();
|
||||
if (!contentTypes || contentTypes.length === 0) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
var illegalRe = /[\/\?<>\\:\*\|"]/g;
|
||||
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[\. ]+$/;
|
||||
const illegalRe = /[/?<>\\:*|"]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1F\x80-\x9F]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[. ]+$/;
|
||||
|
||||
function sanitize(input: string, replacement: string) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Input must be string');
|
||||
}
|
||||
var sanitized = input
|
||||
const sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
@@ -18,8 +19,8 @@ function sanitize(input: string, replacement: string) {
|
||||
}
|
||||
|
||||
export default function (input: string, options?: any) {
|
||||
var replacement = (options && options.replacement) || '';
|
||||
var output = sanitize(input, replacement);
|
||||
const replacement = (options && options.replacement) || '';
|
||||
const output = sanitize(input, replacement);
|
||||
if (replacement === '') {
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class Settings {
|
||||
public static globalConfigPath: string | undefined = undefined;
|
||||
public static globalConfig: any;
|
||||
private static config: WorkspaceConfiguration;
|
||||
private static isInitialized: boolean = false;
|
||||
private static isInitialized = false;
|
||||
private static listeners: { id: string; callback: (global?: any) => void }[] = [];
|
||||
private static fileCreationWatcher: FileSystemWatcher | undefined;
|
||||
private static fileChangeWatcher: FileSystemWatcher | undefined;
|
||||
@@ -76,7 +76,7 @@ export class Settings {
|
||||
private static readConfigPromise: Promise<void> | undefined = undefined;
|
||||
private static project: Project | undefined = undefined;
|
||||
private static configDebouncer = debounceCallback();
|
||||
private static hasExtendedConfig: boolean = false;
|
||||
private static hasExtendedConfig = false;
|
||||
private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any;
|
||||
|
||||
public static async registerCommands() {
|
||||
@@ -335,7 +335,7 @@ export class Settings {
|
||||
/**
|
||||
* Retrieve a setting from global and local config
|
||||
*/
|
||||
public static get<T>(name: string, merging: boolean = false): T | undefined {
|
||||
public static get<T>(name: string, merging = false): T | undefined {
|
||||
if (!Settings.config) {
|
||||
return;
|
||||
}
|
||||
@@ -383,11 +383,7 @@ export class Settings {
|
||||
* @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`.
|
||||
* @returns A promise that resolves when the setting is updated.
|
||||
*/
|
||||
public static async safeUpdate<T>(
|
||||
name: string,
|
||||
value: T,
|
||||
updateGlobal: boolean = false
|
||||
): Promise<void> {
|
||||
public static async safeUpdate<T>(name: string, value: T, updateGlobal = false): Promise<void> {
|
||||
if (Settings.hasExtendedConfig) {
|
||||
const configKey = `${CONFIG_KEY}.${name}`;
|
||||
|
||||
@@ -418,11 +414,7 @@ ${JSON.stringify(value, null, 2)}`,
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
public static async update<T>(
|
||||
name: string,
|
||||
value: T,
|
||||
updateGlobal: boolean = false
|
||||
): Promise<void> {
|
||||
public static async update<T>(name: string, value: T, updateGlobal = false): Promise<void> {
|
||||
const fmConfig = await Settings.projectConfigPath();
|
||||
|
||||
if (updateGlobal) {
|
||||
@@ -571,7 +563,7 @@ ${JSON.stringify(value, null, 2)}`,
|
||||
*/
|
||||
public static async updateCustomTaxonomyOptions(id: string, options: string[]) {
|
||||
const customTaxonomies = Settings.get<CustomTaxonomy[]>(SETTING_TAXONOMY_CUSTOM, true) || [];
|
||||
let taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
|
||||
const taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
|
||||
|
||||
if (taxIdx !== -1) {
|
||||
customTaxonomies[taxIdx].options = options;
|
||||
@@ -753,7 +745,7 @@ ${JSON.stringify(value, null, 2)}`,
|
||||
Logger.info(`Reading dynamic config file: ${absFilePath}`);
|
||||
if (absFilePath) {
|
||||
if (await existsAsync(absFilePath)) {
|
||||
const configFunction = require(absFilePath);
|
||||
const configFunction = await import(absFilePath);
|
||||
const dynamicConfig = await configFunction(
|
||||
Object.assign({}, Settings.globalConfig)
|
||||
);
|
||||
@@ -858,7 +850,7 @@ ${JSON.stringify(value, null, 2)}`,
|
||||
|
||||
// We need to loop through the config to make sure the objects and arrays are merged
|
||||
for (const key in config) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(config, key)) {
|
||||
const value = config[key];
|
||||
const settingName = key.replace(`${CONFIG_KEY}.`, '');
|
||||
|
||||
@@ -1209,7 +1201,7 @@ ${JSON.stringify(value, null, 2)}`,
|
||||
* Reload the config
|
||||
* @param debounced
|
||||
*/
|
||||
private static async reloadConfig(debounced: boolean = true) {
|
||||
private static async reloadConfig(debounced = true) {
|
||||
Logger.info(`Reloading config...`);
|
||||
// Clear the folder cache as we need to see the latest folders
|
||||
Folders.clearCached();
|
||||
|
||||
@@ -17,11 +17,11 @@ export class SlugHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!slugTemplate) {
|
||||
slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE) || undefined;
|
||||
if (slugTemplate === undefined || slugTemplate === null) {
|
||||
slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE);
|
||||
}
|
||||
|
||||
if (slugTemplate) {
|
||||
if (typeof slugTemplate === 'string') {
|
||||
if (slugTemplate.includes('{{title}}')) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
slugTemplate = slugTemplate.replace(regex, articleTitle.toLowerCase().replace(/\s/g, '-'));
|
||||
@@ -69,7 +69,7 @@ export class SlugHelper {
|
||||
return '';
|
||||
}
|
||||
|
||||
const punctuationless = value?.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, ' ');
|
||||
const punctuationless = value?.replace(/[.,-/#!$@%^&*;:{}=\-_`'"~()+?<>]/g, ' ');
|
||||
// Remove double spaces
|
||||
return punctuationless?.replace(/\s{2,}/g, ' ');
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SnippetField } from '../models';
|
||||
export class SnippetParser {
|
||||
public static getPlaceholders(
|
||||
value: string[] | string,
|
||||
openingTags: string = '[[',
|
||||
closingTags: string = ']]'
|
||||
openingTags = '[[',
|
||||
closingTags = ']]'
|
||||
): string[] {
|
||||
const template = SnippetParser.template(value);
|
||||
const parseTree = Mustache.parse(template, [openingTags, closingTags]);
|
||||
@@ -29,9 +29,9 @@ export class SnippetParser {
|
||||
|
||||
public static render(
|
||||
value: string[] | string,
|
||||
data: any,
|
||||
openingTags: string = '[[',
|
||||
closingTags: string = ']]'
|
||||
data: unknown,
|
||||
openingTags = '[[',
|
||||
closingTags = ']]'
|
||||
): string {
|
||||
const template = SnippetParser.template(value);
|
||||
return Mustache.render(template, data, undefined, [openingTags, closingTags]);
|
||||
@@ -40,8 +40,8 @@ export class SnippetParser {
|
||||
public static getFields(
|
||||
value: string[] | string,
|
||||
fields: SnippetField[],
|
||||
openingTags: string = '[[',
|
||||
closingTags: string = ']]'
|
||||
openingTags = '[[',
|
||||
closingTags = ']]'
|
||||
) {
|
||||
const placeholders = SnippetParser.getPlaceholders(value, openingTags, closingTags);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export class Sorting {
|
||||
* @returns
|
||||
*/
|
||||
public static alphabetically = (property: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (a: any, b: any) => {
|
||||
if (a[property] < b[property]) {
|
||||
return -1;
|
||||
@@ -24,6 +25,7 @@ export class Sorting {
|
||||
* @returns
|
||||
*/
|
||||
public static numerically = (property: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (a: any, b: any) => {
|
||||
return a[property] - b[property];
|
||||
};
|
||||
@@ -35,6 +37,7 @@ export class Sorting {
|
||||
* @returns
|
||||
*/
|
||||
public static date = (property: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (a: any, b: any) => {
|
||||
const dateA = DateHelper.tryParse(a[property]);
|
||||
const dateB = DateHelper.tryParse(b[property]);
|
||||
@@ -49,13 +52,16 @@ export class Sorting {
|
||||
* @returns
|
||||
*/
|
||||
public static dateWithFallback = (property: string, fallback: string) => {
|
||||
return (a: any, b: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (a: any, b: any): number => {
|
||||
const dateA = DateHelper.tryParse(a[property]);
|
||||
const dateB = DateHelper.tryParse(b[property]);
|
||||
|
||||
// Sort by date
|
||||
var dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
|
||||
if (dCount) return dCount;
|
||||
const dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
|
||||
if (dCount) {
|
||||
return dCount;
|
||||
}
|
||||
|
||||
// If there is a tie, sort by fallback property
|
||||
if (a[fallback] < b[fallback]) {
|
||||
@@ -67,15 +73,4 @@ export class Sorting {
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort by number
|
||||
* @param property
|
||||
* @returns
|
||||
*/
|
||||
public static number = (property: string) => {
|
||||
return (a: any, b: any) => {
|
||||
return a[property] - b[property];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ export class TaxonomyHelper {
|
||||
oldValue: string,
|
||||
newValue?: string,
|
||||
pages?: Page[],
|
||||
needsSettingsUpdate: boolean = true
|
||||
needsSettingsUpdate = true
|
||||
) {
|
||||
// Retrieve all the markdown files
|
||||
const allFiles = pages
|
||||
@@ -349,7 +349,7 @@ export class TaxonomyHelper {
|
||||
const article = FrontMatterParser.fromFile(mdFile);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
|
||||
let fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
|
||||
const fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
|
||||
|
||||
if (fieldNames.length > 0 && article && article.data) {
|
||||
const { data } = article;
|
||||
@@ -484,8 +484,8 @@ export class TaxonomyHelper {
|
||||
const article = FrontMatterParser.fromFile(mdFile);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
|
||||
let oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
|
||||
let newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
|
||||
const oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
|
||||
const newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
|
||||
|
||||
if (oldFieldNames.length > 0 && newFieldNames.length > 0 && article && article.data) {
|
||||
const { data } = article;
|
||||
@@ -559,7 +559,7 @@ export class TaxonomyHelper {
|
||||
private static getFieldsHierarchy(
|
||||
taxonomyType: TaxonomyType | string,
|
||||
contentType: IContentType,
|
||||
fallback: boolean = false
|
||||
fallback = false
|
||||
): string[] {
|
||||
let fieldNames: string[] = [];
|
||||
if (taxonomyType === TaxonomyType.Tag) {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const decodeBase64 = (dataString: string) => {
|
||||
const typePart = dataParts[0].split(':').pop() as string;
|
||||
const dataPart = dataParts.pop() as string;
|
||||
|
||||
let response: any = {};
|
||||
const response: any = {};
|
||||
|
||||
response.type = typePart;
|
||||
response.data = Buffer.from(dataPart, 'base64');
|
||||
|
||||
@@ -33,6 +33,7 @@ export * from './openFileInEditor';
|
||||
export * from './parseWinPath';
|
||||
export * from './processArticlePlaceholders';
|
||||
export * from './processDateTimePlaceholders';
|
||||
export * from './processFilePrefixPlaceholders';
|
||||
export * from './processFmPlaceholders';
|
||||
export * from './processI18nPlaceholders';
|
||||
export * from './processPathPlaceholders';
|
||||
|
||||
@@ -3,9 +3,14 @@ import { Logger } from './Logger';
|
||||
import { Notifications } from './Notifications';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { Folders, WORKSPACE_PLACEHOLDER } from '../commands';
|
||||
|
||||
export const openFileInEditor = async (filePath: string) => {
|
||||
if (filePath) {
|
||||
if (filePath.startsWith(WORKSPACE_PLACEHOLDER)) {
|
||||
filePath = Folders.getAbsFilePath(filePath);
|
||||
}
|
||||
|
||||
try {
|
||||
const doc = await workspace.openTextDocument(Uri.file(filePath));
|
||||
await window.showTextDocument(doc, 1, false);
|
||||
|
||||
@@ -8,11 +8,7 @@ import { DateHelper } from './DateHelper';
|
||||
* @param articleDate
|
||||
* @returns
|
||||
*/
|
||||
export const processDateTimePlaceholders = (
|
||||
value: string,
|
||||
dateFormat?: string,
|
||||
articleDate?: Date
|
||||
) => {
|
||||
export const processDateTimePlaceholders = (value: string, articleDate?: Date) => {
|
||||
if (value && typeof value === 'string') {
|
||||
if (value.includes(`{{date|`)) {
|
||||
const regex = /{{date\|[^}]*}}/g;
|
||||
@@ -21,7 +17,7 @@ export const processDateTimePlaceholders = (
|
||||
for (const match of matches) {
|
||||
const placeholderParts = match.split('|');
|
||||
if (placeholderParts.length > 1) {
|
||||
let dateFormat = placeholderParts[1].trim().replace('}}', '');
|
||||
const dateFormat = placeholderParts[1].trim().replace('}}', '');
|
||||
|
||||
if (dateFormat) {
|
||||
if (dateFormat && typeof dateFormat === 'string') {
|
||||
|
||||
62
src/helpers/processFilePrefixPlaceholders.ts
Normal file
62
src/helpers/processFilePrefixPlaceholders.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { FileType, Uri, workspace } from 'vscode';
|
||||
import { parse } from 'path';
|
||||
import { isValidFile } from './isValidFile';
|
||||
|
||||
/**
|
||||
* Processes file prefix placeholders in a given string value.
|
||||
*
|
||||
* This function replaces placeholders in the format `{{filePrefix.index}}` or `{{filePrefix.index|zeros:4}}`
|
||||
* with the appropriate index number based on the number of files in the directory of the given file path.
|
||||
*
|
||||
* @param value - The string containing the placeholders to be replaced.
|
||||
* @param folderPath - The path of the file whose directory will be used to determine the index number.
|
||||
* @returns A promise that resolves to the string with the placeholders replaced by the index number.
|
||||
*/
|
||||
export const processFilePrefixPlaceholders = async (value: string, folderPath?: string) => {
|
||||
// Example: {{filePrefix.index}} or {{filePrefix.index|chars:4,zeros:true}}
|
||||
if (value && value.includes('{{filePrefix.index') && folderPath) {
|
||||
const dirContent = await workspace.fs.readDirectory(Uri.file(folderPath));
|
||||
const files = dirContent.filter(
|
||||
([filePath, type]) =>
|
||||
type === FileType.File &&
|
||||
!filePath.startsWith('.') &&
|
||||
isValidFile(filePath) &&
|
||||
!filePath.includes('_index.')
|
||||
);
|
||||
|
||||
let chars = 3;
|
||||
const idxValue = files.length + 1;
|
||||
|
||||
if (value.includes('{{filePrefix.index}}')) {
|
||||
const regex = new RegExp('{{filePrefix.index}}', 'g');
|
||||
const placeholderValue = idxValue.toString().padStart(chars, '0');
|
||||
value = value.replace(regex, placeholderValue);
|
||||
}
|
||||
// Example: {{filePrefix.index|zeros:4}}
|
||||
else if (value.includes('{{filePrefix.index')) {
|
||||
const regex = /{{filePrefix.index[^}]*}}/g;
|
||||
const matches = value.match(regex);
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const placeholderParts = match.split('|');
|
||||
if (placeholderParts.length > 1) {
|
||||
const options = placeholderParts[1].trim().replace('}}', '').split(',');
|
||||
|
||||
for (const option of options) {
|
||||
if (option.startsWith('zeros:')) {
|
||||
chars = parseInt(option.replace('zeros:', ''));
|
||||
}
|
||||
}
|
||||
|
||||
const placeholderValue = chars
|
||||
? idxValue.toString().padStart(chars, '0')
|
||||
: idxValue.toString();
|
||||
value = value.replace(match, placeholderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
@@ -11,7 +11,7 @@ export const processPathPlaceholders = (
|
||||
const relPathToken = '{{pathToken.relPath}}';
|
||||
if (value.includes(relPathToken) && contentFolder?.path) {
|
||||
const dirName = dirname(filePath);
|
||||
let relPath = relative(contentFolder.path, dirName);
|
||||
const relPath = relative(contentFolder.path, dirName);
|
||||
value = value.replace(relPathToken, relPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Logger } from '../../helpers/Logger';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export abstract class BaseListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
||||
public static process(msg: PostMessageData) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { workspace, window } from 'vscode';
|
||||
import { DataFile } from './../../models/DataFile';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { dirname } from 'path';
|
||||
import { basename, dirname, join } from 'path';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { DataFileHelper, Logger } from '../../helpers';
|
||||
import { existsAsync, readFileAsync, writeFileAsync } from '../../utils';
|
||||
import { mkdirAsync } from '../../utils/mkdirAsync';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { DataFolder, PostMessageData } from '../../models';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
import { SettingsListener } from './SettingsListener';
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
public static process(msg: PostMessageData) {
|
||||
@@ -26,11 +28,35 @@ export class DataListener extends BaseListener {
|
||||
case DashboardMessage.putDataEntries:
|
||||
this.processDataUpdate(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.createDataFile:
|
||||
this.createDataFile(msg.command, msg.requestId || '', msg.payload);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DataFile object based on the provided path and folder.
|
||||
*
|
||||
* @param path - The path of the file.
|
||||
* @param folder - The DataFolder object.
|
||||
* @returns The created DataFile object.
|
||||
*/
|
||||
public static createDataFileObject(path: string, folder: DataFolder): DataFile {
|
||||
const filePath = Folders.wsPath(path);
|
||||
return {
|
||||
id: basename(path),
|
||||
title: basename(path),
|
||||
file: filePath,
|
||||
fileType: path.endsWith('.json') ? 'json' : 'yaml',
|
||||
labelField: folder.labelField,
|
||||
schema: folder.schema,
|
||||
type: folder.type,
|
||||
singleEntry: typeof folder.singleEntry === 'boolean' ? folder.singleEntry : false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the data update
|
||||
* @param msgData
|
||||
@@ -81,4 +107,57 @@ export class DataListener extends BaseListener {
|
||||
const entries = await DataFileHelper.process(msgData);
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new data file
|
||||
* @param command
|
||||
* @param requestId
|
||||
* @param data
|
||||
*/
|
||||
private static async createDataFile(command: string, requestId: string, dataFolder: DataFolder) {
|
||||
if (!command || !requestId || !dataFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dataFolder.id || !dataFolder.path) {
|
||||
this.sendError(
|
||||
command as DashboardCommand,
|
||||
requestId,
|
||||
localize(LocalizationKey.listenersPanelDataListenerCreateDataFileError)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = await window.showInputBox({
|
||||
title: localize(LocalizationKey.listenersPanelDataListenerCreateDataFileInputTitle),
|
||||
prompt: localize(LocalizationKey.listenersPanelDataListenerCreateDataFileInputTitle),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!fileName || fileName.trim() === '') {
|
||||
this.sendError(
|
||||
command as DashboardCommand,
|
||||
requestId,
|
||||
localize(LocalizationKey.listenersPanelDataListenerCreateDataFileNoFileName)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const absPath = Folders.getAbsFilePath(dataFolder.path);
|
||||
if (!(await existsAsync(absPath))) {
|
||||
const dirPath = dirname(absPath);
|
||||
if (!(await existsAsync(dirPath))) {
|
||||
await mkdirAsync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// Check the file type and create the file
|
||||
const filePath = join(absPath, `${fileName}.${dataFolder.fileType || 'json'}`);
|
||||
await writeFileAsync(filePath, dataFolder.fileType === 'json' ? '{}' : '', 'utf8');
|
||||
|
||||
// Update the settings
|
||||
await SettingsListener.getSettings(true);
|
||||
const dataFile = DataListener.createDataFileObject(filePath, dataFolder);
|
||||
this.sendRequest(command as DashboardCommand, requestId, dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ export class MediaListener extends BaseListener {
|
||||
public static async process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
const { page, folder, sorting } = msg.payload ?? {};
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.getMedia:
|
||||
const { page, folder, sorting } = msg?.payload;
|
||||
this.sendMediaFiles(page, folder, sorting);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
@@ -67,11 +68,7 @@ export class MediaListener extends BaseListener {
|
||||
* @param folder
|
||||
* @param sorting
|
||||
*/
|
||||
public static async sendMediaFiles(
|
||||
page: number = 0,
|
||||
folder: string = '',
|
||||
sorting: SortingOption | null = null
|
||||
) {
|
||||
public static async sendMediaFiles(page = 0, folder = '', sorting: SortingOption | null = null) {
|
||||
MediaLibrary.reset();
|
||||
const files = await MediaHelpers.getMedia(page, folder, sorting);
|
||||
this.sendMsg(DashboardCommand.media, files);
|
||||
@@ -143,7 +140,7 @@ export class MediaListener extends BaseListener {
|
||||
}
|
||||
|
||||
if (unmappedFiles && unmappedFiles.length > 0) {
|
||||
this.sendRequest(command as any, requestId, unmappedFiles);
|
||||
this.sendRequest(command as DashboardCommand, requestId, unmappedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +148,7 @@ export class MediaListener extends BaseListener {
|
||||
* Store the file and send a message after multiple uploads
|
||||
* @param data
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private static async store(data: any) {
|
||||
try {
|
||||
const { folder } = data;
|
||||
@@ -167,7 +165,9 @@ export class MediaListener extends BaseListener {
|
||||
this.sendMediaFiles(0, folder || '');
|
||||
delete this.timers[folderPath];
|
||||
}, 500);
|
||||
} catch {}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,13 +178,16 @@ export class MediaListener extends BaseListener {
|
||||
try {
|
||||
MediaHelpers.deleteFile(data.file);
|
||||
this.sendMediaFiles(data.page || 0, data.folder || '');
|
||||
} catch {}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update media metadata
|
||||
* @param data
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private static async update(data: any) {
|
||||
try {
|
||||
const { page, folder } = data;
|
||||
@@ -192,6 +195,8 @@ export class MediaListener extends BaseListener {
|
||||
await MediaHelpers.updateMetadata(data);
|
||||
|
||||
this.sendMediaFiles(page || 0, folder || '');
|
||||
} catch {}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export class PagesListener extends BaseListener {
|
||||
// Recreate all the watchers
|
||||
for (const folder of folders) {
|
||||
const folderUri = Uri.parse(folder.path);
|
||||
let watcher = workspace.createFileSystemWatcher(
|
||||
const watcher = workspace.createFileSystemWatcher(
|
||||
new RelativePattern(folderUri, '**/*'),
|
||||
false,
|
||||
false,
|
||||
@@ -200,7 +200,7 @@ export class PagesListener extends BaseListener {
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
public static async getPagesData(clear: boolean = false, cb?: (pages: Page[]) => void) {
|
||||
public static async getPagesData(clear = false, cb?: (pages: Page[]) => void) {
|
||||
const ext = Extension.getInstance();
|
||||
|
||||
// Get data from the cache
|
||||
@@ -257,7 +257,7 @@ export class PagesListener extends BaseListener {
|
||||
*/
|
||||
private static async createSearchIndex(pages: Page[]) {
|
||||
const pagesIndex = Fuse.createIndex(
|
||||
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType'],
|
||||
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType', 'fmLocale.locale'],
|
||||
pages
|
||||
);
|
||||
await Extension.getInstance().setState(
|
||||
|
||||
@@ -198,7 +198,7 @@ export class SettingsListener extends BaseListener {
|
||||
/**
|
||||
* Retrieve the settings for the dashboard
|
||||
*/
|
||||
public static async getSettings(clear: boolean = false) {
|
||||
public static async getSettings(clear = false) {
|
||||
Logger.verbose(`SettingsListener:getSettings:start - clear: ${clear}`);
|
||||
const settings = await DashboardSettings.get(clear);
|
||||
Logger.verbose(
|
||||
@@ -315,7 +315,7 @@ export class SettingsListener extends BaseListener {
|
||||
private static async copyTemplateFiles(
|
||||
files: [string, FileType][],
|
||||
templateFileLocation: string,
|
||||
extRelPath: string = ''
|
||||
extRelPath = ''
|
||||
) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (!wsFolder) {
|
||||
|
||||
@@ -30,6 +30,8 @@ import { Event, commands, extensions } from 'vscode';
|
||||
import { GitAPIState, GitRepository, PostMessageData } from '../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
|
||||
export class GitListener {
|
||||
private static gitAPI: {
|
||||
@@ -39,7 +41,7 @@ export class GitListener {
|
||||
getAPI: (version: number) => any;
|
||||
repositories: GitRepository[];
|
||||
} | null = null;
|
||||
private static isRegistered: boolean = false;
|
||||
private static isRegistered = false;
|
||||
private static client: SimpleGit | null = null;
|
||||
private static subClient: SimpleGit | null = null;
|
||||
private static repository: GitRepository | null = null;
|
||||
@@ -123,6 +125,7 @@ export class GitListener {
|
||||
break;
|
||||
case GeneralCommands.toVSCode.git.selectBranch:
|
||||
this.selectBranch();
|
||||
break;
|
||||
case GeneralCommands.toVSCode.git.isRepo:
|
||||
this.checkIsGitRepo(msg.command, msg.requestId);
|
||||
break;
|
||||
@@ -135,7 +138,11 @@ export class GitListener {
|
||||
}
|
||||
|
||||
const isRepo = await GitListener.isGitRepository();
|
||||
Dashboard.postWebviewMessage({ command: command as any, payload: isRepo, requestId });
|
||||
Dashboard.postWebviewMessage({
|
||||
command: command as DashboardCommand | DashboardMessage,
|
||||
payload: isRepo,
|
||||
requestId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +159,7 @@ export class GitListener {
|
||||
* @param commitMsg The commit message for the push operation.
|
||||
* @param isSync Determines whether to perform a sync operation (default: true) or a fetch operation.
|
||||
*/
|
||||
public static async sync(commitMsg?: string, isSync: boolean = true) {
|
||||
public static async sync(commitMsg?: string, isSync = true) {
|
||||
try {
|
||||
this.sendMsg(GeneralCommands.toWebview.git.syncingStart, isSync ? 'syncing' : 'fetching');
|
||||
|
||||
@@ -320,7 +327,7 @@ export class GitListener {
|
||||
* @param submoduleFolder The path to the submodule folder.
|
||||
* @returns The Git client instance or null if it cannot be retrieved.
|
||||
*/
|
||||
private static getClient(submoduleFolder: string = ''): SimpleGit | null {
|
||||
private static getClient(submoduleFolder = ''): SimpleGit | null {
|
||||
if (!submoduleFolder && this.client) {
|
||||
return this.client;
|
||||
} else if (submoduleFolder && this.subClient) {
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Command } from '../../panelWebView/Command';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export abstract class BaseListener {
|
||||
public static process(msg: PostMessageData) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
||||
public static process(_: PostMessageData) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
processArticlePlaceholdersFromData,
|
||||
processTimePlaceholders,
|
||||
processFmPlaceholders,
|
||||
parseWinPath
|
||||
parseWinPath,
|
||||
Questions
|
||||
} from '../../helpers';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
@@ -107,6 +108,24 @@ export class DataListener extends BaseListener {
|
||||
case CommandToCode.copilotSuggestDescription:
|
||||
this.copilotSuggestDescription(msg.command, msg.requestId);
|
||||
break;
|
||||
case CommandToCode.copilotSuggestTitle:
|
||||
this.copilotSuggestTitle(msg.command, msg.requestId, msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static async copilotSuggestTitle(command: string, requestId?: string, title?: string) {
|
||||
if (!command || !requestId || !title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aiTitles = await Copilot.suggestTitles(title);
|
||||
title = await Questions.pickTitleSuggestions(title, aiTitles || [], true);
|
||||
|
||||
if (title) {
|
||||
this.sendRequest(command, requestId, title);
|
||||
} else {
|
||||
this.sendRequestError(command, requestId, 'Failed to suggest title');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +349,7 @@ export class DataListener extends BaseListener {
|
||||
metadata.articleDetails = articleDetails;
|
||||
}
|
||||
|
||||
let updatedMetadata = Object.assign({}, metadata);
|
||||
const updatedMetadata = Object.assign({}, metadata);
|
||||
if (commaSeparated) {
|
||||
for (const key of commaSeparated) {
|
||||
if (updatedMetadata[key] && typeof updatedMetadata[key] === 'string') {
|
||||
@@ -437,6 +456,8 @@ export class DataListener extends BaseListener {
|
||||
if (!sourceField.default) {
|
||||
value = undefined;
|
||||
}
|
||||
} else if (sourceField?.type === 'number') {
|
||||
// We don't have to do anything for numbers, we can leave the 0 value
|
||||
} else {
|
||||
value = undefined;
|
||||
}
|
||||
@@ -566,13 +587,14 @@ export class DataListener extends BaseListener {
|
||||
* @returns
|
||||
*/
|
||||
public static async getParentObject(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any,
|
||||
article: ParsedFrontMatter,
|
||||
parents: string[] | undefined,
|
||||
blockData?: BlockFieldData
|
||||
) {
|
||||
let parentObj = data;
|
||||
let allParents = Object.assign([], parents);
|
||||
const allParents = Object.assign([], parents);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
let selectedIndexes: number[] = [];
|
||||
if (blockData?.selectedIndex) {
|
||||
@@ -590,7 +612,7 @@ export class DataListener extends BaseListener {
|
||||
parentObj = article.data;
|
||||
|
||||
// Loop through the parents of the block field
|
||||
for (const parent of blockData?.parentFields) {
|
||||
for (const parent of blockData?.parentFields ?? []) {
|
||||
if (!parentObj) {
|
||||
continue;
|
||||
}
|
||||
@@ -748,6 +770,7 @@ export class DataListener extends BaseListener {
|
||||
articleData: {
|
||||
field: string;
|
||||
value: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: { [key: string]: any };
|
||||
contentType?: IContentType;
|
||||
},
|
||||
@@ -757,7 +780,8 @@ export class DataListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
let { field, value, data, contentType } = articleData;
|
||||
const { field, data, contentType } = articleData;
|
||||
let { value } = articleData;
|
||||
|
||||
value = value || '';
|
||||
const valueBefore = value;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { i18n } from '../../commands';
|
||||
import { ExtensionState } from '../../constants';
|
||||
import { Page } from '../../dashboardWebView/models';
|
||||
import { Extension } from '../../helpers';
|
||||
@@ -29,14 +30,28 @@ export class FieldsListener extends BaseListener {
|
||||
* @param payload
|
||||
* @returns
|
||||
*/
|
||||
private static async searchByType(command: string, requestId?: string, type?: string) {
|
||||
if (!type || !requestId) {
|
||||
private static async searchByType(
|
||||
command: string,
|
||||
requestId?: string,
|
||||
data?: { type?: string; sameLocale?: boolean; activePath?: string }
|
||||
) {
|
||||
if (!data?.type || !data?.activePath || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeLocale = await i18n.getLocale(data.activePath);
|
||||
if (!activeLocale?.locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
PagesListener.getPagesData(false, async (pages) => {
|
||||
const fuseOptions: Fuse.IFuseOptions<Page> = {
|
||||
keys: [{ name: 'fmContentType', weight: 1 }]
|
||||
keys: [
|
||||
{ name: 'fmContentType', weight: 1 },
|
||||
...(data.sameLocale ? [{ name: 'fmLocale.locale', weight: 1 }] : [])
|
||||
],
|
||||
findAllMatches: true,
|
||||
threshold: 0
|
||||
};
|
||||
|
||||
const pagesIndex = await Extension.getInstance().getState<Fuse.FuseIndex<Page>>(
|
||||
@@ -47,9 +62,8 @@ export class FieldsListener extends BaseListener {
|
||||
const fuse = new Fuse(pages || [], fuseOptions, fuseIndex);
|
||||
const results = fuse.search({
|
||||
$and: [
|
||||
{
|
||||
fmContentType: type
|
||||
}
|
||||
{ fmContentType: data.type! },
|
||||
...(data.sameLocale ? [{ 'fmLocale.locale': activeLocale.locale }] : [])
|
||||
]
|
||||
});
|
||||
const pageResults = results.map((page) => page.item);
|
||||
|
||||
@@ -480,9 +480,25 @@ export enum LocalizationKey {
|
||||
*/
|
||||
dashboardDataViewDataViewUpdateMessage = 'dashboard.dataView.dataView.update.message',
|
||||
/**
|
||||
* Select your date type first
|
||||
* Create new data file
|
||||
*/
|
||||
dashboardDataViewDataViewCreateNew = 'dashboard.dataView.dataView.createNew',
|
||||
/**
|
||||
* Select data folder
|
||||
*/
|
||||
dashboardDataViewDataViewSelectDataFolder = 'dashboard.dataView.dataView.selectDataFolder',
|
||||
/**
|
||||
* Close data file
|
||||
*/
|
||||
dashboardDataViewDataViewCloseSelectedDataFile = 'dashboard.dataView.dataView.closeSelectedDataFile',
|
||||
/**
|
||||
* Select your data type first
|
||||
*/
|
||||
dashboardDataViewEmptyViewHeading = 'dashboard.dataView.emptyView.heading',
|
||||
/**
|
||||
* Start by creating a new data file
|
||||
*/
|
||||
dashboardDataViewEmptyViewHeadingCreate = 'dashboard.dataView.emptyView.heading.create',
|
||||
/**
|
||||
* Edit "{0}"
|
||||
*/
|
||||
@@ -1868,6 +1884,26 @@ export enum LocalizationKey {
|
||||
* To which locale do you want to create a new content?
|
||||
*/
|
||||
commandsI18nCreateQuickPickPlaceHolder = 'commands.i18n.create.quickPick.placeHolder',
|
||||
/**
|
||||
* Open or create translation
|
||||
*/
|
||||
commandsI18nCreateOrOpenQuickPickTitle = 'commands.i18n.createOrOpen.quickPick.title',
|
||||
/**
|
||||
* Existing translations
|
||||
*/
|
||||
commandsI18nCreateOrOpenQuickPickCategoryExisting = 'commands.i18n.createOrOpen.quickPick.category.existing',
|
||||
/**
|
||||
* Open "{0}"
|
||||
*/
|
||||
commandsI18nCreateOrOpenQuickPickActionOpen = 'commands.i18n.createOrOpen.quickPick.action.open',
|
||||
/**
|
||||
* New translations
|
||||
*/
|
||||
commandsI18nCreateOrOpenQuickPickCategoryNew = 'commands.i18n.createOrOpen.quickPick.category.new',
|
||||
/**
|
||||
* Create "{0}"
|
||||
*/
|
||||
commandsI18nCreateOrOpenQuickPickActionCreate = 'commands.i18n.createOrOpen.quickPick.action.create',
|
||||
/**
|
||||
* Translating content...
|
||||
*/
|
||||
@@ -2592,6 +2628,18 @@ export enum LocalizationKey {
|
||||
* Something went wrong while parsing your front matter. Please check the contents of your file.
|
||||
*/
|
||||
listenersPanelDataListenerPushMetadataFrontMatterError = 'listeners.panel.dataListener.pushMetadata.frontMatter.error',
|
||||
/**
|
||||
* What is the name of the data file?
|
||||
*/
|
||||
listenersPanelDataListenerCreateDataFileInputTitle = 'listeners.panel.dataListener.createDataFile.inputTitle',
|
||||
/**
|
||||
* No data file id or path defined.
|
||||
*/
|
||||
listenersPanelDataListenerCreateDataFileError = 'listeners.panel.dataListener.createDataFile.error',
|
||||
/**
|
||||
* No filename provided.
|
||||
*/
|
||||
listenersPanelDataListenerCreateDataFileNoFileName = 'listeners.panel.dataListener.createDataFile.noFileName',
|
||||
/**
|
||||
* No active editor
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as l10n from '@vscode/l10n';
|
||||
|
||||
export const localize = (key: string, ...args: any[]): string => {
|
||||
export const localize = (key: string, ...args: (string | number)[]): string => {
|
||||
return l10n.t(key, ...args);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface ContentFolder {
|
||||
|
||||
disableCreation?: boolean;
|
||||
excludeSubdir?: boolean;
|
||||
excludePaths?: string[];
|
||||
previewPath?: string;
|
||||
trailingSlash?: boolean;
|
||||
filePrefix?: string;
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface DataFile {
|
||||
file: string;
|
||||
fileType: 'json' | 'yaml';
|
||||
labelField: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
schema?: any;
|
||||
type?: string;
|
||||
singleEntry?: boolean;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user