Compare commits
155 Commits
azure-tran
...
v10.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a97a11c1c | ||
|
|
430760eca8 | ||
|
|
5353d07fcb | ||
|
|
1f94ae165c | ||
|
|
dbd42ac1f9 | ||
|
|
1031088f85 | ||
|
|
fcbbe7f834 | ||
|
|
c58d0573c6 | ||
|
|
ba7a0225c1 | ||
|
|
65fd8b4a78 | ||
|
|
46af17eac2 | ||
|
|
2eb0b775a3 | ||
|
|
bf07f29698 | ||
|
|
a22219c1b4 | ||
|
|
ec326a74ca | ||
|
|
2246fbb933 | ||
|
|
fa6f7dcfe6 | ||
|
|
f83ed9b970 | ||
|
|
9ce70fe722 | ||
|
|
4a1b37ba88 | ||
|
|
0b8155a75f | ||
|
|
19a0f4b53f | ||
|
|
0bde5610c5 | ||
|
|
b90f2adb18 | ||
|
|
a70b4316f8 | ||
|
|
16453cbb21 | ||
|
|
46e90df501 | ||
|
|
d8d72980ea | ||
|
|
7a5e452602 | ||
|
|
beee186d72 | ||
|
|
64fc1e4b76 | ||
|
|
5c4a716367 | ||
|
|
31873bc2d2 | ||
|
|
0e92834517 | ||
|
|
d262518023 | ||
|
|
da2cf68f5c | ||
|
|
2e7ece44e2 | ||
|
|
c039d260dc | ||
|
|
2fc543f0dd | ||
|
|
48314b3f3f | ||
|
|
a43b581e1b | ||
|
|
1ad55cdbbb | ||
|
|
ffa70050eb | ||
|
|
e8f70c78fd | ||
|
|
504774a4c8 | ||
|
|
a764c2fea7 | ||
|
|
5f623689cc | ||
|
|
54bf408c76 | ||
|
|
03f2284dd2 | ||
|
|
f637def278 | ||
|
|
da46374fb4 | ||
|
|
dee732f3ee | ||
|
|
d3b93424d1 | ||
|
|
a467791eaf | ||
|
|
70a5de960f | ||
|
|
31e27f63c1 | ||
|
|
a50f567fbb | ||
|
|
bdafd25cfe | ||
|
|
18b7708367 | ||
|
|
3fedaf7d5f | ||
|
|
75a3fc21a3 | ||
|
|
82b894c35b | ||
|
|
60952a05ac | ||
|
|
a571b34724 | ||
|
|
f46e4999a1 | ||
|
|
f9138cb3c3 | ||
|
|
893c46362e | ||
|
|
9136841b30 | ||
|
|
0e21093f92 | ||
|
|
3abd9589f1 | ||
|
|
81265e3c49 | ||
|
|
f6fd57e126 | ||
|
|
20d613452f | ||
|
|
35a6c8bada | ||
|
|
0b7f58d0ab | ||
|
|
c859874470 | ||
|
|
d70d2284b4 | ||
|
|
03236da793 | ||
|
|
07935aec73 | ||
|
|
f64c8c5958 | ||
|
|
c4267a69fa | ||
|
|
34b331b0ee | ||
|
|
5d0fc4f605 | ||
|
|
169f4ef14a | ||
|
|
7ea386328c | ||
|
|
c17400ce6d | ||
|
|
7b20d9f23d | ||
|
|
449bb110c2 | ||
|
|
0d3a99abe6 | ||
|
|
d2b9307a65 | ||
|
|
3842777f71 | ||
|
|
3a74c14ba6 | ||
|
|
a5ac7379bc | ||
|
|
c245e1474c | ||
|
|
c82c081fce | ||
|
|
31e344f358 | ||
|
|
366ae82318 | ||
|
|
c1a0609216 | ||
|
|
87bdabf515 | ||
|
|
0ae7cb27ce | ||
|
|
15870bcc99 | ||
|
|
5e77419f5a | ||
|
|
ec9f55b982 | ||
|
|
fdcfdc971d | ||
|
|
2bc103026b | ||
|
|
e0cdc5cf65 | ||
|
|
f39b707e30 | ||
|
|
23b1efec55 | ||
|
|
dd13d8779c | ||
|
|
6f6015cf83 | ||
|
|
afd2878428 | ||
|
|
c66deb032c | ||
|
|
4c079b3e9d | ||
|
|
03c2cd31d7 | ||
|
|
d1dba01923 | ||
|
|
2a8d7b0ebe | ||
|
|
3b26944a4a | ||
|
|
78cac94dd6 | ||
|
|
9c6845ed8a | ||
|
|
286ac4adfe | ||
|
|
7633ac91be | ||
|
|
282527c90d | ||
|
|
07fbf8bdb9 | ||
|
|
6e2633572a | ||
|
|
2e35da3d91 | ||
|
|
2bd607b13c | ||
|
|
106f1e6c94 | ||
|
|
54cd3ead64 | ||
|
|
7e9bd5b0ce | ||
|
|
9086868817 | ||
|
|
4bff53299e | ||
|
|
ee101cfe4d | ||
|
|
247051f592 | ||
|
|
e6b6bba7df | ||
|
|
be5d15d2f8 | ||
|
|
65364b8486 | ||
|
|
6dd82bd4fe | ||
|
|
e0b18465dc | ||
|
|
661efcf23f | ||
|
|
152f36e352 | ||
|
|
08697abba4 | ||
|
|
0a530dce27 | ||
|
|
63e296d62f | ||
|
|
003d93b0f2 | ||
|
|
59528a3db0 | ||
|
|
c298f2fd69 | ||
|
|
b02a80c28e | ||
|
|
f19bd07359 | ||
|
|
83b9f2380e | ||
|
|
3f88b05a1c | ||
|
|
48ada1c352 | ||
|
|
a8777c4032 | ||
|
|
fe5df3779b | ||
|
|
91ec23e77c | ||
|
|
36ae7081d1 |
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,6 +12,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@@ -23,16 +24,11 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
**Device:**
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
- OS: [e.g. iOS]
|
||||
- Front Matter CMS Version [e.g. 10.2.0]
|
||||
- Browser [e.g. chrome, safari]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
46
.github/actions/localization/action.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Localization sync
|
||||
description: Syncs the localization values from English to the other supported languages
|
||||
|
||||
inputs:
|
||||
TRANSLATION_API_KEY:
|
||||
description: 'The API key for the translation service'
|
||||
required: true
|
||||
TRANSLATION_API_LOCATION:
|
||||
description: 'The location of the translation service'
|
||||
required: true
|
||||
TRANSLATION_API_URL:
|
||||
description: 'The URL of the translation service'
|
||||
required: true
|
||||
PACKAGE_NAME:
|
||||
description: 'The name of the package to be uploaded'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install the dependencies
|
||||
shell: bash
|
||||
run: npm ci
|
||||
|
||||
- name: Sync localization
|
||||
shell: bash
|
||||
run: npm run localization:sync
|
||||
env:
|
||||
TRANSLATION_API_KEY: ${{ inputs.TRANSLATION_API_KEY }}
|
||||
TRANSLATION_API_LOCATION: ${{ inputs.TRANSLATION_API_LOCATION }}
|
||||
TRANSLATION_API_URL: ${{ inputs.TRANSLATION_API_URL }}
|
||||
|
||||
- name: Remove the node_modules
|
||||
shell: bash
|
||||
run: rm -rf node_modules
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.PACKAGE_NAME }}
|
||||
path: .
|
||||
66
.github/workflows/release-beta.yml
vendored
@@ -5,19 +5,45 @@ on:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: 'fm-localized'
|
||||
MS_URL: 'https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta'
|
||||
VSX_URL: 'https://open-vsx.org/extension/eliostruyf/vscode-front-matter-beta'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Build and release'
|
||||
localization:
|
||||
name: 'Localization'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Beta
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Localize the solution
|
||||
uses: ./.github/actions/localization
|
||||
with:
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
|
||||
TRANSLATION_API_LOCATION: ${{ secrets.TRANSLATION_API_LOCATION }}
|
||||
TRANSLATION_API_URL: ${{ secrets.TRANSLATION_API_URL }}
|
||||
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
release-ms:
|
||||
name: 'Release to VSCode Marketplace'
|
||||
runs-on: ubuntu-latest
|
||||
needs: localization
|
||||
environment:
|
||||
name: 'MS - BETA'
|
||||
url: ${{ env.MS_URL }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install the dependencies
|
||||
run: npm ci
|
||||
@@ -25,15 +51,33 @@ jobs:
|
||||
- name: Prepare BETA
|
||||
run: node scripts/beta-release.js $GITHUB_RUN_ID
|
||||
|
||||
- name: Run localization sync
|
||||
run: npm run localization:sync
|
||||
env:
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
|
||||
TRANSLATION_API_LOCATION: ${{ secrets.TRANSLATION_API_LOCATION }}
|
||||
TRANSLATION_API_URL: ${{ secrets.TRANSLATION_API_URL }}
|
||||
|
||||
- name: Publish
|
||||
run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
|
||||
|
||||
release-vsx:
|
||||
name: 'Release to Open VSX'
|
||||
runs-on: ubuntu-latest
|
||||
needs: localization
|
||||
environment:
|
||||
name: 'Open VSX - BETA'
|
||||
url: ${{ env.VSX_URL }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install the dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Prepare BETA
|
||||
run: node scripts/beta-release.js $GITHUB_RUN_ID
|
||||
|
||||
- name: Publish to open-vsx.org
|
||||
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
|
||||
|
||||
66
.github/workflows/release.yml
vendored
@@ -5,19 +5,45 @@ on:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: 'fm-localized'
|
||||
MS_URL: 'https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter'
|
||||
VSX_URL: 'https://open-vsx.org/extension/eliostruyf/vscode-front-matter'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Build and release'
|
||||
localization:
|
||||
name: 'Localization'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Stable
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Localize the solution
|
||||
uses: ./.github/actions/localization
|
||||
with:
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
|
||||
TRANSLATION_API_LOCATION: ${{ secrets.TRANSLATION_API_LOCATION }}
|
||||
TRANSLATION_API_URL: ${{ secrets.TRANSLATION_API_URL }}
|
||||
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
release-ms:
|
||||
name: 'Release to VSCode Marketplace'
|
||||
runs-on: ubuntu-latest
|
||||
needs: localization
|
||||
environment:
|
||||
name: 'MS - Stable'
|
||||
url: ${{ env.MS_URL }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install the dependencies
|
||||
run: npm ci
|
||||
@@ -25,15 +51,33 @@ jobs:
|
||||
- name: Prepare MAIN release
|
||||
run: node scripts/main-release.js
|
||||
|
||||
- name: Run localization sync
|
||||
run: npm run localization:sync
|
||||
env:
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
|
||||
TRANSLATION_API_LOCATION: ${{ secrets.TRANSLATION_API_LOCATION }}
|
||||
TRANSLATION_API_URL: ${{ secrets.TRANSLATION_API_URL }}
|
||||
|
||||
- name: Publish
|
||||
run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }}
|
||||
|
||||
release-vsx:
|
||||
name: 'Release to Open VSX'
|
||||
runs-on: ubuntu-latest
|
||||
needs: localization
|
||||
environment:
|
||||
name: 'Open VSX - Stable'
|
||||
url: ${{ env.VSX_URL }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PACKAGE_NAME }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install the dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Prepare MAIN release
|
||||
run: node scripts/main-release.js
|
||||
|
||||
- name: Publish to open-vsx.org
|
||||
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
|
||||
|
||||
8
.vscode/launch.json
vendored
@@ -10,7 +10,9 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: build:ext"
|
||||
},
|
||||
@@ -19,7 +21,9 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}", "--disable-extension=eliostruyf.vscode-front-matter"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
|
||||
10
.vscode/settings.json
vendored
@@ -1,5 +1,15 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"commitHelper.messages": [
|
||||
{
|
||||
"type": "👨💻 apps",
|
||||
"values": ["#search", "#profile"]
|
||||
},
|
||||
{
|
||||
"type": "⚙️ tasks",
|
||||
"values": ["#build", "#deploy", "#skip"]
|
||||
}
|
||||
],
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeBackground": "#15c2cb",
|
||||
"titleBar.inactiveBackground": "#44ffd299",
|
||||
|
||||
75
CHANGELOG.md
@@ -1,6 +1,77 @@
|
||||
# Change Log
|
||||
|
||||
## [10.0.0] - 2024-xx-xx - [Release notes](https://beta.frontmatter.codes/updates/v10.0.0)
|
||||
## [10.2.1] - 2024-08-08
|
||||
|
||||
- [#820](https://github.com/estruyf/vscode-front-matter/issues/820): Update API links to the new API URL
|
||||
|
||||
## [10.2.0] - 2024-06-12 - [Release notes](https://beta.frontmatter.codes/updates/v10.2.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#797](https://github.com/estruyf/vscode-front-matter/issues/797): Adding common actions at the bottom of the snippet cards
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#441](https://github.com/estruyf/vscode-front-matter/issues/441): Show input descriptions for snippet and data forms
|
||||
- [#442](https://github.com/estruyf/vscode-front-matter/issues/442): Hide sidebar on data view when data file is selected + show dropdown of data files
|
||||
- [#788](https://github.com/estruyf/vscode-front-matter/issues/788): Show a warning on setting update when it exists in an extended configuration
|
||||
- [#798](https://github.com/estruyf/vscode-front-matter/issues/798): Changed dialog to slide-over for the snippet forms
|
||||
- [#799](https://github.com/estruyf/vscode-front-matter/issues/799): Added `frontMatter.logging` setting to define the logging output. Options are `info`, `warn`, `error`, and `verbose`. The default is `info`.
|
||||
- [#800](https://github.com/estruyf/vscode-front-matter/issues/800): Add colors for the Front Matter CMS output
|
||||
- [#808](https://github.com/estruyf/vscode-front-matter/issues/808): Add support to generate field groups and `block` fields in content type generation
|
||||
- [#810](https://github.com/estruyf/vscode-front-matter/issues/810): Update the tab title based on the view
|
||||
- [#811](https://github.com/estruyf/vscode-front-matter/issues/811): Added `panel.gitActions` view mode option to hide the Git actions in the panel
|
||||
- [#812](https://github.com/estruyf/vscode-front-matter/issues/812): Added the `{{locale}}` placeholder, which can be used in the `previewPath` property
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- [#802](https://github.com/estruyf/vscode-front-matter/issues/802): Update `glob` to the latest version and remove the sync method
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#796](https://github.com/estruyf/vscode-front-matter/issues/796): Fix issue in retrieving folders/files on dashboard load
|
||||
- [#801](https://github.com/estruyf/vscode-front-matter/issues/801): Faster folder processing on updates
|
||||
- [#804](https://github.com/estruyf/vscode-front-matter/issues/804): Fix blinking of the front matter content area
|
||||
- [#806](https://github.com/estruyf/vscode-front-matter/issues/804): Fix preview URL for `index.md` files in root of the page folder path
|
||||
- [#809](https://github.com/estruyf/vscode-front-matter/issues/809): Fix retrieving the `filePrefix` when updating the file name on slug change
|
||||
|
||||
## [10.1.0] - 2024-04-11 - [Release notes](https://beta.frontmatter.codes/updates/v10.1.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#671](https://github.com/estruyf/vscode-front-matter/issues/671): Command bar for contents and media dashboard
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#773](https://github.com/estruyf/vscode-front-matter/issues/773): Added the ability to rename content files
|
||||
- [#777](https://github.com/estruyf/vscode-front-matter/issues/777): Show an error in the metadata panel if something went wrong while parsing the front matter
|
||||
- [#778](https://github.com/estruyf/vscode-front-matter/issues/778): Added the ability to open a file or webpage when custom scripts is completed
|
||||
- [#783](https://github.com/estruyf/vscode-front-matter/issues/783): Always show the custom panel view
|
||||
- [#785](https://github.com/estruyf/vscode-front-matter/issues/785): Adding common actions at the bottom of the content and media cards
|
||||
- [#787](https://github.com/estruyf/vscode-front-matter/issues/787): Support for glob patterns in the page folder paths
|
||||
- [#790](https://github.com/estruyf/vscode-front-matter/pull/790): Updated Japanese translations thanks to [mayumihara](https://github.com/mayumih387)
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#716](https://github.com/estruyf/vscode-front-matter/issues/716): Fix `dataFile` dropdown class
|
||||
- [#768](https://github.com/estruyf/vscode-front-matter/issues/768): Update broken link to the documentation
|
||||
- [#771](https://github.com/estruyf/vscode-front-matter/issues/771): Fix lowercase `data` tab label
|
||||
- [#782](https://github.com/estruyf/vscode-front-matter/issues/782): Fix for setting the correct view when inserting media or snippets
|
||||
- [#786](https://github.com/estruyf/vscode-front-matter/issues/786): Remove on startup as VSCode now triggers on known commands
|
||||
|
||||
## [10.0.2] - 2024-03-01
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#769](https://github.com/estruyf/vscode-front-matter/issues/769): Fix to remove internal properties for content folders
|
||||
|
||||
## [10.0.1] - 2024-02-28
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#766](https://github.com/estruyf/vscode-front-matter/issues/766): Fix for snippet placeholder retrieval
|
||||
|
||||
## [10.0.0] - 2024-02-28 - [Release notes](https://beta.frontmatter.codes/updates/v10.0.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
@@ -18,6 +89,8 @@
|
||||
- [#741](https://github.com/estruyf/vscode-front-matter/issues/741): Added message on the content dashboard when content is processed
|
||||
- [#747](https://github.com/estruyf/vscode-front-matter/issues/747): The `@frontmatter/extensibility` dependency now supports scripts for placeholders
|
||||
- [#752](https://github.com/estruyf/vscode-front-matter/issues/752): Placeholder support in default `list` field values
|
||||
- Support for using the `fieldCollection` field in a `block` field
|
||||
- Updated the list of commands which are available in the command palette
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
|
||||
@@ -182,17 +182,54 @@ You can open showcase issues for the following things:
|
||||
## 🖤 Backers & Sponsors 👇 🤘
|
||||
|
||||
<p align="center">
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" />
|
||||
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center" title="Powered by Vercel">
|
||||
<a href="https://run.events/?utm_source=frontmatter&utm_campaign=oss">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/runevents-purple.webp" alt="run.events - Event Management Platform" height="50px" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center" title="Powered by Vercel">
|
||||
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" height="44px" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center">
|
||||
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" />
|
||||
</a>
|
||||
<a href="http://bejs.io/" title="Supported by the BEJS Community">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/bejs-community.png" alt="Supported by the BEJS Community" height="50px"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 📊 Telemetry
|
||||
|
||||
The Front Matter CMS extension collects telemetry data to help us build a better understand which features from the CMS are used. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry), or you can only disable it for the extension by configuring the `frontMatter.telemetry.disable` setting.
|
||||
|
||||
We only collect the following data:
|
||||
|
||||
- Type of event
|
||||
- Extension title (main or beta)
|
||||
- Extension version
|
||||
|
||||
No user-specific data is collected, you can check the telemetry implementation in the following files:
|
||||
|
||||
- [Telemetry class](https://github.com/estruyf/vscode-front-matter/blob/59528a3db01be8d34dc40638e6cf827090e31986/src/helpers/Telemetry.ts)
|
||||
- [Metrics API](https://github.com/FrontMatter/web-documentation-nextjs/blob/main/pages/api/metrics.ts)
|
||||
|
||||
For crash reports in the webviews, we make use of Sentry to help us understand what went wrong. This data is only used to fix issues and improve the extension. You can find more information about the Sentry implementation in the following files:
|
||||
|
||||
- [Sentry config](https://github.com/estruyf/vscode-front-matter/blob/63e296d62f11be73ac86d9e823084247952a7ddc/src/utils/sentryInit.ts)
|
||||
|
||||
> The user ip address is not collected.
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
31
README.md
@@ -185,9 +185,17 @@ You can open showcase issues for the following things:
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center" title="Powered by Vercel">
|
||||
<a href="https://run.events/?utm_source=frontmatter&utm_campaign=oss">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/runevents-purple.webp" alt="run.events - Event Management Platform" height="50px" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p align="center" title="Powered by Vercel">
|
||||
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
|
||||
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" />
|
||||
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" height="44px" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -199,6 +207,27 @@ You can open showcase issues for the following things:
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 📊 Telemetry
|
||||
|
||||
The Front Matter CMS extension collects telemetry data to help us build a better understand which features from the CMS are used. The extension respects the `telemetry.enableTelemetry` setting which you can learn more about in the [Visual Studio Code FAQ](https://aka.ms/vscode-remote/telemetry), or you can only disable it for the extension by configuring the `frontMatter.telemetry.disable` setting.
|
||||
|
||||
We only collect the following data:
|
||||
|
||||
- Type of event
|
||||
- Extension title (main or beta)
|
||||
- Extension version
|
||||
|
||||
No user-specific data is collected, you can check the telemetry implementation in the following files:
|
||||
|
||||
- [Telemetry class](https://github.com/estruyf/vscode-front-matter/blob/59528a3db01be8d34dc40638e6cf827090e31986/src/helpers/Telemetry.ts)
|
||||
- [Metrics API](https://github.com/FrontMatter/web-documentation-nextjs/blob/main/pages/api/metrics.ts)
|
||||
|
||||
For crash reports in the webviews, we make use of Sentry to help us understand what went wrong. This data is only used to fix issues and improve the extension. You can find more information about the Sentry implementation in the following files:
|
||||
|
||||
- [Sentry config](https://github.com/estruyf/vscode-front-matter/blob/63e296d62f11be73ac86d9e823084247952a7ddc/src/utils/sentryInit.ts)
|
||||
|
||||
> The user ip address is not collected.
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
17
SUPPORT.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Support
|
||||
|
||||
This article provides information on how to get support for Front Matter CMS.
|
||||
|
||||
> 👉 Note: before participating in our community, please read our [code of conduct](./CODE_OF_CONDUCT.md). By interacting with this repository, organization, or community you agree to abide by its terms.
|
||||
|
||||
## Asking for help
|
||||
|
||||
There are a few different ways to ask for help with Front Matter CMS:
|
||||
|
||||
1. **GitHub Discussions**: You can ask questions and share your experiences in the [Discussions](https://github.com/estruyf/vscode-front-matter/discussions) section of this repository.
|
||||
2. **GitHub Issues**: If you encounter a bug or have a feature request, you can open an issue in the [Issues](https://github.com/estruyf/vscode-front-matter/issues) section of this repository.
|
||||
3. **Discord**: You can join our [Discord](https://discord.gg/JBVtNMsJFB) server and ask your questions there.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute to Front Matter CMS, please read our [contributing guide](./CONTRIBUTING.md).
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#C5C5C5" width="16" height="16">
|
||||
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#C5C5C5" width="16" height="16" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 555 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#424242" width="16" height="16">
|
||||
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#424242" width="16" height="16" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 0 1 2.25-2.25h16.5A2.25 2.25 0 0 1 22.5 6v12a2.25 2.25 0 0 1-2.25 2.25H3.75A2.25 2.25 0 0 1 1.5 18V6ZM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0 0 21 18v-1.94l-2.69-2.689a1.5 1.5 0 0 0-2.12 0l-.88.879.97.97a.75.75 0 1 1-1.06 1.06l-5.16-5.159a1.5 1.5 0 0 0-2.12 0L3 16.061Zm10.125-7.81a1.125 1.125 0 1 1 2.25 0 1.125 1.125 0 0 1-2.25 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 555 B |
@@ -1,3 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#C5C5C5" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#C5C5C5" width="16" height="16" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M8.128 9.155a3.751 3.751 0 1 1 .713-1.321l1.136.656a.75.75 0 0 1 .222 1.104l-.006.007a.75.75 0 0 1-1.032.157 1.421 1.421 0 0 0-.113-.072l-.92-.531Zm-4.827-3.53a2.25 2.25 0 0 1 3.994 2.063.756.756 0 0 0-.122.23 2.25 2.25 0 0 1-3.872-2.293ZM13.348 8.272a5.073 5.073 0 0 0-3.428 3.57 5.08 5.08 0 0 0-.165 1.202 1.415 1.415 0 0 1-.707 1.201l-.96.554a3.751 3.751 0 1 0 .734 1.309l13.729-7.926a.75.75 0 0 0-.181-1.374l-.803-.215a5.25 5.25 0 0 0-2.894.05l-5.325 1.629Zm-9.223 7.03a2.25 2.25 0 1 0 2.25 3.897 2.25 2.25 0 0 0-2.25-3.897ZM12 12.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||
<path d="M16.372 12.615a.75.75 0 0 1 .75 0l5.43 3.135a.75.75 0 0 1-.182 1.374l-.802.215a5.25 5.25 0 0 1-2.894-.051l-5.147-1.574a.75.75 0 0 1-.156-1.367l3-1.732Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 939 B |
@@ -1,3 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#424242" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" fill="#424242" width="16" height="16" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M8.128 9.155a3.751 3.751 0 1 1 .713-1.321l1.136.656a.75.75 0 0 1 .222 1.104l-.006.007a.75.75 0 0 1-1.032.157 1.421 1.421 0 0 0-.113-.072l-.92-.531Zm-4.827-3.53a2.25 2.25 0 0 1 3.994 2.063.756.756 0 0 0-.122.23 2.25 2.25 0 0 1-3.872-2.293ZM13.348 8.272a5.073 5.073 0 0 0-3.428 3.57 5.08 5.08 0 0 0-.165 1.202 1.415 1.415 0 0 1-.707 1.201l-.96.554a3.751 3.751 0 1 0 .734 1.309l13.729-7.926a.75.75 0 0 0-.181-1.374l-.803-.215a5.25 5.25 0 0 0-2.894.05l-5.325 1.629Zm-9.223 7.03a2.25 2.25 0 1 0 2.25 3.897 2.25 2.25 0 0 0-2.25-3.897ZM12 12.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||
<path d="M16.372 12.615a.75.75 0 0 1 .75 0l5.43 3.135a.75.75 0 0 1-.182 1.374l-.802.215a5.25 5.25 0 0 1-2.894-.051l-5.147-1.574a.75.75 0 0 1-.156-1.367l3-1.732Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 939 B |
@@ -247,14 +247,6 @@
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.table__cell {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table__title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.table__cell__seo_details {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -281,11 +273,6 @@
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.seo__status__note {
|
||||
font-size: 10px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
/* Fields */
|
||||
.field__toggle {
|
||||
position: relative;
|
||||
@@ -364,7 +351,7 @@ input:checked + .field__toggle__slider:before {
|
||||
}
|
||||
|
||||
/* File list */
|
||||
.file_list vscode-label {
|
||||
.file_list label {
|
||||
border-bottom: 1px solid var(--vscode-foreground);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
"dashboard.header.tabs.contents": "Contenus",
|
||||
"dashboard.header.tabs.media": "Médias",
|
||||
"dashboard.header.tabs.snippets": "Snippets",
|
||||
"dashboard.header.tabs.data": "données",
|
||||
"dashboard.header.tabs.data": "Données",
|
||||
"dashboard.header.tabs.taxonomies": "Taxonomies",
|
||||
"dashboard.header.viewSwitch.toGrid": "Afficher en grille",
|
||||
"dashboard.header.viewSwitch.toList": "Afficher en liste",
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
"dashboard.header.tabs.contents": "Contenuto",
|
||||
"dashboard.header.tabs.media": "Media",
|
||||
"dashboard.header.tabs.snippets": "Snippets",
|
||||
"dashboard.header.tabs.data": "dati",
|
||||
"dashboard.header.tabs.data": "Dati",
|
||||
"dashboard.header.tabs.taxonomies": "Tassonomie",
|
||||
"dashboard.header.viewSwitch.toGrid": "Passa alla griglia",
|
||||
"dashboard.header.viewSwitch.toList": "Passa all'elenco",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"common.edit": "編集",
|
||||
"common.delete": "削除",
|
||||
"common.cancel": "キャンセル",
|
||||
"common.clear": "クリア",
|
||||
"common.apply": "適用",
|
||||
"common.clear": "クリア",
|
||||
"common.clear.value": "値をクリア",
|
||||
"common.search": "検索",
|
||||
"common.save": "保存",
|
||||
@@ -35,6 +35,16 @@
|
||||
"common.no": "いいえ",
|
||||
"common.openSettings": "設定を開く",
|
||||
"common.back": "戻る",
|
||||
"common.open": "開く",
|
||||
"common.openWithValue": "開く: {0}",
|
||||
"common.openCustomActions": "カスタムコマンドを開く",
|
||||
"common.view": "表示",
|
||||
"common.translate": "翻訳する",
|
||||
"common.languages": "言語",
|
||||
"common.scripts": "スクリプト",
|
||||
"common.rename": "ファイル名を変更する",
|
||||
|
||||
"loading.initPages": "記事を読み込んでいます",
|
||||
|
||||
"notifications.outputChannel.link": "出力ウィンドウ",
|
||||
"notifications.outputChannel.description": "詳細は{0}を確認してください。",
|
||||
@@ -42,18 +52,36 @@
|
||||
"settings.view.common": "一般",
|
||||
"settings.view.contentFolders": "記事フォルダー",
|
||||
"settings.view.astro": "Astro",
|
||||
"settings.view.integration": "統合機能",
|
||||
|
||||
"settings.openOnStartup": "起動時にダッシュボードを開く",
|
||||
"settings.contentTypes": "記事タイプ",
|
||||
"settings.contentFolders": "記事フォルダー",
|
||||
"settings.diagnostic": "診断",
|
||||
"settings.diagnostic.description": "診断プログラムを実行して、Front Matter CMS構成全体を確認できます。",
|
||||
"settings.diagnostic.link": "完全診断を実行する",
|
||||
"settings.git": "Git同期",
|
||||
"settings.git.enabled": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。",
|
||||
"settings.git.commitMessage": "コミットメッセージ",
|
||||
"settings.git.submoduleInfo": "Gitサブモジュールを使用している場合は、サブモジュールの設定についてドキュメントを参照してください。",
|
||||
"settings.git.submoduleLink": "Gitサブモジュールについて確認する",
|
||||
"settings.integration.title": "統合機能",
|
||||
|
||||
"settings.commonSettings.website.title": "ウェブサイトとSSGの設定",
|
||||
"settings.commonSettings.previewUrl": "プレビュー用URL",
|
||||
"settings.commonSettings.websiteUrl": "ウェブサイトのURL",
|
||||
"settings.commonSettings.startCommand": "SSG/フレームワーク起動コマンド",
|
||||
|
||||
"settings.integrationsView.deepl.title": "DeepL",
|
||||
"settings.integrationsView.deepl.intput.label": "API key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "DeepL API keyを入力",
|
||||
|
||||
"settings.integrationsView.azure.title": "Azure AI Translator",
|
||||
"settings.integrationsView.azure.intput.label": "サブスクリプションキー",
|
||||
"settings.integrationsView.azure.intput.placeholder": "Azure AI Translatorのサブスクリプションキーを入力",
|
||||
"settings.integrationsView.azure.region.label": "リージョン",
|
||||
"settings.integrationsView.azure.region.placeholder": "Azure AI Translatorのリージョンを入力 例: westeurope",
|
||||
|
||||
"developer.title": "開発モード",
|
||||
"developer.reload.title": "ダッシュボードを再読み込み",
|
||||
"developer.reload.label": "再読み込み",
|
||||
@@ -81,6 +109,8 @@
|
||||
"dashboard.contents.contentActions.menuItem.view": "開く",
|
||||
"dashboard.contents.contentActions.alert.title": "削除: {0}",
|
||||
"dashboard.contents.contentActions.alert.description": "本当に\"{0}\"を削除しますか?",
|
||||
"dashboard.contents.contentActions.translations.create": "翻訳する",
|
||||
"dashboard.contents.contentActions.translations.menu": "翻訳版",
|
||||
|
||||
"dashboard.contents.item.invalidTitle": "<無効なタイトル>",
|
||||
"dashboard.contents.item.invalidDescription": "<無効なディスクリプション>",
|
||||
@@ -108,6 +138,7 @@
|
||||
"dashboard.dataView.dataView.getStarted": "データタイプを選択して開始する",
|
||||
"dashboard.dataView.dataView.noDataFiles": "データファイルが見つかりませんでした",
|
||||
"dashboard.dataView.dataView.getStarted.link": "データファイルの利用方法について確認する",
|
||||
"dashboard.dataView.dataView.update.message": "データエントリーを更新しました。",
|
||||
|
||||
"dashboard.dataView.emptyView.heading": "最初にデータタイプを選んでください",
|
||||
|
||||
@@ -118,6 +149,13 @@
|
||||
|
||||
"dashboard.errorView.description": "ダッシュボードを一旦閉じてからやり直してください。",
|
||||
|
||||
"dashboard.filters.languageFilter.label": "ロケール",
|
||||
"dashboard.filters.languageFilter.all": "全て",
|
||||
|
||||
"dashboard.header.actionsBar.itemsSelected": "{0}件を選択中",
|
||||
"dashboard.header.actionsBar.alertDelete.title": "選択ファイルを削除",
|
||||
"dashboard.header.actionsBar.alertDelete.description": "選択したファイルを本当に削除しますか?",
|
||||
|
||||
"dashboard.header.breadcrumb.home": "ホーム",
|
||||
|
||||
"dashboard.header.clearFilters.title": "絞り込み・グループ・並べ替えを解除",
|
||||
@@ -201,10 +239,17 @@
|
||||
"dashboard.media.folderCreation.hexo.create": "Assetフォルダーを作成",
|
||||
"dashboard.media.folderCreation.folder.create": "新規フォルダーを作成",
|
||||
|
||||
"dashboard.media.folderItem.contentDirectory": "コンテンツディレクトリー",
|
||||
"dashboard.media.folderItem.publicDirectory": "Publicディレクトリー",
|
||||
|
||||
"dashboard.media.item.buttom.insert.image": "画像を挿入",
|
||||
"dashboard.media.item.buttom.insert.snippet": "スニペットを挿入",
|
||||
|
||||
"dashboard.media.item.quickAction.insert.field": "この画像を\"{0}\"フィールドに追加",
|
||||
"dashboard.media.item.quickAction.insert.markdown": "画像をMarkdown記法で挿入",
|
||||
"dashboard.media.item.quickAction.copy.path": "ファイルパスをコピー",
|
||||
"dashboard.media.item.quickAction.delete": "ファイルを削除",
|
||||
"dashboard.media.item.menuItem.view": "メタデータの詳細を表示",
|
||||
"dashboard.media.item.menuItem.edit.metadata": "メタデータを編集",
|
||||
"dashboard.media.item.menuItem.insert.image": "画像を挿入",
|
||||
"dashboard.media.item.menuItem.reveal.media": "メディアの場所を表示",
|
||||
@@ -275,6 +320,8 @@
|
||||
"dashboard.steps.stepsToGetStarted.contentFolders.information.description": "エクスプローラーでフォルダー名を右クリックして「フォルダーを登録」を選択する方法でも、フォルダーの登録が可能です。",
|
||||
"dashboard.steps.stepsToGetStarted.tags.name": "全てのタグとカテゴリーをインポート(オプション)",
|
||||
"dashboard.steps.stepsToGetStarted.tags.description": "Front Matterに記事用フォルダーが登録されました。記事から全てのタグとカテゴリーをインポートしますか?",
|
||||
"dashboard.steps.stepsToGetStarted.git.name": "Git同期を有効化しますか?",
|
||||
"dashboard.steps.stepsToGetStarted.git.description": "Git同期を有効にして、変更内容をリポジトリと簡単に同期させます。",
|
||||
"dashboard.steps.stepsToGetStarted.showDashboard.name": "ダッシュボードを開く",
|
||||
"dashboard.steps.stepsToGetStarted.showDashboard.description": "全ての設定が終わると、ダッシュボードが表示できるようになります。",
|
||||
"dashboard.steps.stepsToGetStarted.template.name": "設定用のテンプレートを使用する",
|
||||
@@ -283,6 +330,7 @@
|
||||
"dashboard.steps.stepsToGetStarted.astroContentTypes.name": "Astroコンテンツコレクションのコンテンツタイプを作成する",
|
||||
|
||||
"dashboard.taxonomyView.button.add.title": "\"{0}\"をタクソノミーに追加",
|
||||
"dashboard.taxonomyView.button.tag.title": "タグを追加",
|
||||
"dashboard.taxonomyView.button.edit.title": "\"{0}\"を編集",
|
||||
"dashboard.taxonomyView.button.merge.title": "\"{0}\"をマージ",
|
||||
"dashboard.taxonomyView.button.move.title": "他のタクソノミーへ移行",
|
||||
@@ -329,6 +377,11 @@
|
||||
"dashboard.configuration.astro.astroContentTypes.empty": "Astroコンテンツコレクションが見つかりません。",
|
||||
"dashboard.configuration.astro.astroContentTypes.description": "以下のAstroコンテンツコレクションは、コンテンツタイプを生成するために使用できます。",
|
||||
|
||||
"panel.git.gitAction.title": "変更の反映",
|
||||
"panel.git.gitAction.branch.select": "ブランチを選択",
|
||||
"panel.git.gitAction.input.placeholder": "コミットメッセージ",
|
||||
"panel.git.gitAction.button.fetch": "フェッチ",
|
||||
|
||||
"panel.contentType.contentTypeValidator.title": "記事タイプ",
|
||||
"panel.contentType.contentTypeValidator.hint": "記事タイプのフィールドは設定と異なります。この記事の記事タイプを、作成・更新または設定しますか?",
|
||||
"panel.contentType.contentTypeValidator.button.create": "新しい記事タイプを作成",
|
||||
@@ -414,6 +467,7 @@
|
||||
"panel.globalSettings.action.server.placeholder": "例: {0}",
|
||||
|
||||
"panel.metadata.title": "メタデータ",
|
||||
"panel.metadata.focusProblems": "詳細を「問題」表示で確認してください。",
|
||||
|
||||
"panel.otherActions.title": "他のコマンド",
|
||||
"panel.otherActions.writingSettings.enabled": "ライティング設定が有効",
|
||||
@@ -474,6 +528,10 @@
|
||||
|
||||
"commands.article.setDate.error": "日付の表示形式の解析中に何らかの問題が発生しました。\"{0}\"の設定を確認してください。",
|
||||
"commands.article.updateSlug.error": "ファイル名を変更できませんでした。: {0}",
|
||||
"commands.article.rename.fileNotExists.error": "ファイルが存在しません。",
|
||||
"commands.article.rename.fileExists.error": "\"{0}\" というファイル名は既に存在しています。",
|
||||
"commands.article.rename.fileName.title": "ファイル名を変更: {0}",
|
||||
"commands.article.rename.fileName.prompt": "ファイル名",
|
||||
|
||||
"commands.cache.cleared": "キャッシュがクリアされました。",
|
||||
|
||||
@@ -501,6 +559,19 @@
|
||||
"commands.folders.get.notificationError.remove.action": "フォルダー設定を削除",
|
||||
"commands.folders.get.notificationError.create.action": "フォルダーを作成",
|
||||
|
||||
"commands.i18n.create.warning.noFileSelected": "ファイルが選択されていません。",
|
||||
"commands.i18n.create.warning.noFile": "ファイルが取得できませんでした。",
|
||||
"commands.i18n.create.warning.noContentType": "現在のファイルの記事タイプを取得できませんでした。",
|
||||
"commands.i18n.create.warning.noConfig": "i18nの設定が見つかりません。",
|
||||
"commands.i18n.create.error.noLocaleDefinition": "現在のファイルのロケールを取得できませんでした。",
|
||||
"commands.i18n.create.error.noLocales": "現在のファイルは利用可能なすべての言語に翻訳されています。",
|
||||
"commands.i18n.create.error.noContentFolder": "現在のファイルの記事フォルダーを指定できませんでした。",
|
||||
"commands.i18n.create.error.fileExists": "そのi18n翻訳は既に存在しています。",
|
||||
"commands.i18n.create.success.created": "\"{0}\" i18n記事ファイルを作成しました。",
|
||||
"commands.i18n.create.quickPick.title": "言語別の記事を作成",
|
||||
"commands.i18n.create.quickPick.placeHolder": "どの言語で記事を作成しますか?",
|
||||
"commands.i18n.translate.progress.title": "記事を翻訳しています...",
|
||||
|
||||
"commands.preview.panel.title": "プレビュー: {0}",
|
||||
"commands.preview.askUserToPickFolder.title": "プレビュー用の記事フォルダーを選択してください。",
|
||||
|
||||
@@ -611,9 +682,6 @@
|
||||
"helpers.extension.getVersion.changelog": "変更履歴を確認する",
|
||||
"helpers.extension.getVersion.starIt": "⭐️を付ける",
|
||||
"helpers.extension.getVersion.update.notification": "{0} が v{1} に更新されました!新機能をチェックしてください!",
|
||||
"helpers.extension.migrateSettings.deprecated.warning": "\"{0}\"及び\"{1}\"の設定は非推奨になりました。代わりに\"isPublishDate\"と\"isModifiedDate\"の日付フィールドを使用してください。",
|
||||
"helpers.extension.migrateSettings.deprecated.warning.hide": "非表示にする",
|
||||
"helpers.extension.migrateSettings.deprecated.warning.seeGuide": "移行ガイドを読む",
|
||||
"helpers.extension.migrateSettings.templates.quickPick.title": "{0} - テンプレート",
|
||||
"helpers.extension.migrateSettings.templates.quickPick.placeholder": "テンプレート機能の使用を継続しますか?",
|
||||
"helpers.extension.checkIfExtensionCanRun.warning": "Front MatterのBETA版は安定版がインストールされている場合は利用できません。BETA版のみがインストールされていることを確認してください。",
|
||||
@@ -648,6 +716,7 @@
|
||||
"helpers.questions.selectContentType.quickPick.title": "記事タイプ",
|
||||
"helpers.questions.selectContentType.quickPick.placeholder": "新規作成する記事の記事タイプを選択してください。",
|
||||
"helpers.questions.selectContentType.noSelection.warning": "記事タイプが選択されていません。",
|
||||
"helpers.questions.selectContentType.quickPick.error.noContentTypes": "このフォルダーには、一致する記事タイプが設定されていません。",
|
||||
|
||||
"helpers.seoHelper.checkLength.diagnostic.message": "記事{0}の文字数が{1}文字を超えています(現在の文字数: {2})。SEOの観点上、{1}文字以内に収めることが推奨されます。",
|
||||
|
||||
@@ -690,6 +759,7 @@
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.progress.title": "テンプレートをダウンロードして初期化しています...",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.download.error": "テンプレートのダウンロードに失敗しました。",
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.init.error": "テンプレートの初期化に失敗しました。",
|
||||
"listeners.dashboard.settingsListener.setSecretValue.message": "設定が更新されました。",
|
||||
|
||||
"listeners.dashboard.snippetListener.addSnippet.missingFields.warning": "スニペットのタイトルまたはbodyが空です。",
|
||||
"listeners.dashboard.snippetListener.addSnippet.exists.warning": "同じタイトルのスニペットが既に存在しています。",
|
||||
@@ -700,6 +770,7 @@
|
||||
"listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。",
|
||||
"listeners.panel.dataListener.aiSuggestTaxonomy.noData.error": "記事データがありません。",
|
||||
"listeners.panel.dataListener.getDataFileEntries.noDataFiles.error": "データファイルのエントリーが見つかりませんでした。",
|
||||
"listeners.panel.dataListener.pushMetadata.frontMatter.error": "front matterの解析中にエラーが発生しまいsた。ファイルの内容を確認してください。",
|
||||
|
||||
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "アクティブなエディターがありません。",
|
||||
|
||||
@@ -37,6 +37,12 @@
|
||||
"common.back": "Back",
|
||||
"common.open": "Open",
|
||||
"common.openWithValue": "Open: {0}",
|
||||
"common.openCustomActions": "Open custom actions",
|
||||
"common.view": "View",
|
||||
"common.translate": "Translate",
|
||||
"common.languages": "Languages",
|
||||
"common.scripts": "Scripts",
|
||||
"common.rename": "Rename",
|
||||
|
||||
"loading.initPages": "Loading content",
|
||||
|
||||
@@ -68,7 +74,7 @@
|
||||
|
||||
"settings.integrationsView.deepl.title": "DeepL",
|
||||
"settings.integrationsView.deepl.intput.label": "API key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "Enter your Azure Translator API key",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "Enter your Deepl API key",
|
||||
|
||||
"settings.integrationsView.azure.title": "Azure AI Translator Service",
|
||||
"settings.integrationsView.azure.intput.label": "Subscription key",
|
||||
@@ -146,6 +152,10 @@
|
||||
"dashboard.filters.languageFilter.label": "Locale",
|
||||
"dashboard.filters.languageFilter.all": "All",
|
||||
|
||||
"dashboard.header.actionsBar.itemsSelected": "{0} selected",
|
||||
"dashboard.header.actionsBar.alertDelete.title": "Delete selected files",
|
||||
"dashboard.header.actionsBar.alertDelete.description": "Are you sure you want to delete the selected files?",
|
||||
|
||||
"dashboard.header.breadcrumb.home": "Home",
|
||||
|
||||
"dashboard.header.clearFilters.title": "Clear filters, grouping, and sorting",
|
||||
@@ -199,7 +209,7 @@
|
||||
"dashboard.header.tabs.contents": "Contents",
|
||||
"dashboard.header.tabs.media": "Media",
|
||||
"dashboard.header.tabs.snippets": "Snippets",
|
||||
"dashboard.header.tabs.data": "data",
|
||||
"dashboard.header.tabs.data": "Data",
|
||||
"dashboard.header.tabs.taxonomies": "Taxonomies",
|
||||
|
||||
"dashboard.header.viewSwitch.toGrid": "Change to grid",
|
||||
@@ -229,6 +239,9 @@
|
||||
"dashboard.media.folderCreation.hexo.create": "Create post asset folder",
|
||||
"dashboard.media.folderCreation.folder.create": "Create new folder",
|
||||
|
||||
"dashboard.media.folderItem.contentDirectory": "Content directory",
|
||||
"dashboard.media.folderItem.publicDirectory": "Public directory",
|
||||
|
||||
"dashboard.media.item.buttom.insert.image": "Insert image",
|
||||
"dashboard.media.item.buttom.insert.snippet": "Insert snippet",
|
||||
|
||||
@@ -261,6 +274,8 @@
|
||||
"dashboard.preview.button.refresh.title": "Refresh",
|
||||
"dashboard.preview.button.open.title": "Open",
|
||||
|
||||
"dashboard.snippetsView.item.type.content": "Content snippet",
|
||||
"dashboard.snippetsView.item.type.media": "Media snippet",
|
||||
"dashboard.snippetsView.item.quickAction.editSnippet": "Edit snippet",
|
||||
"dashboard.snippetsView.item.quickAction.deleteSnippet": "Delete snippet",
|
||||
"dashboard.snippetsView.item.quickAction.viewSnippet": "View snippet file",
|
||||
@@ -454,6 +469,7 @@
|
||||
"panel.globalSettings.action.server.placeholder": "Example: {0}",
|
||||
|
||||
"panel.metadata.title": "Metadata",
|
||||
"panel.metadata.focusProblems": "Check the problems view for more information",
|
||||
|
||||
"panel.otherActions.title": "Other actions",
|
||||
"panel.otherActions.writingSettings.enabled": "Writing settings enabled",
|
||||
@@ -514,6 +530,10 @@
|
||||
|
||||
"commands.article.setDate.error": "Something failed while parsing the date format. Check your \"{0}\" setting.",
|
||||
"commands.article.updateSlug.error": "Failed to rename file: {0}",
|
||||
"commands.article.rename.fileNotExists.error": "The file did not exist",
|
||||
"commands.article.rename.fileExists.error": "A file with the name \"{0}\" already exists",
|
||||
"commands.article.rename.fileName.title": "Rename: {0}",
|
||||
"commands.article.rename.fileName.prompt": "File name",
|
||||
|
||||
"commands.cache.cleared": "Cache cleared",
|
||||
|
||||
@@ -698,6 +718,7 @@
|
||||
"helpers.questions.selectContentType.quickPick.title": "Content type",
|
||||
"helpers.questions.selectContentType.quickPick.placeholder": "Select the content type to create your new content",
|
||||
"helpers.questions.selectContentType.noSelection.warning": "No content type was selected.",
|
||||
"helpers.questions.selectContentType.quickPick.error.noContentTypes": "There are no matching content types configured for this folder.",
|
||||
|
||||
"helpers.seoHelper.checkLength.diagnostic.message": "Article {0} is longer than {1} characters (current length: {2}). For SEO reasons, it would be better to make it less than {1} characters.",
|
||||
|
||||
@@ -706,6 +727,7 @@
|
||||
"helpers.settingsHelper.readConfig.progress.title": "{0}: Reading dynamic config file...",
|
||||
"helpers.settingsHelper.readConfig.error": "Error reading your configuration.",
|
||||
"helpers.settingsHelper.refreshConfig.success": "Settings have been refreshed.",
|
||||
"helpers.settingsHelper.safeUpdate.warning": "Cannot update setting \"{0}\" because you've extended or split the Front Matter CMS configuration. Please manually add your changes. Check the output for the setting update.",
|
||||
|
||||
"helpers.taxonomyHelper.rename.input.title": "Rename the {0}",
|
||||
"helpers.taxonomyHelper.rename.validate.equalValue": "The new value must be different from the old one.",
|
||||
@@ -751,6 +773,7 @@
|
||||
"listeners.panel.dataListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
|
||||
"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.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
|
||||
|
||||
282
package-lock.json
generated
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.0.0",
|
||||
"version": "10.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.0.0",
|
||||
"version": "10.2.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
@@ -22,7 +21,6 @@
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/glob": "7.1.3",
|
||||
"@types/invariant": "^2.2.35",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash.omit": "^4.5.7",
|
||||
@@ -38,6 +36,7 @@
|
||||
"@types/vscode": "^1.73.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",
|
||||
@@ -54,7 +53,7 @@
|
||||
"eslint": "^8.33.0",
|
||||
"fuse.js": "6.5.3",
|
||||
"github-directory-downloader": "^1.3.6",
|
||||
"glob": "7.1.6",
|
||||
"glob": "^10.3.12",
|
||||
"gray-matter": "4.0.3",
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
@@ -85,7 +84,7 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"recoil": "^0.4.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
@@ -400,15 +399,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bendera/vscode-webview-elements": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@bendera/vscode-webview-elements/-/vscode-webview-elements-0.6.2.tgz",
|
||||
"integrity": "sha512-smtr+KvCKV2MwjVrmyvrhonpaXVpxCjTMXUQOwDwWSAQ42x5pnlpjCGElz2dljc5VHS1Mh1ovPSQ/P3jAm7vMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit-element": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
@@ -764,6 +754,21 @@
|
||||
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
|
||||
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/fast-element": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz",
|
||||
@@ -1807,16 +1812,6 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
|
||||
@@ -1919,12 +1914,6 @@
|
||||
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||
@@ -2073,6 +2062,12 @@
|
||||
"integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
@@ -2337,6 +2332,15 @@
|
||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vscode-elements/elements": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vscode-elements/elements/-/elements-1.2.0.tgz",
|
||||
"integrity": "sha512-aCsf9iEnx+PE2rRfAySjvFTSgqP4NUvHG0nOc5AxFB1FXHyG/ayYA2TN9XpT7zuO024tRAu+XoKREbRC7uAmLA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/l10n": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.14.tgz",
|
||||
@@ -3022,13 +3026,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
@@ -3036,7 +3040,7 @@
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
@@ -3569,9 +3573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -4569,17 +4573,17 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"body-parser": "1.20.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie": "0.6.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
@@ -4846,9 +4850,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5089,20 +5093,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"version": "10.3.12",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
||||
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.6",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^7.0.4",
|
||||
"path-scurry": "^1.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -5126,6 +5132,30 @@
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
|
||||
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "13.24.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
|
||||
@@ -6574,20 +6604,36 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz",
|
||||
"integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==",
|
||||
"node_modules/lit": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz",
|
||||
"integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lit-html": "^1.1.1"
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-element": "^4.0.4",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz",
|
||||
"integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz",
|
||||
"integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==",
|
||||
"dev": true
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz",
|
||||
"integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/load-json-file": {
|
||||
"version": "4.0.0",
|
||||
@@ -8497,12 +8543,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
||||
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -8513,9 +8559,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
|
||||
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
|
||||
"version": "10.2.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
|
||||
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
@@ -9137,9 +9183,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
@@ -10193,9 +10239,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/recoil": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz",
|
||||
"integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
|
||||
"integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hamt_plus": "1.0.2"
|
||||
@@ -10514,6 +10560,26 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -11405,52 +11471,6 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase/node_modules/glob": {
|
||||
"version": "10.3.10",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.5",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||
"path-scurry": "^1.10.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase/node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -11932,9 +11952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.28.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz",
|
||||
"integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==",
|
||||
"version": "5.28.4",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
|
||||
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
@@ -12558,9 +12578,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-middleware": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
|
||||
"integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
|
||||
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"colorette": "^2.0.10",
|
||||
|
||||
448
package.json
@@ -3,14 +3,15 @@
|
||||
"displayName": "Front Matter CMS",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "10.0.0",
|
||||
"version": "10.2.1",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
"color": "#0e131f",
|
||||
"theme": "dark"
|
||||
},
|
||||
"badges": [{
|
||||
"badges": [
|
||||
{
|
||||
"description": "version",
|
||||
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
|
||||
"href": "https://github.com/estruyf/vscode-front-matter"
|
||||
@@ -50,8 +51,7 @@
|
||||
},
|
||||
"activationEvents": [
|
||||
"workspaceContains:**/.frontmatter",
|
||||
"workspaceContains:**/frontmatter.json",
|
||||
"onStartupFinished"
|
||||
"workspaceContains:**/frontmatter.json"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
@@ -70,10 +70,17 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [{
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
{
|
||||
"command": "workbench.action.webview.reloadWebviewAction",
|
||||
"key": "ctrl+r",
|
||||
"mac": "cmd+r",
|
||||
"when": "activeWebviewPanelId == frontMatterDashboard"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertMedia",
|
||||
"key": "ctrl+shift+i",
|
||||
@@ -88,19 +95,23 @@
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}]
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"frontmatter-explorer": [{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}]
|
||||
"frontmatter-explorer": [
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "%settings.configuration.title%",
|
||||
@@ -168,7 +179,8 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -184,7 +196,8 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -285,6 +298,10 @@
|
||||
"default": null,
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.previewPath.description%"
|
||||
},
|
||||
"trailingSlash": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description%"
|
||||
},
|
||||
"filePrefix": {
|
||||
"type": [
|
||||
"null",
|
||||
@@ -532,7 +549,8 @@
|
||||
"categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [{
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"contentFolders",
|
||||
@@ -605,7 +623,8 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -801,7 +820,8 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -855,7 +875,8 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -1096,26 +1117,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.media.supportedMimeTypes": {
|
||||
@@ -1166,6 +1190,12 @@
|
||||
"markdownDescription": "%setting.frontMatter.preview.pathName.markdownDescription%",
|
||||
"scope": "Site preview"
|
||||
},
|
||||
"frontMatter.preview.trailingSlash": {
|
||||
"type": "boolean",
|
||||
"default": "",
|
||||
"markdownDescription": "%setting.frontMatter.preview.trailingSlash.markdownDescription%",
|
||||
"scope": "Site preview"
|
||||
},
|
||||
"frontMatter.site.baseURL": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -1345,7 +1375,8 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1539,7 +1570,8 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1710,6 +1742,10 @@
|
||||
"default": null,
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description%"
|
||||
},
|
||||
"trailingSlash": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description%"
|
||||
},
|
||||
"slugTemplate": {
|
||||
"type": [
|
||||
"null",
|
||||
@@ -1747,48 +1783,51 @@
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
@@ -1801,7 +1840,8 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1983,10 +2023,21 @@
|
||||
"frontMatter.website.host": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%setting.frontMatter.website.host.markdownDescription%"
|
||||
},
|
||||
"frontMatter.logging": {
|
||||
"type": "string",
|
||||
"default": "info",
|
||||
"enum": [
|
||||
"error",
|
||||
"warn",
|
||||
"info",
|
||||
"verbose"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [{
|
||||
"commands": [
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2312,16 +2363,21 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"submenus": [{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}],
|
||||
"submenus": [
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"webview/context": [{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}],
|
||||
"editor/title": [{
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
@@ -2407,11 +2463,14 @@
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}],
|
||||
"frontmatter.submenu": [{
|
||||
"explorer/context": [
|
||||
{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}
|
||||
],
|
||||
"frontmatter.submenu": [
|
||||
{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "explorerResourceIsFolder",
|
||||
"group": "frontmatter@1"
|
||||
@@ -2427,7 +2486,8 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [{
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2435,14 +2495,6 @@
|
||||
"command": "frontMatter.project.switch",
|
||||
"when": "frontMatter:project:switch:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "!frontMatterCanInit"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.preview",
|
||||
"when": "frontMatterCanOpenPreview"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.data",
|
||||
"when": "frontMatter:dashboard:data:enabled"
|
||||
@@ -2459,10 +2511,26 @@
|
||||
"command": "frontMatter.i18n.create",
|
||||
"when": "frontMatter:i18n:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.authenticate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.remap",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertTags",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertCategories",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.registerFolder",
|
||||
"when": "false"
|
||||
@@ -2523,10 +2591,38 @@
|
||||
"command": "frontMatter.markup.options",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.hyperlink",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.config.reload",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.initTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.addMissingFields",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.exportTaxonomy",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.generateSlug",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.promoteSettings",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertSnippet",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
@@ -2547,14 +2643,6 @@
|
||||
"command": "frontMatter.insertCategories",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertTags",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.createTemplate",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.preview",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
@@ -2571,16 +2659,13 @@
|
||||
"command": "frontMatter.contenttype.generate",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.addMissingFields",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.setContentType",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [{
|
||||
"view/title": [
|
||||
{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2612,52 +2697,70 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"grammars": [{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
}],
|
||||
"walkthroughs": [{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
"languages": [
|
||||
{
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "frontmatter.project.output",
|
||||
"scopeName": "frontmatter.project.output",
|
||||
"path": "./syntaxes/frontmatter-output.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [
|
||||
{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
|
||||
@@ -2683,7 +2786,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@bendera/vscode-webview-elements": "0.6.2",
|
||||
"@estruyf/vscode": "^1.1.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
@@ -2692,7 +2794,6 @@
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/glob": "7.1.3",
|
||||
"@types/invariant": "^2.2.35",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash.omit": "^4.5.7",
|
||||
@@ -2708,6 +2809,7 @@
|
||||
"@types/vscode": "^1.73.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",
|
||||
@@ -2724,7 +2826,7 @@
|
||||
"eslint": "^8.33.0",
|
||||
"fuse.js": "6.5.3",
|
||||
"github-directory-downloader": "^1.3.6",
|
||||
"glob": "7.1.6",
|
||||
"glob": "^10.3.12",
|
||||
"gray-matter": "4.0.3",
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
@@ -2755,7 +2857,7 @@
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"recoil": "^0.4.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.8",
|
||||
@@ -2786,4 +2888,4 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"command.frontMatter.markup.unorderedlist": "順序なしリスト",
|
||||
"command.frontMatter.git.sync": "同期",
|
||||
"command.frontMatter.cache.clear": "キャッシュをクリア",
|
||||
"command.frontMatter.i18n.create": "新しい翻訳を作成",
|
||||
"settings.configuration.title": "Front Matter: チームで作業する場合はfrontmatter.jsonで設定してください。",
|
||||
"setting.frontMatter.projects.markdownDescription": "Front Matter CMSを利用するプロジェクトを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "プロジェクトの名前を指定します。",
|
||||
@@ -60,7 +61,7 @@
|
||||
"setting.frontMatter.content.defaultFileType.markdownDescription": "新しい記事を作成する際の既定のファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultfiletype)",
|
||||
"setting.frontMatter.content.defaultSorting.markdownDescription": "ダッシュボード上に表示される記事一覧の既定の並び順を設定します。Enum(列挙型)や任意のIDを指定してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultsorting)",
|
||||
"setting.frontMatter.content.draftField.markdownDescription": "記事の下書きステータスを管理するフィールドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.draftfield)",
|
||||
"setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの種類",
|
||||
"setting.frontMatter.content.draftField.properties.type.description": "使用する下書きフィールドの型",
|
||||
"setting.frontMatter.content.draftField.properties.name.description": "使用するフィールドの名前",
|
||||
"setting.frontMatter.content.draftField.properties.invert.description": "既定では、記事が下書きの場合、下書きフィールドは true に設定されます。これを true に設定すると、false に設定されます。",
|
||||
"setting.frontMatter.content.draftField.properties.choices.description": "フィールドの選択肢のリスト",
|
||||
@@ -75,6 +76,12 @@
|
||||
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "現在の場所に使用できる記事タイプを定義します。定義しない場合は、全ての記事タイプを使用できます。",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "フォルダー内の新しい記事の作成を無効にします。",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "ページフォルダー用デフォルトのロケールIDを設定します。このフォルダー内の全ての記事が`frontMatter.content.i18n`で設定された言語へ翻訳可能になります。",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.locales.description": "ページフォルダーで利用するロケールを設定します。この設定は記事の翻訳に使用されます。",
|
||||
"setting.frontMatter.content.i18n.markdownDescription": "ウェブサイトで利用するロケールを設定します。この設定はページフォルダーレベルの設定より優先されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)",
|
||||
"setting.frontMatter.content.i18n.items.properties.title.description": "言語名",
|
||||
"setting.frontMatter.content.i18n.items.properties.locale.description": "言語コード",
|
||||
"setting.frontMatter.content.i18n.items.properties.path.description": "言語フォルダーの相対パス",
|
||||
"setting.frontMatter.content.placeholders.markdownDescription": "記事タイプとテンプレートで使用するプレースホルダーを配列で定義して、記事のfront matterを自動で入力できるようにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.placeholders)",
|
||||
"setting.frontMatter.content.placeholders.items.properties.id.description": "プレースホルダーのIDを記事タイプまたはテンプレートで、次のように使用します: {{placeholder}}",
|
||||
"setting.frontMatter.content.placeholders.items.properties.value.description": "プレースホルダーの値",
|
||||
@@ -92,6 +99,7 @@
|
||||
"setting.frontMatter.content.sorting.items.properties.type.description": "フィールド値の型",
|
||||
"setting.frontMatter.content.supportedFileTypes.markdownDescription": "Front Matterでサポートされるファイル形式を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.supportedfiletypes)",
|
||||
"setting.frontMatter.content.wysiwyg.markdownDescription": "What You See, Is What You Get(WYSIWYG)Markdownコントロールを有効にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.wysiwyg)",
|
||||
"setting.frontMatter.content.filters.markdownDescription": "ダッシュボードで利用するフィルターを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.content.filters)",
|
||||
"setting.frontMatter.custom.scripts.markdownDescription": "実行するNode.jsスクリプトのパスを指定します。現在のファイルのパスが引数として渡されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.custom.scripts)",
|
||||
"setting.frontMatter.custom.scripts.items.properties.id.description": "スクリプトの ID。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.title.description": "スクリプトに付けるタイトル。ボタンのタイトルとして表示されます。",
|
||||
@@ -100,8 +108,8 @@
|
||||
"setting.frontMatter.custom.scripts.items.properties.bulk.description": "全ての記事ファイルに対してスクリプトを実行する",
|
||||
"setting.frontMatter.custom.scripts.items.properties.output.description": "スクリプト出力を出力する場所を定義します。デフォルトは通知表示ですが、エディターパネルに表示するように指定できます。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.outputType.description": "エディター・パネルの出力のタイプ。たとえば、「マークダウン」に変更するために使用できます",
|
||||
"setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.type.description": "スクリプトが使用される型",
|
||||
"setting.frontMatter.custom.scripts.items.properties.command.description": "実行するスクリプトの種類",
|
||||
"setting.frontMatter.custom.scripts.items.properties.hidden.description": "UI からアクションを非表示にする",
|
||||
"setting.frontMatter.custom.scripts.items.properties.environments.items.properties.type.description": "スクリプトを使用する必要がある環境タイプ",
|
||||
"setting.frontMatter.custom.scripts.items.properties.environments.items.properties.script.description": "実行するスクリプトへのパス",
|
||||
@@ -152,7 +160,18 @@
|
||||
"setting.frontMatter.global.notifications.markdownDescription": "表示したい通知を設定します。既定では全ての通知が表示されます。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.notifications)",
|
||||
"setting.frontMatter.global.disabledNotifications.markdownDescription": "これは、Front Matter CMSで無効にできる通知タイプの配列です。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications)",
|
||||
"setting.frontMatter.media.defaultSorting.markdownDescription": "ダッシュボードのメディア一覧での既定の並び順を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアファイルでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "メディアコンテンツでサポートされるMIMEタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes)",
|
||||
|
||||
"setting.frontMatter.media.contentTypes.markdownDescription": "Front Matterで利用するメディアコンテンツのタイプを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.media.contenttypes)",
|
||||
"setting.frontMatter.media.contentTypes.items.description": "Front Matterで利用するメディアフコンテンツのタイプについての説明。",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.name.description": "メディアコンテンツのタイプ名",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fileTypes.description": "利用可能とするメディアコンテンツのタイプの指定",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.description": "メディアコンテンツのフィールドを設定します。",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description": "UI表示用のタイトル",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description": "フィールドの名前",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description": "フィールド値の型",
|
||||
"setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description": "単一行フィールド",
|
||||
|
||||
"setting.frontMatter.panel.freeform.markdownDescription": "未登録のタグ/カテゴリーをタグピッカーに入力可能にするかどうかを設定します(有効にすると、後で保存するオプションが使えます)。規定値はtrue。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "パネル内で非表示にしたいコマンドを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "プレビュー表示に使用するホストのURLを設定します(例:`http://localhost:1313`)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
|
||||
@@ -164,11 +183,11 @@
|
||||
"setting.frontMatter.taxonomy.commaSeparatedFields.items.description": "コンマ区切りの配列として使用するフィールドの名前。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.markdownDescription": "記事・ページ・その他で利用したい記事タイプを設定します。front matterで正しく`type`が設定されていることを確認してください。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.contenttypes)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.description": "Front Matterで使用する記事タイプを定義します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義する",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.name.description": "フィールドの種類を定義します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fileType.description": "作成する記事タイプを指定します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義する",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.description": "記事タイプのフィールドを定義します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.description": "Front Matterで使用する記事タイプを定義します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールドの種類を定義する",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.type.description": "フィールド値の型",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.name.description": "使用するフィールドの名前",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.title.description": "UI に表示するタイトル",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.description.description": "UI に表示する説明",
|
||||
@@ -176,8 +195,8 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.description": "選択肢を定義する",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "選択肢 ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "選択肢のタイトル",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールドである",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) である",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "単一行フィールド",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "WYSIWYG フィールド (HTML 出力) ",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "複数の値を選択できますか?",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "画像フィールドをプレビューとして使用できるかどうかを指定します。記事タイプごとに使用できるプレビュー画像は1つだけです。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "メタデータ セクションからフィールドを非表示にしますか?",
|
||||
@@ -210,6 +229,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "比較で大文字と小文字を区別するかどうかを指定します。デフォルト: true",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "新しい記事を作成するときにフォルダーを作成するかどうかを指定します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "記事タイプのカスタム プレビュー パスを定義します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "記事タイプのカスタムスラッグのテンプレートを設定します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "新しい記事の作成に使用できるオプションのテンプレート。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "新しい記事の作成後に使用できるオプションのポストスクリプト。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.filePrefix.description": "ファイル名の接頭辞を定義します。",
|
||||
@@ -236,6 +256,7 @@
|
||||
"setting.frontMatter.taxonomy.seoTitleLength.markdownDescription": "SEOに適したタイトルの文字数を設定します。(`-1`に設定するとオフになります。)[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlelength)",
|
||||
"setting.frontMatter.taxonomy.slugPrefix.markdownDescription": "スラッグに付与する接頭辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugprefix)",
|
||||
"setting.frontMatter.taxonomy.slugSuffix.markdownDescription": "スラッグに付与する接尾辞を設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugsuffix)",
|
||||
"setting.frontMatter.taxonomy.slugTemplate.markdownDescription": "記事を作成する際のカスタムスラッグのテンプレートを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugtemplate)",
|
||||
"setting.frontMatter.taxonomy.tags.markdownDescription": "Front Matterで利用するタグを管理します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags)",
|
||||
"setting.frontMatter.telemetry.disable.markdownDescription": "利用状況の送信をオフにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable)",
|
||||
"setting.frontMatter.templates.enabled.markdownDescription": "テンプレート機能を利用します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled)",
|
||||
@@ -250,5 +271,8 @@
|
||||
"command.frontMatter.settings.refresh": "Front Matterの設定の再読み込み",
|
||||
"setting.frontMatter.config.dynamicFilePath.markdownDescription": "動的構成ファイルへのパスを指定します (例: [[ワークスペース]]/config.js)。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "記事をサブコンテンツとして作成可能かどうかを設定します。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。"
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "記事をサブコンテンツとして作成するかどうかを設定します。",
|
||||
|
||||
"setting.frontMatter.git.disableOnBranches.markdownDescription": "Git Actionsを無効化したいブランチを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches)",
|
||||
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "特定のブランチを更新する場合にコミットメッセージを必須にします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage)"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"command.frontMatter.initTemplate": "Initialize the template folder",
|
||||
"command.frontMatter.createTemplate": "Create template from current file",
|
||||
"command.frontMatter.createCategory": "Create category",
|
||||
"command.frontMatter.createContent": "Create new content from defined content type or template",
|
||||
"command.frontMatter.createContent": "Create new content",
|
||||
"command.frontMatter.createTag": "Create tag",
|
||||
"command.frontMatter.diagnostics": "Diagnostic logging",
|
||||
"command.frontMatter.exportTaxonomy": "Export all tags & categories to your settings",
|
||||
@@ -73,11 +73,13 @@
|
||||
"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.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.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.contentTypes.description": "Defines which content types can be used for the current location. If not defined, all content types will be available.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.disableCreation.description": "Disable the creation of new content in the folder.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.defaultLocale.description": "Set the default locale ID for the page folder. All content from this folder is translatable to the languages defined in the `frontMatter.content.i18n` setting.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.locales.description": "Define the locales for the page folder. This will be used for the translation of the content.",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.slugTemplate.description": "Defines a custom slug template.",
|
||||
"setting.frontMatter.content.i18n.markdownDescription": "Specify the locales you want to use for your website. This setting can be overwritten on page folder level. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n)",
|
||||
"setting.frontMatter.content.i18n.items.properties.title.description": "Title of the locale",
|
||||
"setting.frontMatter.content.i18n.items.properties.locale.description": "Locale code",
|
||||
@@ -175,6 +177,7 @@
|
||||
"setting.frontMatter.panel.freeform.markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "Specify the actions you want to disable in the panel. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "Specify the host URL (example: http://localhost:1313) to be used when opening the preview. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host)",
|
||||
"setting.frontMatter.preview.trailingSlash.markdownDescription": "Specify if you want to add a trailing slash to the preview URL. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.trailingslash)",
|
||||
"setting.frontMatter.preview.pathName.markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.pathname)",
|
||||
"setting.frontMatter.site.baseURL.markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.site.baseurl)",
|
||||
"setting.frontMatter.taxonomy.alignFilename.markdownDescription": "Align the filename with the new slug when it gets generated. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.alignfilename)",
|
||||
@@ -229,6 +232,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "Specify if the comparison is case sensitive. Default: true",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "Specify if you want to create a folder when creating new content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "Defines a custom preview path for the content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.slugTemplate.description": "Defines a custom slug template for the content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.template.description": "An optional template that can be used for creating new content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.postScript.description": "An optional post script that can be used after new content creation.",
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
import {
|
||||
Position,
|
||||
TextDocument,
|
||||
TextDocumentWillSaveEvent,
|
||||
TextEdit,
|
||||
Uri,
|
||||
commands,
|
||||
window,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import { Folders } from './Folders';
|
||||
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
|
||||
import { isValidFile } from './../helpers/isValidFile';
|
||||
@@ -10,13 +20,14 @@ import {
|
||||
SETTING_SLUG_PREFIX,
|
||||
SETTING_SLUG_SUFFIX,
|
||||
SETTING_CONTENT_PLACEHOLDERS,
|
||||
TelemetryEvent
|
||||
TelemetryEvent,
|
||||
SETTING_SLUG_TEMPLATE
|
||||
} from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { CustomPlaceholder, Field } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Logger,
|
||||
Settings,
|
||||
SlugHelper,
|
||||
processArticlePlaceholdersFromData,
|
||||
@@ -32,17 +43,35 @@ import { Telemetry } from '../helpers/Telemetry';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { MediaListener } from '../listeners/panel';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { Position } from 'vscode';
|
||||
import { SNIPPET } from '../constants/Snippet';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export class Article {
|
||||
/**
|
||||
* Registers the commands for the Article class.
|
||||
*
|
||||
* @param subscriptions - The array of subscriptions to register the commands with.
|
||||
*/
|
||||
public static async registerCommands(subscriptions: unknown[]) {
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate)
|
||||
);
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.generateSlug, Article.updateSlug));
|
||||
|
||||
// Inserting an image in Markdown
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia));
|
||||
|
||||
// Inserting a snippet in Markdown
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the article date
|
||||
*/
|
||||
public static async setDate() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
@@ -52,7 +81,7 @@ export class Article {
|
||||
return;
|
||||
}
|
||||
|
||||
article = this.updateDate(article);
|
||||
article = await this.updateDate(article);
|
||||
|
||||
try {
|
||||
ArticleHelper.update(editor, article);
|
||||
@@ -67,8 +96,8 @@ export class Article {
|
||||
* Update the date in the front matter
|
||||
* @param article
|
||||
*/
|
||||
public static updateDate(article: ParsedFrontMatter) {
|
||||
article.data = ArticleHelper.updateDates(article);
|
||||
public static async updateDate(article: ParsedFrontMatter) {
|
||||
article.data = await ArticleHelper.updateDates(article);
|
||||
return article;
|
||||
}
|
||||
|
||||
@@ -76,12 +105,12 @@ export class Article {
|
||||
* Sets the article lastmod date
|
||||
*/
|
||||
public static async setLastModifiedDate() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedArticle = this.setLastModifiedDateInner(editor.document);
|
||||
const updatedArticle = await this.setLastModifiedDateInner(editor.document);
|
||||
|
||||
if (typeof updatedArticle === 'undefined') {
|
||||
return;
|
||||
@@ -90,10 +119,8 @@ export class Article {
|
||||
ArticleHelper.update(editor, updatedArticle as ParsedFrontMatter);
|
||||
}
|
||||
|
||||
public static async setLastModifiedDateOnSave(
|
||||
document: vscode.TextDocument
|
||||
): Promise<vscode.TextEdit[]> {
|
||||
const updatedArticle = this.setLastModifiedDateInner(document);
|
||||
public static async setLastModifiedDateOnSave(document: TextDocument): Promise<TextEdit[]> {
|
||||
const updatedArticle = await this.setLastModifiedDateInner(document);
|
||||
|
||||
if (typeof updatedArticle === 'undefined') {
|
||||
return [];
|
||||
@@ -104,9 +131,10 @@ export class Article {
|
||||
return [update];
|
||||
}
|
||||
|
||||
private static setLastModifiedDateInner(
|
||||
document: vscode.TextDocument
|
||||
): ParsedFrontMatter | undefined {
|
||||
private static async setLastModifiedDateInner(
|
||||
document: TextDocument
|
||||
): Promise<ParsedFrontMatter | undefined> {
|
||||
Logger.verbose(`Article:setLastModifiedDateInner:Start`);
|
||||
const article = ArticleHelper.getFrontMatterFromDocument(document);
|
||||
|
||||
// Only set the date, if there is already front matter set
|
||||
@@ -115,9 +143,17 @@ export class Article {
|
||||
}
|
||||
|
||||
const cloneArticle = Object.assign({}, article);
|
||||
const dateField = ArticleHelper.getModifiedDateField(article) || DefaultFields.LastModified;
|
||||
const dateField = await ArticleHelper.getModifiedDateField(article);
|
||||
Logger.verbose(`Article:setLastModifiedDateInner:DateField - ${JSON.stringify(dateField)}`);
|
||||
|
||||
try {
|
||||
cloneArticle.data[dateField] = Article.formatDate(new Date());
|
||||
const fieldName = dateField?.name || DefaultFields.LastModified;
|
||||
const fieldValue = Article.formatDate(new Date(), dateField?.dateFormat);
|
||||
cloneArticle.data[fieldName] = fieldValue;
|
||||
Logger.verbose(
|
||||
`Article:setLastModifiedDateInner:DateField name - ${fieldName} - value - ${fieldValue}`
|
||||
);
|
||||
Logger.verbose(`Article:setLastModifiedDateInner:End`);
|
||||
return cloneArticle;
|
||||
} catch (e: unknown) {
|
||||
Notifications.error(
|
||||
@@ -158,7 +194,7 @@ export class Article {
|
||||
Telemetry.send(TelemetryEvent.generateSlug);
|
||||
|
||||
const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -170,8 +206,12 @@ export class Article {
|
||||
}
|
||||
|
||||
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
filePrefix = await ArticleHelper.getFilePrefix(
|
||||
filePrefix,
|
||||
editor.document.uri.fsPath,
|
||||
contentType
|
||||
);
|
||||
|
||||
const titleField = 'title';
|
||||
const articleTitle: string = article.data[titleField];
|
||||
@@ -217,7 +257,7 @@ export class Article {
|
||||
// Check if the file name should be updated by the slug
|
||||
// This is required for systems like Jekyll
|
||||
if (updateFileName) {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (editor) {
|
||||
const ext = extname(editor.document.fileName);
|
||||
const fileName = basename(editor.document.fileName);
|
||||
@@ -235,7 +275,7 @@ export class Article {
|
||||
try {
|
||||
await editor.document.save();
|
||||
|
||||
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
|
||||
await workspace.fs.rename(editor.document.uri, Uri.file(newPath), {
|
||||
overwrite: false
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
@@ -255,21 +295,38 @@ export class Article {
|
||||
* Retrieve the slug from the front matter
|
||||
*/
|
||||
public static getSlug() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = parseWinPath(editor.document.fileName);
|
||||
|
||||
if (!isValidFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedFile = parse(file);
|
||||
|
||||
const slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE);
|
||||
if (slugTemplate) {
|
||||
if (slugTemplate === '{{title}}') {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data?.title) {
|
||||
return article.data.title.toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
} else {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
return SlugHelper.createSlug(article.data.title, article.data, slugTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
|
||||
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
|
||||
|
||||
if (parsedFile.name.toLowerCase() !== 'index') {
|
||||
return parsedFile.name;
|
||||
return `${prefix}${parsedFile.name}${suffix}`;
|
||||
}
|
||||
|
||||
const folderName = basename(dirname(file));
|
||||
@@ -280,7 +337,7 @@ export class Article {
|
||||
* Toggle the page its draft mode
|
||||
*/
|
||||
public static async toggleDraft() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
@@ -298,13 +355,13 @@ export class Article {
|
||||
* Article auto updater
|
||||
* @param event
|
||||
*/
|
||||
public static async autoUpdate(event: vscode.TextDocumentWillSaveEvent) {
|
||||
public static async autoUpdate(event: TextDocumentWillSaveEvent) {
|
||||
const document = event.document;
|
||||
if (document && ArticleHelper.isSupportedFile(document)) {
|
||||
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
|
||||
|
||||
// Is article located in one of the content folders
|
||||
const folders = Folders.get();
|
||||
const folders = Folders.getCached();
|
||||
const documentPath = parseWinPath(document.fileName);
|
||||
const folder = folders.find((f) => documentPath.startsWith(f.path));
|
||||
if (!folder) {
|
||||
@@ -323,11 +380,16 @@ export class Article {
|
||||
public static formatDate(dateValue: Date, fieldDateFormat?: string): string {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
|
||||
Logger.verbose(`Article:formatDate:Start`);
|
||||
|
||||
if (fieldDateFormat) {
|
||||
Logger.verbose(`Article:formatDate:FieldDateFormat - ${fieldDateFormat}`);
|
||||
return format(dateValue, DateHelper.formatUpdate(fieldDateFormat) as string);
|
||||
} else if (dateFormat && typeof dateFormat === 'string') {
|
||||
Logger.verbose(`Article:formatDate:DateFormat - ${dateFormat}`);
|
||||
return format(dateValue, DateHelper.formatUpdate(dateFormat) as string);
|
||||
} else {
|
||||
Logger.verbose(`Article:formatDate:toISOString - ${dateValue}`);
|
||||
return typeof dateValue.toISOString === 'function'
|
||||
? dateValue.toISOString()
|
||||
: dateValue?.toString();
|
||||
@@ -338,19 +400,19 @@ export class Article {
|
||||
* Insert an image from the media dashboard into the article
|
||||
*/
|
||||
public static async insertMedia() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType =
|
||||
article && article.data ? ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
|
||||
article && article.data ? await ArticleHelper.getContentType(article) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
const position = editor.selection.active;
|
||||
const selectionText = editor.document.getText(editor.selection);
|
||||
|
||||
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: 'media',
|
||||
data: {
|
||||
pageBundle: !!contentType.pageBundle,
|
||||
@@ -369,7 +431,7 @@ export class Article {
|
||||
* Insert a snippet into the article
|
||||
*/
|
||||
public static async insertSnippet() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
@@ -423,9 +485,9 @@ export class Article {
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType = article ? ArticleHelper.getContentType(article) : undefined;
|
||||
const contentType = article ? await ArticleHelper.getContentType(article) : undefined;
|
||||
|
||||
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: NavigationType.Snippets,
|
||||
data: {
|
||||
fileTitle: article?.data.title || '',
|
||||
|
||||
@@ -22,20 +22,16 @@ export class Backers {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], { silent: true });
|
||||
if (githubAuth && githubAuth.accessToken) {
|
||||
try {
|
||||
const isBeta = ext.isBetaVersion();
|
||||
const response = await fetch(
|
||||
`https://${isBeta ? `beta.` : ``}frontmatter.codes/api/v2/backers`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
accept: 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: githubAuth.accessToken
|
||||
})
|
||||
}
|
||||
);
|
||||
const response = await fetch(`https://api.frontmatter.codes/v2/backers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
accept: 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: githubAuth.accessToken
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const prevData = await ext.getState<boolean>(CONTEXT.backer, 'global');
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
import { commands, QuickPickItem, window } from 'vscode';
|
||||
import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
import { Extension, Settings } from '../helpers';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export class Content {
|
||||
/**
|
||||
* Registers the commands for the Content class.
|
||||
*/
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.createContent, Content.create));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates content based on user selection.
|
||||
* If templates are enabled, shows a quick pick menu to choose between content type and template.
|
||||
* If templates are disabled, executes the createByContentType command directly.
|
||||
*/
|
||||
public static async create() {
|
||||
const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED);
|
||||
if (!templatesEnabled) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
ExtensionListener,
|
||||
SnippetListener,
|
||||
TaxonomyListener,
|
||||
LogListener,
|
||||
LocalizationListener,
|
||||
SsgListener
|
||||
} from '../listeners/dashboard';
|
||||
@@ -35,6 +34,7 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
import { ignoreMsgCommand } from '../utils';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -45,6 +45,13 @@ export class Dashboard {
|
||||
return Dashboard._viewData;
|
||||
}
|
||||
|
||||
public static setTitle(title: string) {
|
||||
if (title && Dashboard.webview) {
|
||||
Dashboard.webview.title =
|
||||
title || `Front Matter ${l10n.t(LocalizationKey.commandsDashboardTitle)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the dashboard
|
||||
*/
|
||||
@@ -223,7 +230,9 @@ export class Dashboard {
|
||||
});
|
||||
|
||||
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
|
||||
Logger.info(`Receiving message from webview: ${msg.command}`);
|
||||
if (!ignoreMsgCommand(msg.command)) {
|
||||
Logger.verbose(`Receiving message from dashboard: ${msg.command}`);
|
||||
}
|
||||
|
||||
LocalizationListener.process(msg);
|
||||
DashboardListener.process(msg);
|
||||
@@ -237,7 +246,6 @@ export class Dashboard {
|
||||
ModeListener.process(msg);
|
||||
GitListener.process(msg);
|
||||
TaxonomyListener.process(msg);
|
||||
LogListener.process(msg);
|
||||
SsgListener.process(msg);
|
||||
});
|
||||
}
|
||||
@@ -358,7 +366,7 @@ export class Dashboard {
|
||||
version.usedVersion ? '' : `data-showWelcome="true"`
|
||||
} ${
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} data-webview-url="${webviewUrl}" ></div>
|
||||
} data-webview-url="${webviewUrl}" data-is-crash-disabled="${!Telemetry.isVscodeEnabled()}" ></div>
|
||||
|
||||
${(scriptsToLoad || [])
|
||||
.map((script) => {
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { Folders } from './Folders';
|
||||
import { ViewColumn, workspace } from 'vscode';
|
||||
import { ViewColumn, commands, workspace } from 'vscode';
|
||||
import ContentProvider from '../providers/ContentProvider';
|
||||
import { join } from 'path';
|
||||
import { ContentFolder } from '../models';
|
||||
import { Settings } from '../helpers/SettingsHelper';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
DEFAULT_FILE_TYPES,
|
||||
SETTING_CONTENT_SUPPORTED_FILETYPES
|
||||
} from '../constants';
|
||||
import { Extension } from '../helpers';
|
||||
|
||||
export class Diagnostics {
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show));
|
||||
}
|
||||
|
||||
public static async show() {
|
||||
const folders = Folders.get();
|
||||
const folders = await Folders.get();
|
||||
const projectName = Folders.getProjectFolderName();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
@@ -18,27 +31,38 @@ export class Diagnostics {
|
||||
|
||||
const all = await Diagnostics.allProjectFiles();
|
||||
|
||||
const logging = `# Project name
|
||||
const fileTypes = Diagnostics.getFileTypes();
|
||||
|
||||
const logging = `# ${Extension.getInstance().displayName} - Diagnostics
|
||||
|
||||
Beta: \`${Extension.getInstance().isBetaVersion()}\`
|
||||
Version: \`${Extension.getInstance().version}\`
|
||||
|
||||
## Project name
|
||||
|
||||
${projectName}
|
||||
|
||||
# Folders
|
||||
## Workspace folder
|
||||
|
||||
${folders.map((f) => `- ${f.title}: "${f.path}"`).join('\n')}
|
||||
\`${wsFolder ? wsFolder.fsPath : 'No workspace folder'}\`
|
||||
|
||||
# Workspace folder
|
||||
|
||||
${wsFolder ? wsFolder.fsPath : 'No workspace folder'}
|
||||
|
||||
# Total files
|
||||
## Total files
|
||||
|
||||
${all}
|
||||
|
||||
# Folders to search files
|
||||
## Folders
|
||||
|
||||
| Title | Path |
|
||||
| ----- | ---- |
|
||||
${folders.map((f) => `| ${f.title} | \`${f.path}\` |`).join('\n')}
|
||||
|
||||
### Files in folders
|
||||
|
||||
| Project start length | Search in | ${fileTypes.join(` | `)} |
|
||||
|--- | --- | --- | --- | --- |
|
||||
${folderData.join('\n')}
|
||||
|
||||
# Complete frontmatter.json config
|
||||
## Complete frontmatter.json config
|
||||
|
||||
\`\`\`json
|
||||
${JSON.stringify(Settings.globalConfig, null, 2)}
|
||||
@@ -48,8 +72,12 @@ ${JSON.stringify(Settings.globalConfig, null, 2)}
|
||||
ContentProvider.show(logging, `${projectName} diagnostics`, 'markdown', ViewColumn.One);
|
||||
}
|
||||
|
||||
private static getFileTypes = (): string[] => {
|
||||
return Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
|
||||
};
|
||||
|
||||
private static async allProjectFiles() {
|
||||
const allFiles = await workspace.findFiles(`**/*.*`);
|
||||
const allFiles = await workspace.findFiles(`**/*.*`, '**/node_modules/**');
|
||||
return `Total files found: ${allFiles.length}`;
|
||||
}
|
||||
|
||||
@@ -59,22 +87,19 @@ ${JSON.stringify(Settings.globalConfig, null, 2)}
|
||||
projectStart = projectStart?.replace(/\\/g, '/');
|
||||
projectStart = projectStart?.startsWith('/') ? projectStart.substring(1) : projectStart;
|
||||
|
||||
const mdFiles = await workspace.findFiles(
|
||||
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')
|
||||
);
|
||||
const mdxFiles = await workspace.findFiles(
|
||||
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')
|
||||
);
|
||||
const markdownFiles = await workspace.findFiles(
|
||||
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')
|
||||
const fileTypes = Diagnostics.getFileTypes();
|
||||
const fileTypeLengths = await Promise.all(
|
||||
fileTypes.map(async (ft) => {
|
||||
const path = join(projectStart || '', folder.excludeSubdir ? '/' : '**/', `*.${ft}`);
|
||||
const files = await workspace.findFiles(path, '**/node_modules/**');
|
||||
return (files || []).length;
|
||||
})
|
||||
);
|
||||
|
||||
return `- Project start length: ${projectStart.length} | Search in: "${join(
|
||||
return `| ${projectStart.length} | \`${join(
|
||||
projectStart,
|
||||
folder.excludeSubdir ? '/' : '**/',
|
||||
'*.*'
|
||||
)}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${
|
||||
markdownFiles.length
|
||||
}`;
|
||||
)}\` | ${fileTypeLengths.join(` | `)} |`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder';
|
||||
import { Questions } from './../helpers/Questions';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
SETTING_CONTENT_I18N,
|
||||
SETTING_CONTENT_PAGE_FOLDERS,
|
||||
SETTING_CONTENT_STATIC_FOLDER,
|
||||
@@ -14,7 +15,7 @@ import { ContentFolder, FileInfo, FolderInfo, I18nConfig, StaticFolder } from '.
|
||||
import uniqBy = require('lodash.uniqby');
|
||||
import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Logger, Settings, processTimePlaceholders } from '../helpers';
|
||||
import { Extension, Logger, Settings, processTimePlaceholders } from '../helpers';
|
||||
import { existsSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
@@ -25,13 +26,27 @@ import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { glob } from 'glob';
|
||||
import { mkdirAsync } from '../utils/mkdirAsync';
|
||||
import { existsAsync } from '../utils';
|
||||
import { existsAsync, isWindows } from '../utils';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
export class Folders {
|
||||
private static _folders: ContentFolder[] = [];
|
||||
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.createByTemplate, Folders.create));
|
||||
}
|
||||
|
||||
public static clearCached() {
|
||||
Logger.verbose(`Folders:clearCached`);
|
||||
Folders._folders = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a media folder
|
||||
* @returns
|
||||
@@ -98,8 +113,9 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = Folders.get().filter((f) => !f.disableCreation);
|
||||
const location = folders.find((f) => f.title === selectedFolder);
|
||||
let folders = await Folders.get();
|
||||
folders = folders.filter((f) => !f.disableCreation);
|
||||
const location = folders.find((f) => f.path === selectedFolder.path);
|
||||
if (location) {
|
||||
const folderPath = Folders.getFolderPath(Uri.file(location.path));
|
||||
if (folderPath) {
|
||||
@@ -122,7 +138,7 @@ export class Folders {
|
||||
if (folder && folder.fsPath) {
|
||||
const wslPath = folder.fsPath.replace(/\//g, '\\');
|
||||
|
||||
let folders = Folders.get();
|
||||
let folders = await Folders.get();
|
||||
|
||||
const exists = folders.find(
|
||||
(f) => f.path.includes(folder.fsPath) || f.path.includes(wslPath)
|
||||
@@ -171,7 +187,7 @@ export class Folders {
|
||||
*/
|
||||
public static async unregister(folder: Uri) {
|
||||
if (folder && folder.path) {
|
||||
let folders = Folders.get();
|
||||
let folders = await Folders.get();
|
||||
folders = folders.filter((f) => f.path !== folder.fsPath);
|
||||
await Folders.update(folders);
|
||||
|
||||
@@ -282,8 +298,9 @@ export class Folders {
|
||||
* Get the registered folders information
|
||||
*/
|
||||
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
|
||||
Logger.verbose('Folders:getInfo:start');
|
||||
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
|
||||
const folders = Folders.get();
|
||||
const folders = await Folders.get();
|
||||
|
||||
if (folders && folders.length > 0) {
|
||||
const folderInfo: FolderInfo[] = [];
|
||||
@@ -295,9 +312,11 @@ export class Folders {
|
||||
}
|
||||
}
|
||||
|
||||
Logger.verbose('Folders:getInfo:end');
|
||||
return folderInfo;
|
||||
}
|
||||
|
||||
Logger.verbose('Folders:getInfo:end - no folders found');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -305,7 +324,14 @@ export class Folders {
|
||||
* Get the folder settings
|
||||
* @returns
|
||||
*/
|
||||
public static get(): ContentFolder[] {
|
||||
public static async get(): Promise<ContentFolder[]> {
|
||||
Logger.verbose('Folders:get:start');
|
||||
|
||||
if (Folders._folders.length > 0) {
|
||||
Logger.verbose('Folders:get:end - cached folders');
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
|
||||
@@ -315,6 +341,26 @@ export class Folders {
|
||||
|
||||
const contentFolders: ContentFolder[] = [];
|
||||
|
||||
// Check if wildcard is used
|
||||
const wildcardFolders = folders.filter((f) => f.path.includes('*'));
|
||||
if (wildcardFolders && wildcardFolders.length > 0) {
|
||||
for (const folder of wildcardFolders) {
|
||||
folders = folders.filter((f) => f.path !== folder.path);
|
||||
|
||||
const folderPath = Folders.absWsFolder(folder, wsFolder);
|
||||
const subFolders = await Folders.findFolders(folderPath);
|
||||
for (const subFolder of subFolders) {
|
||||
const subFolderPath = parseWinPath(subFolder);
|
||||
|
||||
folders.push({
|
||||
...folder,
|
||||
title: `${folder.title} (${subFolderPath.replace(wsFolder?.fsPath || '', '')})`,
|
||||
path: subFolderPath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folders.forEach((folder) => {
|
||||
if (!folder.title) {
|
||||
folder.title = basename(folder.path);
|
||||
@@ -333,11 +379,11 @@ export class Folders {
|
||||
),
|
||||
l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction),
|
||||
l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction)
|
||||
).then((answer) => {
|
||||
).then(async (answer) => {
|
||||
if (
|
||||
answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorRemoveAction)
|
||||
) {
|
||||
const folders = Folders.get();
|
||||
const folders = await Folders.get();
|
||||
Folders.update(folders.filter((f) => f.path !== folder.path));
|
||||
} else if (
|
||||
answer === l10n.t(LocalizationKey.commandsFoldersGetNotificationErrorCreateAction)
|
||||
@@ -365,25 +411,25 @@ export class Folders {
|
||||
} else if (i18n.locale !== folder.defaultLocale && i18n.path) {
|
||||
localeFolders.push({
|
||||
...folder,
|
||||
title: `${folder.title} (${i18n.title})`,
|
||||
title: folder.title,
|
||||
originalPath: folder.path,
|
||||
locale: i18n.locale,
|
||||
localeTitle: i18n?.title || i18n.locale,
|
||||
localeSourcePath: sourcePath,
|
||||
path: join(folderPath, i18n.path)
|
||||
path: parseWinPath(join(folderPath, i18n.path))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTitle = defaultLocale?.title
|
||||
? `${folder.title} (${defaultLocale.title})`
|
||||
: folder.title;
|
||||
contentFolders.push({
|
||||
...folder,
|
||||
title: defaultTitle,
|
||||
title: folder.title,
|
||||
locale: folder.defaultLocale,
|
||||
localeTitle: defaultLocale?.title || folder.defaultLocale,
|
||||
originalPath: folder.path,
|
||||
localeSourcePath: sourcePath,
|
||||
path: join(folderPath, defaultLocale?.path || '')
|
||||
path: parseWinPath(join(folderPath, defaultLocale?.path || ''))
|
||||
});
|
||||
|
||||
contentFolders.push(...localeFolders);
|
||||
@@ -397,7 +443,17 @@ export class Folders {
|
||||
}
|
||||
});
|
||||
|
||||
return contentFolders.filter((folder) => folder !== null) as ContentFolder[];
|
||||
Logger.verbose('Folders:get:end');
|
||||
Folders._folders = contentFolders.filter((folder) => folder !== null) as ContentFolder[];
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached folder settings
|
||||
* @returns {ContentFolder[]} - The cached folder settings
|
||||
*/
|
||||
public static getCached(): ContentFolder[] {
|
||||
return Folders._folders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,8 +461,13 @@ export class Folders {
|
||||
* @param folders
|
||||
*/
|
||||
public static async update(folders: ContentFolder[]) {
|
||||
const originalFolders = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
// Filter out the locale folders
|
||||
folders = folders.filter((folder) => !folder.locale || folder.locale === folder.defaultLocale);
|
||||
|
||||
// Remove the internal FM properties
|
||||
const folderDetails = folders
|
||||
.map((folder) => {
|
||||
const detail = {
|
||||
@@ -414,17 +475,29 @@ export class Folders {
|
||||
path: Folders.relWsFolder(folder, wsFolder)
|
||||
};
|
||||
|
||||
if (detail['$schema'] || detail.extended) {
|
||||
return null;
|
||||
delete detail['$schema'];
|
||||
delete detail.extended;
|
||||
|
||||
if (detail.locale && detail.locale === detail.defaultLocale) {
|
||||
// Check if the folder was on the original list
|
||||
const originalFolder = originalFolders.find((f) => f.path === folder.originalPath);
|
||||
|
||||
if (originalFolder && !originalFolder.locales && folder.locales) {
|
||||
delete detail.locales;
|
||||
}
|
||||
|
||||
delete detail.localeSourcePath;
|
||||
delete detail.localeTitle;
|
||||
}
|
||||
|
||||
delete detail.locale;
|
||||
delete detail.originalPath;
|
||||
|
||||
return detail;
|
||||
})
|
||||
.filter((folder) => folder !== null);
|
||||
|
||||
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
|
||||
await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
|
||||
|
||||
// Reinitialize the folder listeners
|
||||
PagesListener.startWatchers();
|
||||
@@ -437,11 +510,10 @@ export class Folders {
|
||||
*/
|
||||
public static getAbsFilePath(filePath: string): string {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
if (filePath.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
@@ -455,7 +527,6 @@ export class Folders {
|
||||
*/
|
||||
public static getAbsFolderPath(folderPath: string): string {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
let absPath = '';
|
||||
if (folderPath.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
@@ -464,7 +535,7 @@ export class Folders {
|
||||
absPath = join(parseWinPath(wsFolder?.fsPath || ''), folderPath);
|
||||
}
|
||||
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
@@ -475,14 +546,13 @@ export class Folders {
|
||||
* @returns
|
||||
*/
|
||||
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
|
||||
|
||||
if (absPath.includes('../')) {
|
||||
absPath = join(absPath);
|
||||
}
|
||||
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
absPath = isWindows() ? absPath.split('/').join('\\') : absPath;
|
||||
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
@@ -494,12 +564,11 @@ export class Folders {
|
||||
* @returns
|
||||
*/
|
||||
public static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = parseWinPath(folder.path).replace(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
WORKSPACE_PLACEHOLDER
|
||||
);
|
||||
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
|
||||
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
|
||||
@@ -507,9 +576,11 @@ export class Folders {
|
||||
* Find the content folders
|
||||
*/
|
||||
public static async getContentFolders() {
|
||||
Logger.verbose('Folders:getContentFolders:start');
|
||||
// Find folders that contain files
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (!wsFolder) {
|
||||
Logger.error('Folders:getContentFolders:workspaceFolderNotFound');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -530,7 +601,7 @@ export class Folders {
|
||||
folders = [...folders, ...(await this.findFolders(pattern))];
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`Something went wrong while searching for folders with pattern "${pattern}": ${
|
||||
`Folders:getContentFolders:error: Something went wrong while searching for folders with pattern "${pattern}": ${
|
||||
(e as Error).message
|
||||
}`
|
||||
);
|
||||
@@ -543,6 +614,8 @@ export class Folders {
|
||||
}
|
||||
|
||||
const uniqueFolders = [...new Set(folders)];
|
||||
|
||||
Logger.verbose('Folders:getContentFolders:end');
|
||||
return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder));
|
||||
}
|
||||
|
||||
@@ -551,8 +624,8 @@ export class Folders {
|
||||
* @param folderPath
|
||||
* @returns
|
||||
*/
|
||||
public static getFilePrefixByFolderPath(folderPath: string) {
|
||||
const folders = Folders.get();
|
||||
public static async getFilePrefixByFolderPath(folderPath: string) {
|
||||
const folders = await Folders.get();
|
||||
const pageFolder = folders.find((f) => parseWinPath(f.path) === parseWinPath(folderPath));
|
||||
|
||||
if (pageFolder && typeof pageFolder.filePrefix !== 'undefined') {
|
||||
@@ -567,8 +640,8 @@ export class Folders {
|
||||
* @param filePath
|
||||
* @returns
|
||||
*/
|
||||
public static getFilePrefixBeFilePath(filePath: string) {
|
||||
const folders = Folders.get();
|
||||
public static async getFilePrefixBeFilePath(filePath: string) {
|
||||
const folders = await Folders.get();
|
||||
if (folders.length > 0) {
|
||||
filePath = parseWinPath(filePath);
|
||||
|
||||
@@ -596,8 +669,10 @@ export class Folders {
|
||||
* @param filePath - The file path to match against the page folders.
|
||||
* @returns The page folder that matches the file path, or undefined if no match is found.
|
||||
*/
|
||||
public static getPageFolderByFilePath(filePath: string): ContentFolder | undefined {
|
||||
const folders = Folders.get();
|
||||
public static async getPageFolderByFilePath(
|
||||
filePath: string
|
||||
): Promise<ContentFolder | undefined> {
|
||||
const folders = await Folders.get();
|
||||
const parsedPath = parseWinPath(filePath);
|
||||
const pageFolderMatches = folders
|
||||
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
|
||||
@@ -610,6 +685,27 @@ export class Folders {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file stats for a given file.
|
||||
* @param file - The URI of the file.
|
||||
* @param folderPath - The path of the folder containing the file.
|
||||
* @returns An object containing the file path, file name, folder name, folder path, and file stats.
|
||||
*/
|
||||
public static async getFileStats(file: Uri, folderPath: string) {
|
||||
const fileName = basename(file.fsPath);
|
||||
const folderName = dirname(file.fsPath).split(sep).pop();
|
||||
|
||||
const stats = await workspace.fs.stat(file);
|
||||
|
||||
return {
|
||||
filePath: file.fsPath,
|
||||
fileName,
|
||||
folderName,
|
||||
folderPath,
|
||||
...stats
|
||||
};
|
||||
}
|
||||
|
||||
private static async getFilesByFolder(
|
||||
folder: ContentFolder,
|
||||
supportedFiles: string[] | undefined,
|
||||
@@ -617,6 +713,7 @@ export class Folders {
|
||||
): Promise<FolderInfo | undefined> {
|
||||
try {
|
||||
const folderPath = parseWinPath(folder.path);
|
||||
const folderUri = Uri.file(folderPath);
|
||||
|
||||
if (typeof folderPath === 'string') {
|
||||
let files: Uri[] = [];
|
||||
@@ -635,7 +732,9 @@ export class Folders {
|
||||
let foundFiles = await Folders.findFiles(filePath);
|
||||
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
|
||||
foundFiles = foundFiles.filter((f) =>
|
||||
parseWinPath(f.fsPath).startsWith(parseWinPath(folderUri.fsPath))
|
||||
);
|
||||
|
||||
files = [...files, ...foundFiles];
|
||||
}
|
||||
@@ -645,17 +744,8 @@ export class Folders {
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const fileName = basename(file.fsPath);
|
||||
const folderName = dirname(file.fsPath).split(sep).pop();
|
||||
|
||||
const stats = await workspace.fs.stat(file);
|
||||
|
||||
fileStats.push({
|
||||
filePath: file.fsPath,
|
||||
fileName,
|
||||
folderName,
|
||||
...stats
|
||||
});
|
||||
const fileInfo = await Folders.getFileStats(file, folderPath);
|
||||
fileStats.push(fileInfo);
|
||||
} catch (error) {
|
||||
// Skip the file
|
||||
}
|
||||
@@ -669,8 +759,11 @@ export class Folders {
|
||||
|
||||
return {
|
||||
title: folder.title,
|
||||
path: folderPath,
|
||||
files: files.length,
|
||||
lastModified: fileStats
|
||||
lastModified: fileStats,
|
||||
locale: folder.locale,
|
||||
localeTitle: folder.localeTitle
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -686,14 +779,20 @@ export class Folders {
|
||||
* @param pattern
|
||||
* @returns
|
||||
*/
|
||||
private static findFolders(pattern: string): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
glob(pattern, { ignore: '**/node_modules/**', dot: true }, (err, files) => {
|
||||
const allFolders = files.map((file) => dirname(file));
|
||||
const uniqueFolders = [...new Set(allFolders)];
|
||||
resolve(uniqueFolders);
|
||||
});
|
||||
});
|
||||
private static async findFolders(pattern: string): Promise<string[]> {
|
||||
Logger.verbose(`Folders:findFolders:start - ${pattern}`);
|
||||
|
||||
try {
|
||||
pattern = isWindows() ? parseWinPath(pattern) : pattern;
|
||||
const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true });
|
||||
const allFolders = (files || []).map((file) => dirname(file));
|
||||
const uniqueFolders = [...new Set(allFolders)];
|
||||
Logger.verbose(`Folders:findFolders:end - ${uniqueFolders.length}`);
|
||||
return uniqueFolders;
|
||||
} catch (e) {
|
||||
Logger.error(`Folders:findFolders:error - ${(e as Error).message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,11 +801,17 @@ export class Folders {
|
||||
* @returns
|
||||
*/
|
||||
private static async findFiles(pattern: string): Promise<Uri[]> {
|
||||
return new Promise((resolve) => {
|
||||
glob(pattern, { ignore: '**/node_modules/**' }, (err, files) => {
|
||||
const allFiles = files.map((file) => Uri.file(file));
|
||||
resolve(allFiles);
|
||||
});
|
||||
});
|
||||
Logger.verbose(`Folders:findFiles:start - ${pattern}`);
|
||||
|
||||
try {
|
||||
pattern = isWindows() ? parseWinPath(pattern) : pattern;
|
||||
const files = await glob(pattern, { ignore: '**/node_modules/**', dot: true });
|
||||
const allFiles = (files || []).map((file) => Uri.file(file));
|
||||
Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`);
|
||||
return allFiles;
|
||||
} catch (e) {
|
||||
Logger.error(`Folders:findFiles:error - ${(e as Error).message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { processFmPlaceholders } from './../helpers/processFmPlaceholders';
|
||||
import { processPathPlaceholders } from './../helpers/processPathPlaceholders';
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import {
|
||||
SETTING_PREVIEW_HOST,
|
||||
SETTING_PREVIEW_PATHNAME,
|
||||
@@ -9,23 +6,33 @@ import {
|
||||
PreviewCommands,
|
||||
SETTING_EXPERIMENTAL,
|
||||
SETTING_DATE_FORMAT,
|
||||
GeneralCommands
|
||||
GeneralCommands,
|
||||
SETTING_PREVIEW_TRAILING_SLASH
|
||||
} from './../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { join, parse } from 'path';
|
||||
import { commands, env, Uri, ViewColumn, window, WebviewPanel, extensions } from 'vscode';
|
||||
import { Extension, parseWinPath, processTimePlaceholders, Settings } from '../helpers';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
parseWinPath,
|
||||
processI18nPlaceholders,
|
||||
processTimePlaceholders,
|
||||
processFmPlaceholders,
|
||||
processPathPlaceholders,
|
||||
Settings,
|
||||
Telemetry,
|
||||
processDateTimePlaceholders
|
||||
} from '../helpers';
|
||||
import { ContentFolder, ContentType, PreviewSettings } from '../models';
|
||||
import { format } from 'date-fns';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { Article } from '.';
|
||||
import { urlJoin } from 'url-join-ts';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { Folders } from './Folders';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { joinUrl } from '../utils';
|
||||
import { i18n } from './i18n';
|
||||
|
||||
export class Preview {
|
||||
public static filePath: string | undefined = undefined;
|
||||
@@ -65,7 +72,7 @@ export class Preview {
|
||||
const localhostUrl = await this.getLocalServerUrl();
|
||||
|
||||
if (browserLiteCommand) {
|
||||
const pageUrl = urlJoin(localhostUrl.toString(), slug || '');
|
||||
const pageUrl = joinUrl(localhostUrl.toString(), slug || '');
|
||||
commands.executeCommand(browserLiteCommand, pageUrl);
|
||||
return;
|
||||
}
|
||||
@@ -177,7 +184,7 @@ export class Preview {
|
||||
<title>Front Matter Preview</title>
|
||||
</head>
|
||||
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
|
||||
<div id="app" data-type="preview" data-url="${urlJoin(
|
||||
<div id="app" data-type="preview" data-url="${joinUrl(
|
||||
localhostUrl.toString(),
|
||||
slug || ''
|
||||
)}" data-isProd="${isProd}" data-environment="${
|
||||
@@ -208,7 +215,7 @@ export class Preview {
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: PreviewCommands.toWebview.updateUrl,
|
||||
payload: urlJoin(localhost.toString(), slug || '')
|
||||
payload: joinUrl(localhost.toString(), slug || '')
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -237,15 +244,13 @@ export class Preview {
|
||||
|
||||
let contentType: ContentType | undefined = undefined;
|
||||
if (article?.data) {
|
||||
contentType = ArticleHelper.getContentType(article);
|
||||
contentType = await ArticleHelper.getContentType(article);
|
||||
}
|
||||
|
||||
// Check if there is a pathname defined on content folder level
|
||||
const folders = Folders.get();
|
||||
const folders = await Folders.get();
|
||||
if (folders.length > 0) {
|
||||
const foldersWithPath = folders.filter((folder) => folder.previewPath);
|
||||
|
||||
for (const folder of foldersWithPath) {
|
||||
for (const folder of folders) {
|
||||
const folderPath = parseWinPath(folder.path);
|
||||
if (filePath.startsWith(folderPath)) {
|
||||
if (!selectedFolder || selectedFolder.path.length < folderPath.length) {
|
||||
@@ -291,6 +296,11 @@ export class Preview {
|
||||
slug = Article.getSlug();
|
||||
}
|
||||
|
||||
const locale = await i18n.getLocale(filePath);
|
||||
if (locale && locale.path === slug) {
|
||||
slug = '';
|
||||
}
|
||||
|
||||
if (pathname) {
|
||||
// Known placeholders
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
@@ -310,10 +320,17 @@ export class Preview {
|
||||
const folderPath = wsFolder ? parseWinPath(wsFolder.fsPath) : '';
|
||||
const relativePath = filePath.replace(folderPath, '');
|
||||
pathname = processPathPlaceholders(pathname, relativePath, filePath, selectedFolder);
|
||||
pathname = processI18nPlaceholders(pathname, selectedFolder);
|
||||
|
||||
const file = parse(filePath);
|
||||
if (file.name.toLowerCase() === 'index' && pathname.endsWith(slug)) {
|
||||
slug = '';
|
||||
if (file.name.toLowerCase() === 'index') {
|
||||
const cleanPathName = pathname.endsWith('/')
|
||||
? pathname.substring(0, pathname.length - 1)
|
||||
: pathname;
|
||||
|
||||
if (cleanPathName.endsWith(slug) || !pathname || pathname === '/') {
|
||||
slug = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,11 +338,9 @@ export class Preview {
|
||||
pathname = article?.data ? processFmPlaceholders(pathname, article?.data) : pathname;
|
||||
|
||||
try {
|
||||
const articleDate = ArticleHelper.getDate(article);
|
||||
slug = join(
|
||||
format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string),
|
||||
slug
|
||||
);
|
||||
const articleDate = await ArticleHelper.getDate(article);
|
||||
pathname = processDateTimePlaceholders(pathname, dateFormat, articleDate);
|
||||
slug = join(pathname, slug);
|
||||
} catch (error) {
|
||||
slug = join(pathname, slug);
|
||||
}
|
||||
@@ -339,6 +354,24 @@ export class Preview {
|
||||
slug = slug.substring(0, slug.endsWith('_index') ? slug.length - 6 : slug.length - 5);
|
||||
}
|
||||
|
||||
// Add the trailing slash
|
||||
let trailingSlash = false;
|
||||
if (settings.trailingSlash !== undefined) {
|
||||
trailingSlash = settings.trailingSlash;
|
||||
}
|
||||
|
||||
if (selectedFolder && selectedFolder.trailingSlash !== undefined) {
|
||||
trailingSlash = selectedFolder.trailingSlash;
|
||||
}
|
||||
|
||||
if (contentType && contentType.trailingSlash !== undefined) {
|
||||
trailingSlash = contentType.trailingSlash;
|
||||
}
|
||||
|
||||
if (trailingSlash && !slug.endsWith('/')) {
|
||||
slug = `${slug}/`;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
@@ -375,10 +408,12 @@ export class Preview {
|
||||
public static getSettings(): PreviewSettings {
|
||||
const host = Settings.get<string>(SETTING_PREVIEW_HOST);
|
||||
const pathname = Settings.get<string>(SETTING_PREVIEW_PATHNAME);
|
||||
const trailingSlash = Settings.get<boolean>(SETTING_PREVIEW_TRAILING_SLASH);
|
||||
|
||||
return {
|
||||
host,
|
||||
pathname
|
||||
pathname,
|
||||
trailingSlash
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,25 @@ categories: []
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
// Initialize command
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
|
||||
await Project.init();
|
||||
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.initTemplate, () => Project.createSampleTemplate(true))
|
||||
);
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.registerFolder, Folders.register));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.unregisterFolder, Folders.unregister));
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.createFolder, Folders.addMediaFolder));
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.switchProject, Project.switchProject));
|
||||
}
|
||||
|
||||
@@ -63,7 +82,7 @@ categories: []
|
||||
await Settings.createTeamSettings();
|
||||
|
||||
// Add the default content type
|
||||
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
|
||||
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
|
||||
|
||||
if (sampleTemplate !== undefined) {
|
||||
await Project.createSampleTemplate();
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
import { TaxonomyHelper } from './../helpers/TaxonomyHelper';
|
||||
import * as vscode from 'vscode';
|
||||
import { TaxonomyType } from '../models';
|
||||
import { EXTENSION_NAME } from '../constants';
|
||||
import { ArticleHelper, FilesHelper } from '../helpers';
|
||||
import { COMMAND_NAME, EXTENSION_NAME } from '../constants';
|
||||
import { ArticleHelper, Extension, FilesHelper } from '../helpers';
|
||||
import { FrontMatterParser } from '../parsers';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export class Settings {
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.createTag, () => {
|
||||
Settings.create(TaxonomyType.Tag);
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => {
|
||||
Settings.create(TaxonomyType.Category);
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export)
|
||||
);
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new taxonomy
|
||||
*
|
||||
@@ -18,8 +41,8 @@ export class Settings {
|
||||
const taxonomy = type === TaxonomyType.Tag ? 'tag' : 'category';
|
||||
|
||||
const newOption = await vscode.window.showInputBox({
|
||||
prompt: l10n.t(LocalizationKey.commandsFoldersCreateInputPrompt, taxonomy),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsFoldersCreateInputPlaceholder, taxonomy),
|
||||
prompt: l10n.t(LocalizationKey.commandsSettingsCreateInputPrompt, taxonomy),
|
||||
placeHolder: l10n.t(LocalizationKey.commandsSettingsCreateInputPlaceholder, taxonomy),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
|
||||
@@ -102,13 +102,13 @@ export class StatusListener {
|
||||
* @param article
|
||||
* @param collection
|
||||
*/
|
||||
private static verifyRequiredFields(
|
||||
private static async verifyRequiredFields(
|
||||
editor: vscode.TextEditor,
|
||||
article: ParsedFrontMatter,
|
||||
collection: vscode.DiagnosticCollection
|
||||
) {
|
||||
// Check for missing fields
|
||||
const emptyFields = ContentType.findEmptyRequiredFields(article);
|
||||
const emptyFields = await ContentType.findEmptyRequiredFields(article);
|
||||
const fieldsToReport = [];
|
||||
|
||||
if (emptyFields && emptyFields.length > 0) {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { Questions } from './../helpers/Questions';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
SETTING_CONTENT_DEFAULT_FILETYPE,
|
||||
SETTING_TEMPLATES_FOLDER,
|
||||
TelemetryEvent
|
||||
} from '../constants';
|
||||
import { ArticleHelper, Settings } from '../helpers';
|
||||
import { Article } from '.';
|
||||
import { ArticleHelper, Extension, Settings } from '../helpers';
|
||||
import { Article, Folders } from '.';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Project } from './Project';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
@@ -20,6 +21,24 @@ import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
export class Template {
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate)
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => {
|
||||
const folderPath = Folders.getFolderPath(folder);
|
||||
if (folderPath) {
|
||||
Template.create(folderPath);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a template
|
||||
*/
|
||||
@@ -176,7 +195,7 @@ export class Template {
|
||||
newFilePath
|
||||
);
|
||||
|
||||
const article = Article.updateDate(frontMatter);
|
||||
const article = await Article.updateDate(frontMatter);
|
||||
|
||||
if (!article) {
|
||||
return;
|
||||
|
||||
@@ -43,6 +43,30 @@ export class i18n {
|
||||
i18n.processedFiles = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the I18nConfig settings.
|
||||
*
|
||||
* @returns An array of I18nConfig settings.
|
||||
*/
|
||||
public static async getAll() {
|
||||
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N) || [];
|
||||
|
||||
const folders = await Folders.get();
|
||||
if (folders) {
|
||||
for (const folder of folders) {
|
||||
if (folder.locales) {
|
||||
for (const locale of folder.locales) {
|
||||
if (!i18nSettings.some((i18n) => i18n.locale === locale.locale)) {
|
||||
i18nSettings.push(locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i18nSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the I18nConfig settings from the application.
|
||||
* @returns An array of I18nConfig objects if settings are found, otherwise undefined.
|
||||
@@ -53,7 +77,7 @@ export class i18n {
|
||||
}
|
||||
|
||||
const i18nSettings = Settings.get<I18nConfig[]>(SETTING_CONTENT_I18N);
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
if (!pageFolder) {
|
||||
pageFolder = await i18n.getPageFolder(filePath);
|
||||
}
|
||||
@@ -76,7 +100,7 @@ export class i18n {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
if (!pageFolder || !pageFolder.locale) {
|
||||
return false;
|
||||
}
|
||||
@@ -95,7 +119,7 @@ export class i18n {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
if (!pageFolder || !pageFolder.defaultLocale) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +155,7 @@ export class i18n {
|
||||
return;
|
||||
}
|
||||
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
@@ -193,7 +217,7 @@ export class i18n {
|
||||
};
|
||||
} = {};
|
||||
|
||||
let pageFolder = Folders.getPageFolderByFilePath(filePath);
|
||||
let pageFolder = await Folders.getPageFolderByFilePath(filePath);
|
||||
const fileInfo = await i18n.getFileInfo(filePath);
|
||||
|
||||
if (pageFolder && pageFolder.defaultLocale && pageFolder.localeSourcePath) {
|
||||
@@ -248,7 +272,7 @@ export class i18n {
|
||||
fileUri = Uri.file(fileUri);
|
||||
}
|
||||
|
||||
const pageFolder = Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
|
||||
if (!pageFolder || !pageFolder.localeSourcePath) {
|
||||
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
|
||||
return;
|
||||
@@ -307,7 +331,7 @@ export class i18n {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article);
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
|
||||
return;
|
||||
@@ -400,7 +424,8 @@ export class i18n {
|
||||
);
|
||||
|
||||
if (!translations || translations.length < 3) {
|
||||
throw new Error('Invalid response');
|
||||
resolve(article);
|
||||
return;
|
||||
}
|
||||
|
||||
article.data.title = article.data.title ? translations[0] : '';
|
||||
@@ -457,7 +482,7 @@ export class i18n {
|
||||
* @returns A promise that resolves to the ContentFolder object representing the page folder, or undefined if not found.
|
||||
*/
|
||||
private static async getPageFolder(filePath: string): Promise<ContentFolder | undefined> {
|
||||
const folders = Folders.get();
|
||||
const folders = await Folders.get();
|
||||
|
||||
const localeFolders = folders?.filter((folder) => folder.defaultLocale);
|
||||
if (!localeFolders) {
|
||||
|
||||
@@ -12,7 +12,7 @@ export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({
|
||||
alternative,
|
||||
children
|
||||
}: React.PropsWithChildren<IFeatureFlagProps>) => {
|
||||
if (!features || (features.length > 0 && !features.includes(flag))) {
|
||||
if (!features || features.length === 0 || (features.length > 0 && !features.includes(flag))) {
|
||||
if (alternative) {
|
||||
return alternative;
|
||||
}
|
||||
|
||||
13
src/components/icons/ArrowClockwiseIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IArrowClockwiseIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ArrowClockwiseIcon: React.FunctionComponent<IArrowClockwiseIconProps> = ({ className }: React.PropsWithChildren<IArrowClockwiseIconProps>) => {
|
||||
return (
|
||||
<svg className={className || 'h-4 w-4'} fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.07 9.05a7 7 0 0 1 12.55-3.22l.13.17H12.5a.5.5 0 1 0 0 1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 0-1 0v2.2a8 8 0 1 0 1.99 4.77.5.5 0 0 0-1 .08 7 7 0 1 1-13.92-.5Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
13
src/components/icons/RenameIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IRenameIconProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
export const RenameIcon: React.FunctionComponent<IRenameIconProps> = ({
|
||||
className
|
||||
}: React.PropsWithChildren<IRenameIconProps>) => {
|
||||
return (
|
||||
<svg className={className} fill="currentColor" aria-hidden="true" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 2a.5.5 0 0 0 0 1h1v14h-1a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-1V3h1a.5.5 0 0 0 0-1h-3Zm-4 2h4v1h-4C3.67 5 3 5.67 3 6.5v7c0 .83.67 1.5 1.5 1.5h4v1h-4A2.5 2.5 0 0 1 2 13.5v-7A2.5 2.5 0 0 1 4.5 4Zm11 11h-4v1h4a2.5 2.5 0 0 0 2.5-2.5v-7A2.5 2.5 0 0 0 15.5 4h-4v1h4c.83 0 1.5.67 1.5 1.5v7c0 .83-.67 1.5-1.5 1.5Z" fill="currentColor"></path></svg>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import { LabelField } from './LabelField';
|
||||
export type BoolFieldProps = HTMLFieldProps<
|
||||
boolean,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
{ inputRef?: Ref<HTMLInputElement>, description?: string }
|
||||
>;
|
||||
|
||||
function Bool({
|
||||
@@ -37,6 +37,12 @@ function Bool({
|
||||
/>
|
||||
<span className="field__toggle__slider"></span>
|
||||
</label>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8);
|
||||
export type DateFieldProps = HTMLFieldProps<
|
||||
Date,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date }
|
||||
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date, description?: string }
|
||||
>;
|
||||
|
||||
function Date({
|
||||
@@ -50,6 +50,12 @@ function Date({
|
||||
type="datetime-local"
|
||||
value={dateFormat(value) ?? ''}
|
||||
/>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
|
||||
export type LongTextFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLTextAreaElement> }
|
||||
{ inputRef?: Ref<HTMLTextAreaElement>, description?: string }
|
||||
>;
|
||||
|
||||
function LongText({
|
||||
@@ -36,6 +36,12 @@ function LongText({
|
||||
ref={inputRef}
|
||||
value={value ?? ''}
|
||||
/>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
|
||||
export type NumFieldProps = HTMLFieldProps<
|
||||
number,
|
||||
HTMLDivElement,
|
||||
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement> }
|
||||
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement>, description?: string }
|
||||
>;
|
||||
|
||||
function Num({
|
||||
@@ -47,6 +47,12 @@ function Num({
|
||||
type="number"
|
||||
value={value ?? ''}
|
||||
/>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LabelField } from './LabelField';
|
||||
export type TextFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
{ inputRef?: Ref<HTMLInputElement>, description?: string }
|
||||
>;
|
||||
|
||||
function Text({
|
||||
@@ -40,6 +40,12 @@ function Text({
|
||||
type={type}
|
||||
value={value ?? ''}
|
||||
/>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LocalizationKey } from '../../localization';
|
||||
export type UnknownFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
{ inputRef?: Ref<HTMLInputElement>, description?: string }
|
||||
>;
|
||||
|
||||
function UnknownField({
|
||||
@@ -30,6 +30,12 @@ function UnknownField({
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<div className={`text-[var(--vscode-errorForeground)]`}>{l10n.t(LocalizationKey.fieldUnknown)}</div>
|
||||
|
||||
{
|
||||
props.description && (
|
||||
<span className='block text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2'>{props.description}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
12
src/constants/DefaultFeatureFlags.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { FEATURE_FLAG } from './Features';
|
||||
|
||||
export const DEFAULT_PANEL_FEATURE_FLAGS = Object.values(FEATURE_FLAG.panel).filter(
|
||||
(v) => v !== FEATURE_FLAG.panel.globalSettings
|
||||
);
|
||||
|
||||
export const DEFAULT_DASHBOARD_FEATURE_FLAGS = [
|
||||
FEATURE_FLAG.dashboard.data.view,
|
||||
FEATURE_FLAG.dashboard.taxonomy.view,
|
||||
FEATURE_FLAG.dashboard.snippets.view,
|
||||
FEATURE_FLAG.dashboard.snippets.manage
|
||||
];
|
||||
@@ -6,7 +6,8 @@ export const FEATURE_FLAG = {
|
||||
metadata: 'panel.metadata',
|
||||
recentlyModified: 'panel.recentlyModified',
|
||||
otherActions: 'panel.otherActions',
|
||||
contentType: 'panel.contentType'
|
||||
contentType: 'panel.contentType',
|
||||
gitActions: 'panel.gitActions'
|
||||
},
|
||||
dashboard: {
|
||||
snippets: {
|
||||
|
||||
@@ -21,6 +21,15 @@ export const GeneralCommands = {
|
||||
get: 'getSecret',
|
||||
set: 'setSecret'
|
||||
},
|
||||
content: {
|
||||
locales: 'getContentLocales'
|
||||
},
|
||||
logging: {
|
||||
info: 'logInfo',
|
||||
warn: 'logWarn',
|
||||
error: 'logError',
|
||||
verbose: 'logVerbose'
|
||||
},
|
||||
runCommand: 'runCommand',
|
||||
getLocalization: 'getLocalization',
|
||||
openOnWebsite: 'openOnWebsite'
|
||||
|
||||
@@ -10,3 +10,16 @@ export const SENTRY_LINK =
|
||||
'https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293';
|
||||
|
||||
export const DOCS_SUBMODULES = 'https://frontmatter.codes/docs/git-integration#git-submodules';
|
||||
|
||||
export const WEBSITE_LINKS = {
|
||||
root: 'https://frontmatter.codes',
|
||||
api: {
|
||||
root: 'https://api.frontmatter.codes',
|
||||
metrics: 'https://api.frontmatter.codes/metrics'
|
||||
},
|
||||
docs: {
|
||||
dataDashboard: 'https://frontmatter.codes/docs/dashboard/datafiles-view',
|
||||
snippets: `https://frontmatter.codes/docs/snippets`,
|
||||
snippetsPlaceholders: `https://frontmatter.codes/docs/snippets#placeholders`
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const SentryIgnore = [
|
||||
`ResizeObserver loop limit exceeded`,
|
||||
`Cannot read properties of undefined (reading 'unobserve')`,
|
||||
`TypeError: Cannot read properties of undefined (reading 'unobserve')`
|
||||
`TypeError: Cannot read properties of undefined (reading 'unobserve')`,
|
||||
`ResizeObserver loop completed with undelivered notifications.`
|
||||
];
|
||||
|
||||
@@ -47,6 +47,8 @@ export const TelemetryEvent = {
|
||||
webviewContentsView: 'webviewContentsView',
|
||||
webviewSnippetsView: 'webviewSnippetsView',
|
||||
webviewTaxonomyDashboard: 'webviewTaxonomyDashboard',
|
||||
webviewSettings: 'webviewSettings',
|
||||
webviewUnknown: 'webviewUnknown',
|
||||
|
||||
// Git
|
||||
gitSync: 'gitSync',
|
||||
|
||||
@@ -52,6 +52,7 @@ export const SETTING_PANEL_ACTIONS_DISABLED = 'panel.actions.disabled';
|
||||
|
||||
export const SETTING_PREVIEW_HOST = 'preview.host';
|
||||
export const SETTING_PREVIEW_PATHNAME = 'preview.pathName';
|
||||
export const SETTING_PREVIEW_TRAILING_SLASH = 'preview.trailingSlash';
|
||||
|
||||
export const SETTING_CUSTOM_SCRIPTS = 'custom.scripts';
|
||||
|
||||
@@ -114,6 +115,8 @@ export const SETTING_SNIPPETS_WRAPPER = 'snippets.wrapper.enabled';
|
||||
|
||||
export const SETTING_WEBSITE_URL = 'website.host';
|
||||
|
||||
export const SETTING_LOGGING = 'logging';
|
||||
|
||||
/**
|
||||
* Sponsors only settings
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ export enum DashboardMessage {
|
||||
getPinnedItems = 'getPinnedItems',
|
||||
pinItem = 'pinItem',
|
||||
unpinItem = 'unpinItem',
|
||||
rename = 'rename',
|
||||
|
||||
// Media Dashboard
|
||||
getMedia = 'getMedia',
|
||||
@@ -74,8 +75,8 @@ export enum DashboardMessage {
|
||||
getState = 'getState',
|
||||
runCustomScript = 'runCustomScript',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
logError = 'logError',
|
||||
showNotification = 'showNotification',
|
||||
setTitle = 'setTitle',
|
||||
|
||||
// Settings
|
||||
getSettings = 'getSettings',
|
||||
|
||||
@@ -9,8 +9,8 @@ import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
import { DataView } from './DataView';
|
||||
import { Snippets } from './SnippetsView/Snippets';
|
||||
import { FEATURE_FLAG } from '../../constants';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { FEATURE_FLAG, GeneralCommands } from '../../constants';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { TaxonomyView } from './TaxonomyView';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '..';
|
||||
@@ -18,7 +18,6 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { UnknownView } from './UnknownView';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { ErrorView } from './ErrorView';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { SettingsView } from './SettingsView/SettingsView';
|
||||
@@ -30,7 +29,7 @@ export interface IAppProps {
|
||||
export const App: React.FunctionComponent<IAppProps> = ({
|
||||
showWelcome
|
||||
}: React.PropsWithChildren<IAppProps>) => {
|
||||
const { pages, settings, localeReady } = useMessages();
|
||||
const { pages, settings } = useMessages();
|
||||
const view = useRecoilValue(DashboardViewSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
const [isDevMode, setIsDevMode] = useState(false);
|
||||
@@ -55,7 +54,27 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view);
|
||||
}, [mode?.features]);
|
||||
|
||||
const checkDevMode = (retry: number = 0) => {
|
||||
if (!window.fmExternal) {
|
||||
if (retry < 5) {
|
||||
setTimeout(() => checkDevMode(retry + 1), 150);
|
||||
} else {
|
||||
setIsDevMode(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.fmExternal && window.fmExternal.isDevelopment) {
|
||||
setIsDevMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
|
||||
message: `Loaded with view ${view}`,
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
|
||||
if (view && routePaths[view]) {
|
||||
navigate(routePaths[view]);
|
||||
return;
|
||||
@@ -65,12 +84,26 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
}, [view]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.fmExternal && window.fmExternal.isDevelopment) {
|
||||
setIsDevMode(true);
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
|
||||
message: `Settings loaded`,
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
}
|
||||
|
||||
if (pages) {
|
||||
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
|
||||
message: `Pages loaded - ${pages.length} pages`,
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
}
|
||||
}, [JSON.stringify(settings), JSON.stringify(pages)]);
|
||||
|
||||
useEffect(() => {
|
||||
checkDevMode();
|
||||
}, []);
|
||||
|
||||
if (!settings || !localeReady) {
|
||||
if (!settings) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
@@ -87,11 +120,14 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
fallback={<ErrorView />}
|
||||
onError={(error: Error, componentStack: string, eventId: string) => {
|
||||
Messenger.send(
|
||||
DashboardMessage.logError,
|
||||
`Event ID: ${eventId}
|
||||
GeneralCommands.toVSCode.logging.error,
|
||||
{
|
||||
message: `Event ID: ${eventId}
|
||||
Message: ${error.message}
|
||||
|
||||
Stack: ${componentStack}`
|
||||
Stack: ${componentStack}`,
|
||||
location: 'DASHBOARD'
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
setLocaleReady(true);
|
||||
});
|
||||
|
||||
const initResponse = await fetch(`${aiUrl}/api/ai-init`);
|
||||
const initResponse = await fetch(`${aiUrl}/ai-init`);
|
||||
|
||||
if (!initResponse.ok) {
|
||||
return;
|
||||
@@ -70,7 +70,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${aiUrl}/api/ai-chat`, {
|
||||
const response = await fetch(`${aiUrl}/ai-chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Feedback: React.FunctionComponent<IFeedbackProps> = ({
|
||||
}, []);
|
||||
|
||||
const callVote = useCallback(async (vote: boolean) => {
|
||||
await fetch(`${aiUrl}/api/ai-feedback`, {
|
||||
await fetch(`${aiUrl}/ai-feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DateField: React.FunctionComponent<IDateFieldProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`date__field ${className || ''} text-xs text-[var(--frontmatter-text)]`}>
|
||||
<span className={`date__field ${className || ''} text-xs text-[var(--frontmatter-secondary-text)]`}>
|
||||
{dateValue}
|
||||
</span>
|
||||
);
|
||||
|
||||
37
src/dashboardWebView/components/Common/ItemSelection.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemSelectionProps {
|
||||
filePath: string;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
|
||||
filePath,
|
||||
show
|
||||
}: React.PropsWithChildren<IItemSelectionProps>) => {
|
||||
const { onMultiSelect, selectedFiles } = useSelectedItems();
|
||||
|
||||
const cssNames = useMemo(() => {
|
||||
if (show) {
|
||||
return 'block';
|
||||
}
|
||||
return `${selectedFiles.includes(filePath) ? 'block' : 'hidden'} absolute top-2 left-2`;
|
||||
}, [show, selectedFiles]);
|
||||
|
||||
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();
|
||||
onMultiSelect(filePath);
|
||||
}}
|
||||
checked={selectedFiles.includes(filePath)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -20,7 +20,7 @@ export const Spinner: React.FunctionComponent<ISpinnerProps> = (
|
||||
|
||||
{
|
||||
type === 'initPages' && (
|
||||
<div className='spinner-msg h-full text-2xl flex justify-center items-center text-[var(--vscode-foreground)]'>
|
||||
<div className='spinner-msg h-full text-2xl flex justify-center items-center text-[var(--frontmatter-text)]'>
|
||||
<span>{l10n.t(LocalizationKey.loadingInitPages)}</span>
|
||||
<span className='dots'></span>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface ITextFieldProps {
|
||||
name: string;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
icon?: JSX.Element;
|
||||
disabled?: boolean;
|
||||
autoFocus?: boolean;
|
||||
@@ -18,6 +19,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
name,
|
||||
value,
|
||||
placeholder,
|
||||
description,
|
||||
icon,
|
||||
autoFocus,
|
||||
multiline,
|
||||
@@ -27,52 +29,63 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
onReset
|
||||
}: React.PropsWithChildren<ITextFieldProps>) => {
|
||||
return (
|
||||
<div className="relative flex justify-center">
|
||||
{
|
||||
icon && (
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
{icon}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<>
|
||||
|
||||
<div className="relative flex justify-center">
|
||||
{
|
||||
icon && (
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
{icon}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
multiline ? (
|
||||
<textarea
|
||||
rows={rows || 3}
|
||||
name={name}
|
||||
className={`block w-full py-2 ${icon ? "pl-10" : "pl-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`}
|
||||
style={{
|
||||
boxShadow: "none"
|
||||
}}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
autoFocus={!!autoFocus}
|
||||
onChange={(e) => onChange && onChange(e.target.value)}
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
name={name}
|
||||
className={`block w-full py-2 ${icon ? "pl-10" : "pl-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`}
|
||||
style={{
|
||||
boxShadow: "none"
|
||||
}}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
autoFocus={!!autoFocus}
|
||||
onChange={(e) => onChange && onChange(e.target.value)}
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{(value && onReset) && (
|
||||
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
|
||||
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{
|
||||
multiline ? (
|
||||
<textarea
|
||||
rows={rows || 3}
|
||||
name={name}
|
||||
className={`block w-full py-2 ${icon ? "pl-10" : "pl-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`}
|
||||
style={{
|
||||
boxShadow: "none"
|
||||
}}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
autoFocus={!!autoFocus}
|
||||
onChange={(e) => onChange && onChange(e.target.value)}
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
name={name}
|
||||
className={`block w-full py-2 ${icon ? "pl-10" : "pl-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`}
|
||||
style={{
|
||||
boxShadow: "none"
|
||||
}}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
autoFocus={!!autoFocus}
|
||||
onChange={(e) => onChange && onChange(e.target.value)}
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
description && (
|
||||
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
|
||||
{description}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
{(value && onReset) && (
|
||||
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
|
||||
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,23 +1,25 @@
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, EllipsisVerticalIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, I18nConfig, ScriptType } from '../../../models';
|
||||
import { CustomScript, I18nConfig } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { SelectedItemActionAtom, SettingsSelector } from '../../state';
|
||||
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
|
||||
import { PinIcon } from '../Icons/PinIcon';
|
||||
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { RenameIcon } from '../../../components/icons/RenameIcon';
|
||||
import { openOnWebsite } from '../../utils';
|
||||
import { CustomActions } from './CustomActions';
|
||||
import { TranslationMenu } from './TranslationMenu';
|
||||
|
||||
export interface IContentActionsProps {
|
||||
title: string;
|
||||
path: string;
|
||||
relPath: string;
|
||||
contentType: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
listView?: boolean;
|
||||
locale?: I18nConfig;
|
||||
@@ -32,9 +34,9 @@ export interface IContentActionsProps {
|
||||
}
|
||||
|
||||
export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
title,
|
||||
path,
|
||||
relPath,
|
||||
contentType,
|
||||
scripts,
|
||||
onOpen,
|
||||
listView,
|
||||
@@ -42,8 +44,8 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
translations,
|
||||
locale
|
||||
}: React.PropsWithChildren<IContentActionsProps>) => {
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [pinnedItems, setPinnedItems] = useRecoilState(PinnedItemsAtom);
|
||||
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const onView = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
@@ -51,30 +53,19 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
onOpen();
|
||||
};
|
||||
|
||||
const onDelete = (e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
const onDelete = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
setShowDeletionAlert(true);
|
||||
};
|
||||
setSelectedItemAction({ path, action: 'delete' });
|
||||
}, [path]);
|
||||
|
||||
const onDeleteConfirm = () => {
|
||||
if (path) {
|
||||
Messenger.send(DashboardMessage.deleteFile, path);
|
||||
}
|
||||
setShowDeletionAlert(false);
|
||||
};
|
||||
|
||||
const onOpenFile = (filePath: string) => {
|
||||
messageHandler.send(DashboardMessage.openFile, filePath);
|
||||
}
|
||||
|
||||
const openOnWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
const onRename = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (settings?.websiteUrl && path) {
|
||||
Messenger.send(GeneralCommands.toVSCode.openOnWebsite, {
|
||||
websiteUrl: settings.websiteUrl,
|
||||
filePath: path
|
||||
});
|
||||
}
|
||||
messageHandler.send(DashboardMessage.rename, path);
|
||||
}, [path])
|
||||
|
||||
const onOpenWebsite = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
openOnWebsite(settings?.websiteUrl, path);
|
||||
}, [settings?.websiteUrl, path]);
|
||||
|
||||
const pinItem = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
@@ -91,14 +82,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
})
|
||||
}, [path]);
|
||||
|
||||
const runCustomScript = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
Messenger.send(DashboardMessage.runCustomScript, { script, path });
|
||||
},
|
||||
[path]
|
||||
);
|
||||
|
||||
const runCommand = React.useCallback((commandId: string) => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
|
||||
command: commandId,
|
||||
@@ -110,103 +93,22 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
return pinnedItems.includes(relPath);
|
||||
}, [pinnedItems, relPath]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
return (scripts || [])
|
||||
.filter(
|
||||
(script) =>
|
||||
(script.type === undefined || script.type === ScriptType.Content) &&
|
||||
!script.bulk &&
|
||||
!script.hidden
|
||||
)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem key={script.id || script.title} onClick={(e) => runCustomScript(e, script)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
const translationsMenu = React.useMemo(() => {
|
||||
if (!locale || !translations || Object.keys(translations).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const crntLocale = translations[locale.locale];
|
||||
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
|
||||
|
||||
if (otherLocales.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => onOpenFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => onOpenFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
}, [translations, locale, isDefaultLocale]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${listView ? '' : 'group/card absolute top-6 right-0'
|
||||
className={`${listView ? '' : 'group/card absolute top-6 right-2'
|
||||
} flex flex-col space-y-4`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center border border-transparent rounded-full ${listView ? '' : 'p-2 -mt-4'
|
||||
className={`flex items-center border border-transparent rounded-full ${listView ? '' : 'p-1 -mt-3'
|
||||
} group-hover/card:bg-[var(--vscode-sideBar-background)] group-hover/card:border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<div className={`relative flex text-left`}>
|
||||
{!listView && (
|
||||
<div className="hidden group-hover/card:flex">
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} onClick={onView}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
settings?.websiteUrl && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonOpenOnWebsite)} onClick={openOnWebsite}>
|
||||
<GlobeEuropeAfricaIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] data-[state=open]:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
|
||||
<DropdownMenuTrigger
|
||||
className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] data-[state=open]:text-[var(--vscode-tab-activeForeground)] focus:outline-none'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end">
|
||||
@@ -220,9 +122,14 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onRename}>
|
||||
<RenameIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonRename)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{
|
||||
settings?.websiteUrl && (
|
||||
<DropdownMenuItem onClick={openOnWebsite}>
|
||||
<DropdownMenuItem onClick={onOpenWebsite}>
|
||||
<GlobeEuropeAfricaIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
|
||||
</DropdownMenuItem>
|
||||
@@ -238,9 +145,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
{translationsMenu}
|
||||
<TranslationMenu
|
||||
isDefaultLocale={isDefaultLocale}
|
||||
locale={locale}
|
||||
translations={translations} />
|
||||
|
||||
{customScriptActions}
|
||||
<CustomActions
|
||||
filePath={path}
|
||||
contentType={contentType}
|
||||
scripts={scripts} />
|
||||
|
||||
<DropdownMenuItem onClick={onDelete} className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
|
||||
<TrashIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
@@ -252,17 +165,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showDeletionAlert && (
|
||||
<Alert
|
||||
title={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertTitle, title)}
|
||||
description={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertDescription, title)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowDeletionAlert(false)}
|
||||
trigger={onDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Page } from '../../models';
|
||||
import { LoadingAtom, SettingsSelector } from '../../state';
|
||||
import { LoadingAtom, SelectedItemActionAtom, SettingsSelector } from '../../state';
|
||||
import { Overview } from './Overview';
|
||||
import { Spinner } from '../Common/Spinner';
|
||||
import { SponsorMsg } from '../Layout/SponsorMsg';
|
||||
import usePages from '../../hooks/usePages';
|
||||
import { useEffect } from 'react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { GeneralCommands, TelemetryEvent } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { deletePage } from '../../utils';
|
||||
|
||||
export interface IContentsProps {
|
||||
pages: Page[];
|
||||
@@ -22,28 +27,79 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
const loading = useRecoilValue(LoadingAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { pageItems } = usePages(pages);
|
||||
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
|
||||
const [page, setPage] = useState<Page | undefined>(undefined);
|
||||
const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
const pageFolders = [...new Set(pageItems.map((page) => page.fmFolder))];
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
setShowDeletionAlert(false);
|
||||
setSelectedItemAction(undefined);
|
||||
}, []);
|
||||
|
||||
const onDeleteConfirm = useCallback(() => {
|
||||
if (page) {
|
||||
deletePage(page.fmFilePath);
|
||||
}
|
||||
setShowDeletionAlert(false);
|
||||
setSelectedItemAction(undefined);
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedItemAction && selectedItemAction.path && selectedItemAction.action === 'delete') {
|
||||
const page = pageItems.find((p) => p.fmFilePath === selectedItemAction.path);
|
||||
|
||||
if (page) {
|
||||
setPage(page);
|
||||
setShowDeletionAlert(true);
|
||||
}
|
||||
|
||||
setSelectedItemAction(undefined);
|
||||
}
|
||||
}, [pageItems, selectedItemAction]);
|
||||
|
||||
useEffect(() => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: `Contents view loaded with ${pageItems.length} pages`,
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
}, [JSON.stringify(pageItems)]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewContentsView
|
||||
});
|
||||
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsContents));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-full mx-auto pb-6">
|
||||
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
|
||||
</div>
|
||||
<FilesProvider files={pageItems}>
|
||||
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
|
||||
<div className="w-full flex-grow max-w-full mx-auto pb-6">
|
||||
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
|
||||
</div>
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
</PageLayout>
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
|
||||
{showDeletionAlert && page && (
|
||||
<Alert
|
||||
title={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertTitle, page.title)}
|
||||
description={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertDescription, page.title)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={onDismiss}
|
||||
trigger={onDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
</FilesProvider>
|
||||
);
|
||||
};
|
||||
|
||||
89
src/dashboardWebView/components/Contents/CustomActions.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { CommandLineIcon } from '@heroicons/react/24/outline';
|
||||
import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid';
|
||||
import { runCustomScript } from '../../utils';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ICustomActionsProps {
|
||||
filePath: string;
|
||||
contentType: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
showTrigger?: boolean;
|
||||
}
|
||||
|
||||
export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
|
||||
filePath,
|
||||
contentType,
|
||||
scripts,
|
||||
showTrigger = false,
|
||||
}: React.PropsWithChildren<ICustomActionsProps>) => {
|
||||
|
||||
const onRunCustomScript = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
runCustomScript(script, filePath);
|
||||
},
|
||||
[filePath]
|
||||
);
|
||||
|
||||
const customScripts = React.useMemo(() => {
|
||||
return (scripts || []).filter((script: CustomScript) => {
|
||||
if (script.contentTypes && script.contentTypes.length > 0) {
|
||||
return script.contentTypes.includes(contentType);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [scripts, contentType]);
|
||||
|
||||
const customActions = React.useMemo(() => {
|
||||
if (!customScripts || customScripts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(customScripts || [])
|
||||
.filter(
|
||||
(script) =>
|
||||
(script.type === undefined || script.type === ScriptType.Content) &&
|
||||
!script.bulk &&
|
||||
!script.hidden
|
||||
)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
title={script.title}
|
||||
onClick={(e) => onRunCustomScript(e, script)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
);
|
||||
}, [customScripts, onRunCustomScript]);
|
||||
|
||||
if (!customActions || customActions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (showTrigger) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
title={l10n.t(LocalizationKey.commonOpenCustomActions)}
|
||||
className='px-2 text-[var(--frontmatter-secondary-text)] hover:text-[var(--frontmatter-button-hoverBackground)] focus-visible:outline-none'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonOpenCustomActions)}</span>
|
||||
<CommandLineIconSolid className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{customActions}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{customActions}</>;
|
||||
};
|
||||
66
src/dashboardWebView/components/Contents/FooterActions.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { openFile, openOnWebsite } from '../../utils';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
import { CustomScript } from '../../../models';
|
||||
import { CustomActions } from './CustomActions';
|
||||
|
||||
export interface IFooterActionsProps {
|
||||
filePath: string;
|
||||
contentType: string;
|
||||
websiteUrl?: string;
|
||||
scripts?: CustomScript[];
|
||||
}
|
||||
|
||||
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
filePath,
|
||||
contentType,
|
||||
websiteUrl,
|
||||
scripts
|
||||
}: React.PropsWithChildren<IFooterActionsProps>) => {
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
return (
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] rounded-b`}>
|
||||
{/* <ItemSelection filePath={filePath} show /> */}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => openFile(filePath)}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{
|
||||
websiteUrl && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonOpenOnWebsite)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => openOnWebsite(websiteUrl, filePath)}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
|
||||
<GlobeEuropeAfricaIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
|
||||
<CustomActions
|
||||
filePath={filePath}
|
||||
contentType={contentType}
|
||||
scripts={scripts}
|
||||
showTrigger />
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonDelete)}
|
||||
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={() => setSelectedItemAction({ path: filePath, action: 'delete' })}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { DateField } from '../Common/DateField';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { useMemo } from 'react';
|
||||
@@ -13,10 +11,14 @@ import * as React from 'react';
|
||||
import useExtensibility from '../../hooks/useExtensibility';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { I18nLabel } from './I18nLabel';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { openFile } from '../../utils';
|
||||
import { FooterActions } from './FooterActions';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { cn } from '../../../utils/cn';
|
||||
import { Tags } from './Tags';
|
||||
|
||||
export interface IItemProps extends Page { }
|
||||
|
||||
@@ -25,12 +27,12 @@ const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
...pageData
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const { selectedFiles } = useSelectedItems();
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
const cardFields = useMemo(() => settings?.dashboardState?.contents?.cardFields, [settings?.dashboardState?.contents?.cardFields]);
|
||||
const { escapedTitle, escapedDescription } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
|
||||
const navigate = useNavigate();
|
||||
const { titleHtml, descriptionHtml, dateHtml, statusHtml, tagsHtml, imageHtml, footerHtml } = useExtensibility({
|
||||
fmFilePath: pageData.fmFilePath,
|
||||
date: pageData.date,
|
||||
@@ -40,9 +42,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
pageData
|
||||
});
|
||||
|
||||
const openFile = () => {
|
||||
Messenger.send(DashboardMessage.openFile, pageData.fmFilePath);
|
||||
};
|
||||
const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
|
||||
|
||||
const onOpenFile = React.useCallback(() => {
|
||||
openFile(pageData.fmFilePath);
|
||||
}, [pageData.fmFilePath]);
|
||||
|
||||
const tags: string[] | undefined = useMemo(() => {
|
||||
if (!settings?.dashboardState?.contents?.tags) {
|
||||
@@ -91,9 +95,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
return (
|
||||
dateHtml ? (
|
||||
<div className='mr-4' dangerouslySetInnerHTML={{ __html: dateHtml }} />
|
||||
<div className='mr-6' dangerouslySetInnerHTML={{ __html: dateHtml }} />
|
||||
) : (
|
||||
cardFields?.date && pageData.date ? <DateField className={`mr-4`} value={pageData.date} format={pageData.fmDateFormat} /> : null
|
||||
cardFields?.date && pageData.date ? <DateField className={`mr-6`} value={pageData.date} format={pageData.fmDateFormat} /> : null
|
||||
)
|
||||
)
|
||||
}, [dateHtml, cardFields?.date, pageData]);
|
||||
@@ -106,12 +110,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return (
|
||||
<li className="relative">
|
||||
<div
|
||||
className={`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}
|
||||
className={cn(`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`, isSelected && `border-[var(--frontmatter-border-active)]`)}
|
||||
>
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
className={`relative h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
|
||||
onClick={onOpenFile}
|
||||
className={`relative rounded-t h-36 w-full overflow-hidden border-b cursor-pointer border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
{
|
||||
imageHtml ?
|
||||
@@ -133,32 +137,34 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<div className="relative p-4 w-full grow">
|
||||
{
|
||||
(statusPlaceholder || datePlaceholder) && (
|
||||
<div className={`flex justify-between items-center ${hasDraftOrDate ? `mb-2` : ``}`}>
|
||||
{statusPlaceholder}
|
||||
{datePlaceholder}
|
||||
<div className={`space-y-2 ${hasDraftOrDate ? `mb-2` : ``}`}>
|
||||
<div>{statusPlaceholder}</div>
|
||||
<div>{datePlaceholder}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<ContentActions
|
||||
title={pageData.title}
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
locale={pageData.fmLocale}
|
||||
isDefaultLocale={pageData.fmDefaultLocale}
|
||||
translations={pageData.fmTranslations}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile}
|
||||
onOpen={onOpenFile}
|
||||
/>
|
||||
|
||||
<I18nLabel page={pageData} />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
onClick={onOpenFile}
|
||||
className={`text-left block`}>
|
||||
{
|
||||
titleHtml ? (
|
||||
@@ -175,13 +181,13 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
(escapedDescription || descriptionHtml) && (
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}
|
||||
onClick={onOpenFile}
|
||||
className={`mt-2 text-left block`}>
|
||||
{
|
||||
descriptionHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
|
||||
) : (
|
||||
<p className={`text-xs text-[vara(--vscode-titleBar-activeForeground)]`}>{escapedDescription}</p>
|
||||
<p className={`text-xs text-[var(--frontmatter-secondary-text)]`}>{escapedDescription}</p>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
@@ -192,27 +198,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
tagsHtml ? (
|
||||
<div className="mt-2" dangerouslySetInnerHTML={{ __html: tagsHtml }} />
|
||||
) : (
|
||||
tags && tags.length > 0 && (
|
||||
<div className="mt-2">
|
||||
{tags.map(
|
||||
(tag, index) => tag && (
|
||||
<button
|
||||
key={index}
|
||||
className={`inline-block mr-1 mt-1 text-xs text-[var(--vscode-textPreformat-foreground)] hover:brightness-75 hover:underline hover:underline-offset-1`}
|
||||
title={l10n.t(LocalizationKey.commonFilterValue, tag)}
|
||||
onClick={() => {
|
||||
const tagField = settings?.dashboardState.contents.tags;
|
||||
if (tagField) {
|
||||
navigate(`${routePaths.contents}?taxonomy=${tagField}&value=${tag}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
<Tags values={tags} tagField={settings?.dashboardState?.contents?.tags} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
@@ -222,6 +208,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<div className="placeholder__card__footer p-4 w-full" dangerouslySetInnerHTML={{ __html: footerHtml }} />
|
||||
)
|
||||
}
|
||||
|
||||
<FooterActions
|
||||
filePath={pageData.fmFilePath}
|
||||
contentType={pageData.fmContentType}
|
||||
websiteUrl={settings?.websiteUrl}
|
||||
scripts={settings?.scripts} />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
@@ -232,18 +224,20 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`}
|
||||
>
|
||||
<div className="col-span-8 font-bold truncate flex items-center space-x-4">
|
||||
<ItemSelection filePath={pageData.fmFilePath} show />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}>
|
||||
onClick={onOpenFile}>
|
||||
{escapedTitle}
|
||||
</button>
|
||||
|
||||
<ContentActions
|
||||
title={escapedTitle || ""}
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile}
|
||||
onOpen={onOpenFile}
|
||||
listView
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,27 +2,32 @@ import * as React from 'react';
|
||||
import { Page } from '../../models';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { openFile } from '../../utils';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IPinnedItemProps extends Page { }
|
||||
|
||||
export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
...pageData
|
||||
}: React.PropsWithChildren<IPinnedItemProps>) => {
|
||||
const { selectedFiles } = useSelectedItems();
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
|
||||
|
||||
const openFile = React.useCallback(() => {
|
||||
messageHandler.send(DashboardMessage.openFile, pageData.fmFilePath);
|
||||
const isSelected = React.useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
|
||||
|
||||
const onOpenFile = React.useCallback(() => {
|
||||
openFile(pageData.fmFilePath);
|
||||
}, [pageData.fmFilePath]);
|
||||
|
||||
return (
|
||||
<li className='group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)]'>
|
||||
<button onClick={openFile} className='relative h-full w-1/3'>
|
||||
<li className={cn(`group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] relative`, isSelected && `border-[var(--frontmatter-border-active)]`)}>
|
||||
<button onClick={onOpenFile} className='relative h-full w-1/3'>
|
||||
{
|
||||
pageData["fmPreviewImage"] ? (
|
||||
<img
|
||||
@@ -41,13 +46,15 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<button onClick={openFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<button onClick={onOpenFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
|
||||
<p className='font-bold'>{escapedTitle}</p>
|
||||
|
||||
<ContentActions
|
||||
title={pageData.title}
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile}
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
|
||||
if (draftValue) {
|
||||
return (
|
||||
<span
|
||||
className={`inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem] text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
|
||||
className={`inline-block px-[3px] py-[2px] rounded font-semibold uppercase tracking-wide text-[0.7rem] text-[var(--vscode-badge-foreground)] bg-[var(--vscode-badge-background)]`}
|
||||
>
|
||||
{draftValue}
|
||||
</span>
|
||||
@@ -51,7 +51,7 @@ export const Status: React.FunctionComponent<IStatusProps> = ({
|
||||
return (
|
||||
<span
|
||||
className={`draft__status
|
||||
inline-block px-1 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-[0.7rem]
|
||||
inline-block px-[3px] py-[2px] rounded font-semibold uppercase tracking-wide text-[0.7rem]
|
||||
${draftValue ?
|
||||
'bg-[var(--vscode-statusBarItem-errorBackground)] text-[var(--vscode-statusBarItem-errorForeground)]' :
|
||||
isFuture ?
|
||||
|
||||
35
src/dashboardWebView/components/Contents/Tag.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { routePaths } from '../..';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export interface ITagProps {
|
||||
value?: string;
|
||||
tagField?: string | null | undefined;
|
||||
}
|
||||
|
||||
export const Tag: React.FunctionComponent<ITagProps> = ({
|
||||
value,
|
||||
tagField
|
||||
}: React.PropsWithChildren<ITagProps>) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`inline-block mr-1 mt-1 text-xs text-[var(--vscode-button-secondaryForeground)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] border border-[var(--frontmatter-border)] rounded px-1 py-0.5`}
|
||||
title={l10n.t(LocalizationKey.commonFilterValue, value)}
|
||||
onClick={() => {
|
||||
if (tagField) {
|
||||
navigate(`${routePaths.contents}?taxonomy=${tagField}&value=${value}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
#{value}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
29
src/dashboardWebView/components/Contents/Tags.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import { Tag } from './Tag';
|
||||
|
||||
export interface ITagsProps {
|
||||
values?: string[];
|
||||
tagField?: string | null | undefined;
|
||||
}
|
||||
|
||||
export const Tags: React.FunctionComponent<ITagsProps> = ({
|
||||
values,
|
||||
tagField
|
||||
}: React.PropsWithChildren<ITagsProps>) => {
|
||||
if (!values || values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{values.map(
|
||||
(tag, index) => tag && (
|
||||
<Tag
|
||||
key={index}
|
||||
value={tag}
|
||||
tagField={tagField} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
80
src/dashboardWebView/components/Contents/TranslationMenu.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { openFile } from '../../utils/MessageHandlers';
|
||||
import { I18nConfig } from '../../../models';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ITranslationMenuProps {
|
||||
isDefaultLocale?: boolean;
|
||||
locale?: I18nConfig;
|
||||
translations?: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const TranslationMenu: React.FunctionComponent<ITranslationMenuProps> = ({
|
||||
isDefaultLocale,
|
||||
locale,
|
||||
translations,
|
||||
}: React.PropsWithChildren<ITranslationMenuProps>) => {
|
||||
|
||||
const otherLocales = React.useMemo(() => {
|
||||
if (!translations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(translations).filter(([key]) => key !== locale?.locale);
|
||||
}, [translations]);
|
||||
|
||||
const crntLocale = React.useMemo(() => {
|
||||
if (!locale?.locale || !translations || !translations[locale.locale]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return translations[locale.locale];
|
||||
}, [translations, locale]);
|
||||
|
||||
|
||||
if (!locale || !translations || Object.keys(translations).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (otherLocales.length === 0 || !crntLocale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => openFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
};
|
||||
@@ -17,10 +17,13 @@ import { Container } from './SortableContainer';
|
||||
import { SortableItem } from './SortableItem';
|
||||
import { ChevronRightIcon, CircleStackIcon } from '@heroicons/react/24/outline';
|
||||
import { DataType } from '../../../models/DataType';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { GeneralCommands, TelemetryEvent, WEBSITE_LINKS } from '../../../constants';
|
||||
import { NavigationItem } from '../Layout';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import { Transition } from '@headlessui/react';
|
||||
|
||||
export interface IDataViewProps { }
|
||||
|
||||
@@ -125,10 +128,17 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsData));
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewDataView
|
||||
});
|
||||
|
||||
Messenger.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: 'Data view loaded',
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(messageListener);
|
||||
};
|
||||
@@ -161,38 +171,71 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
<div className="flex flex-col h-full overflow-auto inset-y-0">
|
||||
<Header settings={settings} />
|
||||
|
||||
{settings?.dataFiles && settings.dataFiles.length > 0 ? (
|
||||
<div className="relative w-full flex-grow mx-auto overflow-hidden">
|
||||
<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)}
|
||||
</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 ? (
|
||||
<div className={`relative w-full flex-grow mx-auto overflow-hidden`}>
|
||||
{
|
||||
!selectedData && (
|
||||
<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)]`}
|
||||
>
|
||||
{dataFiles &&
|
||||
dataFiles.length > 0 &&
|
||||
dataFiles.map((dataFile, idx) => (
|
||||
<NavigationItem
|
||||
key={`${dataFile.id}-${idx}`}
|
||||
isSelected={selectedData?.id === dataFile.id}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
>
|
||||
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
|
||||
<span>{dataFile.title}</span>
|
||||
</NavigationItem>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
<h2 className={`text-lg text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewSelect)}
|
||||
</h2>
|
||||
|
||||
<section className={`pl-64 flex min-w-0 h-full`}>
|
||||
<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) => (
|
||||
<NavigationItem
|
||||
key={`${dataFile.id}-${idx}`}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
>
|
||||
<ChevronRightIcon className="-ml-1 w-5 mr-2" />
|
||||
<span>{dataFile.title}</span>
|
||||
</NavigationItem>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Transition
|
||||
as={`div`}
|
||||
show={!!selectedData}
|
||||
enter="transition ease transform"
|
||||
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}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{dataFiles.map((dataFile) => (
|
||||
<MenuItem
|
||||
key={dataFile.id}
|
||||
title={dataFile.title}
|
||||
value={dataFile}
|
||||
isCurrent={selectedData.id === dataFile.id}
|
||||
onClick={() => setSchema(dataFile)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<section className={`flex min-w-0 h-full ease transition-[padding] ${selectedData ? "" : "pl-64"}`}>
|
||||
{selectedData ? (
|
||||
<>
|
||||
{!selectedData.singleEntry && (
|
||||
@@ -265,7 +308,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
|
||||
href={`https://frontmatter.codes/docs/dashboard#data-files-view`}
|
||||
href={WEBSITE_LINKS.docs.dataDashboard}
|
||||
title={l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
>
|
||||
{l10n.t(LocalizationKey.dashboardDataViewDataViewGetStartedLink)}
|
||||
|
||||
272
src/dashboardWebView/components/Header/ActionsBar.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationType, Page } from '../../models';
|
||||
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { ActionsBarItem } from './ActionsBarItem';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { useFilesContext } from '../../providers/FilesProvider';
|
||||
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
|
||||
import { RenameIcon } from '../../../components/icons/RenameIcon';
|
||||
import { openFile } from '../../utils';
|
||||
|
||||
export interface IActionsBarProps {
|
||||
view: NavigationType;
|
||||
}
|
||||
|
||||
export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
view
|
||||
}: React.PropsWithChildren<IActionsBarProps>) => {
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [showAlert, setShowAlert] = React.useState(false);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { files } = useFilesContext();
|
||||
|
||||
const viewFile = React.useCallback(() => {
|
||||
if (selectedFiles.length === 1) {
|
||||
if (view === NavigationType.Contents) {
|
||||
openFile(selectedFiles[0]);
|
||||
} else if (view === NavigationType.Media) {
|
||||
setSelectedItemAction({ path: selectedFiles[0], action: 'view' })
|
||||
}
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const onDeleteConfirm = React.useCallback(() => {
|
||||
for (const file of selectedFiles) {
|
||||
if (file) {
|
||||
if (view === NavigationType.Contents) {
|
||||
messageHandler.send(DashboardMessage.deleteFile, file);
|
||||
} else if (view === NavigationType.Media) {
|
||||
messageHandler.send(DashboardMessage.deleteMedia, {
|
||||
file: file,
|
||||
folder: selectedFolder
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelectedFiles([]);
|
||||
setShowAlert(false);
|
||||
}, [selectedFiles]);
|
||||
|
||||
const runCustomScript = React.useCallback((script: CustomScript) => {
|
||||
for (const file of selectedFiles) {
|
||||
messageHandler.send(DashboardMessage.runCustomScript, {
|
||||
script,
|
||||
path: file
|
||||
});
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const languageActions = React.useMemo(() => {
|
||||
const actions: React.ReactNode[] = [];
|
||||
|
||||
if (view === NavigationType.Contents && files.length > 0 && selectedFiles.length === 1) {
|
||||
const selectedItem = selectedFiles[0];
|
||||
const page = ((files || []) as Page[]).find((f: Page) => f.fmFilePath === selectedItem);
|
||||
|
||||
if (page?.fmLocale) {
|
||||
const locale = page.fmLocale;
|
||||
const translations = page.fmTranslations;
|
||||
|
||||
actions.push(
|
||||
<ActionsBarItem
|
||||
key="translate"
|
||||
onClick={() => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
|
||||
command: COMMAND_NAME.i18n.create,
|
||||
args: selectedItem
|
||||
})
|
||||
}}>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonTranslate)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
|
||||
if (translations && Object.keys(translations).length > 0) {
|
||||
const crntLocale = translations[locale.locale];
|
||||
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
|
||||
|
||||
if (otherLocales.length > 0) {
|
||||
actions.push(
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'
|
||||
>
|
||||
<LanguageIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonLanguages)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
|
||||
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => openFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}, [files, selectedFiles]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
if (!settings?.scripts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { scripts } = settings;
|
||||
let crntScripts: CustomScript[] = [];
|
||||
if (view === NavigationType.Contents) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk && !script.hidden);
|
||||
} else if (view === NavigationType.Media) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden);
|
||||
}
|
||||
|
||||
if (crntScripts.length > 0) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonScripts)}</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
{
|
||||
crntScripts.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [view, settings?.scripts, selectedFiles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`w-full flex items-center justify-between py-2 px-4 border-b bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBar-foreground)] border-[var(--frontmatter-border)]`}
|
||||
aria-label="Item actions"
|
||||
>
|
||||
<div className='flex items-center space-x-6'>
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={viewFile}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonView)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
{
|
||||
view === NavigationType.Contents && (
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={() => {
|
||||
messageHandler.send(DashboardMessage.rename, selectedFiles[0]);
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
>
|
||||
<RenameIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonRename)}</span>
|
||||
</ActionsBarItem>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
view === NavigationType.Media && (
|
||||
<>
|
||||
<ActionsBarItem
|
||||
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: selectedFiles[0],
|
||||
action: 'edit'
|
||||
})}
|
||||
>
|
||||
<PencilIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</ActionsBarItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{languageActions}
|
||||
|
||||
{customScriptActions}
|
||||
|
||||
<ActionsBarItem
|
||||
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
onClick={() => setShowAlert(true)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedFiles.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className='flex items-center hover:text-[var(--vscode-statusBarItem-warningBackground)]'
|
||||
onClick={() => setSelectedFiles([])}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderActionsBarItemsSelected, selectedFiles.length)}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteTitle)}`}
|
||||
description={l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteDescription)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
trigger={onDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
26
src/dashboardWebView/components/Header/ActionsBarItem.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IActionsBarItemProps {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const ActionsBarItem: React.FunctionComponent<IActionsBarItemProps> = ({
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
onClick
|
||||
}: React.PropsWithChildren<IActionsBarItemProps>) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(`flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]`, className)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -4,24 +4,25 @@ import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../../state';
|
||||
import { SearchAtom, SettingsAtom } from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
|
||||
export interface IBreadcrumbProps { }
|
||||
|
||||
export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
_: React.PropsWithChildren<IBreadcrumbProps>
|
||||
) => {
|
||||
const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const { selectedFolder, updateFolder } = useMediaFolder();
|
||||
const [, setSearchValue] = useRecoilState(SearchAtom);
|
||||
const [folders, setFolders] = React.useState<string[]>([]);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
|
||||
const updateFolder = (folder: string) => {
|
||||
const updateMediaFolder = React.useCallback((folder: string) => {
|
||||
setSearchValue('');
|
||||
setSelectedFolder(folder);
|
||||
};
|
||||
updateFolder(folder);
|
||||
}, [updateFolder, setSearchValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!settings) {
|
||||
@@ -79,11 +80,11 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
}, [selectedFolder, settings]);
|
||||
|
||||
return (
|
||||
<ol role="list" className="flex space-x-4 px-5 flex-1">
|
||||
<ol role="list" className="flex space-x-2 px-4 flex-1">
|
||||
<li className="flex">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={() => setSelectedFolder(HOME_PAGE_NAVIGATION_ID)}
|
||||
onClick={() => updateMediaFolder(HOME_PAGE_NAVIGATION_ID)}
|
||||
className={`text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
>
|
||||
<HomeIcon className="flex-shrink-0 h-5 w-5" aria-hidden="true" />
|
||||
@@ -106,8 +107,8 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (
|
||||
</svg>
|
||||
|
||||
<button
|
||||
onClick={() => updateFolder(folder)}
|
||||
className={`ml-4 text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
onClick={() => updateMediaFolder(folder)}
|
||||
className={`ml-2 text-sm font-medium text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]`}
|
||||
>
|
||||
{basename(folder)}
|
||||
</button>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FolderAtom, SettingsSelector } from '../../state';
|
||||
import { MenuButton, MenuItem } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IFoldersFilterProps { }
|
||||
|
||||
@@ -14,7 +14,11 @@ export const FoldersFilter: React.FunctionComponent<
|
||||
const DEFAULT_TYPE = l10n.t(LocalizationKey.dashboardHeaderFoldersDefault);
|
||||
const [crntFolder, setCrntFolder] = useRecoilState(FolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const contentFolders = settings?.contentFolders || [];
|
||||
|
||||
const contentFolders = React.useMemo(() => {
|
||||
return settings?.contentFolders
|
||||
.filter((folder, index, self) => index === self.findIndex((t) => t.originalPath === folder.originalPath)) || [];
|
||||
}, [settings?.contentFolders]);
|
||||
|
||||
if (contentFolders.length <= 1) {
|
||||
return null;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { GroupingSelector, SortingAtom } from '../../state';
|
||||
import { GroupingSelector, MultiSelectedItemsAtom, SortingAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
|
||||
@@ -18,8 +18,7 @@ import { ArrowTopRightOnSquareIcon, BoltIcon, PlusIcon } from '@heroicons/react/
|
||||
import { HeartIcon } from '@heroicons/react/24/solid';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { SyncButton } from './SyncButton';
|
||||
import { useMemo } from 'react';
|
||||
import { Pagination } from './Pagination';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
@@ -32,6 +31,8 @@ import { SettingsLink } from '../SettingsView/SettingsLink';
|
||||
import { Link } from '../Common/Link';
|
||||
import { SPONSOR_LINK } from '../../../constants';
|
||||
import { Filters } from './Filters';
|
||||
import { ActionsBar } from './ActionsBar';
|
||||
import { RefreshDashboardData } from './RefreshDashboardData';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -51,6 +52,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
}: React.PropsWithChildren<IHeaderProps>) => {
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetSelectedItems = useResetRecoilState(MultiSelectedItemsAtom);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
@@ -70,6 +72,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
const updateView = (view: NavigationType) => {
|
||||
navigate(routePaths[view]);
|
||||
resetSorting();
|
||||
resetSelectedItems();
|
||||
};
|
||||
|
||||
const runBulkScript = (script: CustomScript) => {
|
||||
@@ -122,10 +125,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
||||
<div className={`mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<div className={`px-4 overflow-x-auto mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<Tabs onNavigate={updateView} />
|
||||
|
||||
<div className='flex items-center space-x-2 pr-4'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<ProjectSwitcher />
|
||||
|
||||
{
|
||||
@@ -160,19 +163,19 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
|
||||
{location.pathname === routePaths.contents && (
|
||||
<>
|
||||
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
|
||||
<div className={`flex items-center justify-end space-x-4 flex-1`}>
|
||||
{/* <SyncButton /> */}
|
||||
|
||||
<div className={`px-4 mt-2 mb-2 flex items-center justify-between`}>
|
||||
<div className={`flex items-center justify-start space-x-2 flex-1`}>
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
|
||||
choices={choiceOptions}
|
||||
onClick={createContent}
|
||||
disabled={!settings?.initialized}
|
||||
/>
|
||||
|
||||
<RefreshDashboardData />
|
||||
</div>
|
||||
|
||||
<Searchbox />
|
||||
</div>
|
||||
|
||||
<div className={`px-4 flex flex-row items-center border-b justify-between border-[var(--frontmatter-border)]`}>
|
||||
@@ -186,7 +189,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
className={`overflow-x-auto py-2 px-4 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<ClearFilters />
|
||||
|
||||
@@ -208,6 +211,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
<Pagination totalPages={totalPages || 0} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActionsBar view={NavigationType.Contents} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -216,6 +221,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
<MediaHeaderTop />
|
||||
|
||||
<MediaHeaderBottom />
|
||||
|
||||
<ActionsBar view={NavigationType.Media} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export const Navigation: React.FunctionComponent<INavigationProps> = ({
|
||||
}, [settings?.draftField?.type, tabInfo]);
|
||||
|
||||
return (
|
||||
<nav className="flex-1 -mb-px flex space-x-6 xl:space-x-8" aria-label="Tabs">
|
||||
<nav className="flex-1 -mb-px flex space-x-2 xl:space-x-4" aria-label="Tabs">
|
||||
{settings?.draftField?.type === 'boolean' ? (
|
||||
tabs.map((tab) => (
|
||||
<NavigationItem
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
@@ -18,6 +17,7 @@ import {
|
||||
} from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ArrowClockwiseIcon } from '../../../components/icons/ArrowClockwiseIcon';
|
||||
|
||||
export interface IRefreshDashboardDataProps { }
|
||||
|
||||
@@ -62,11 +62,11 @@ export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardData
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`mr-2 text-[var(--vscode-foreground)] hover:text-[var(--vscode-textLink-foreground)]`}
|
||||
className={`mr-2 text-[var(--frontmatter-text)] hover:text-[var(--vscode-textLink-foreground)]`}
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderRefreshDashboardLabel)}
|
||||
onClick={refresh}
|
||||
>
|
||||
<ArrowPathIcon className={`h-5 w-5`} />
|
||||
<ArrowClockwiseIcon className={`h-5 w-5`} />
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.dashboardHeaderRefreshDashboardLabel)}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { MagnifyingGlassIcon, XCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import { SearchAtom, SearchReadyAtom } from '../../state';
|
||||
import { RefreshDashboardData } from './RefreshDashboardData';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { TextField } from '../Common/TextField';
|
||||
@@ -40,7 +39,7 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({
|
||||
}, [debounceSearch]);
|
||||
|
||||
return (
|
||||
<div className="flex space-x-4 flex-1">
|
||||
<div className="flex justify-end space-x-4 flex-1">
|
||||
<div className="min-w-0">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{l10n.t(LocalizationKey.commonSearch)}
|
||||
@@ -58,8 +57,6 @@ export const Searchbox: React.FunctionComponent<ISearchboxProps> = ({
|
||||
onReset={reset}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RefreshDashboardData />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { NavigationType } from '../../models';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface ITabProps {
|
||||
navigationType: NavigationType;
|
||||
@@ -16,11 +17,11 @@ export const Tab: React.FunctionComponent<ITabProps> = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`h-full flex items-center py-2 px-4 text-sm font-medium text-center border-b-2 border-transparent hover:border-[var(--vscode-tab-activeForeground)] hover:text-[var(--vscode-tab-activeForeground)] ${location.pathname === `/${navigationType}`
|
||||
className={cn(`h-full flex items-center py-2 px-1 text-sm font-medium text-center border-b-2 border-transparent hover:text-[var(--vscode-tab-activeForeground)] ${location.pathname === `/${navigationType}`
|
||||
?
|
||||
`text-[var(--vscode-tab-activeForeground)] border-[var(--vscode-tab-activeForeground)]` :
|
||||
`text-[var(--vscode-tab-inactiveForeground)]`
|
||||
}`}
|
||||
`text-[var(--frontmatter-nav-active)] border-[var(--frontmatter-nav-active)]` :
|
||||
`text-[var(--frontmatter-nav-inactive)]`
|
||||
}`)}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { CircleStackIcon, PhotoIcon, ScissorsIcon, TagIcon } from '@heroicons/react/24/outline';
|
||||
import { PhotoIcon, ScissorsIcon, TagIcon, CircleStackIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlag } from '../../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../../constants';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { NavigationType } from '../../models';
|
||||
import { ModeAtom } from '../../state';
|
||||
import { Tab } from './Tab';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { PageIcon } from '../../../panelWebView/components/Icons';
|
||||
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
|
||||
|
||||
export interface ITabsProps {
|
||||
onNavigate: (navigationType: NavigationType) => void;
|
||||
@@ -21,42 +22,42 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({
|
||||
|
||||
return (
|
||||
<ul
|
||||
className="flex items-center justify-start h-full"
|
||||
className="flex items-center justify-start h-full space-x-4"
|
||||
data-tabs-toggle="#myTabContent"
|
||||
role="tablist"
|
||||
>
|
||||
<li className="mr-2" role="presentation">
|
||||
<li role="presentation">
|
||||
<Tab navigationType={NavigationType.Contents} onNavigate={onNavigate}>
|
||||
<MarkdownIcon className={`h-6 w-auto mr-2`} />
|
||||
<PageIcon className={`h-4 w-auto mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsContents)}</span>
|
||||
</Tab>
|
||||
</li>
|
||||
<li className="mr-2" role="presentation">
|
||||
<li role="presentation">
|
||||
<Tab navigationType={NavigationType.Media} onNavigate={onNavigate}>
|
||||
<PhotoIcon className={`h-6 w-auto mr-2`} />
|
||||
<PhotoIcon className={`h-4 w-auto mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsMedia)}</span>
|
||||
</Tab>
|
||||
</li>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.snippets.view}>
|
||||
<li className="mr-2" role="presentation">
|
||||
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.snippets.view}>
|
||||
<li role="presentation">
|
||||
<Tab navigationType={NavigationType.Snippets} onNavigate={onNavigate}>
|
||||
<ScissorsIcon className={`h-6 w-auto mr-2`} />
|
||||
<ScissorsIcon className={`h-4 w-auto mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsSnippets)}</span>
|
||||
</Tab>
|
||||
</li>
|
||||
</FeatureFlag>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.data.view}>
|
||||
<li className="mr-2" role="presentation">
|
||||
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.data.view}>
|
||||
<li role="presentation">
|
||||
<Tab navigationType={NavigationType.Data} onNavigate={onNavigate}>
|
||||
<CircleStackIcon className={`h-6 w-auto mr-2`} />
|
||||
<CircleStackIcon className={`h-4 w-auto mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsData)}</span>
|
||||
</Tab>
|
||||
</li>
|
||||
</FeatureFlag>
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
|
||||
<li className="mr-2" role="presentation">
|
||||
<FeatureFlag features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS} flag={FEATURE_FLAG.dashboard.taxonomy.view}>
|
||||
<li role="presentation">
|
||||
<Tab navigationType={NavigationType.Taxonomy} onNavigate={onNavigate}>
|
||||
<TagIcon className={`h-6 w-auto mr-2`} />
|
||||
<TagIcon className={`h-4 w-auto mr-2`} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardHeaderTabsTaxonomies)}</span>
|
||||
</Tab>
|
||||
</li>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<div className="flex flex-col h-full overflow-y-auto overflow-x-hidden">
|
||||
<Header header={header} folders={folders} totalPages={totalPages} settings={settings} />
|
||||
|
||||
<div
|
||||
|
||||
@@ -36,7 +36,7 @@ export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = ({
|
||||
return (
|
||||
<footer
|
||||
className={`w-full px-4 py-2 text-center space-x-8 flex items-center border-t ${isBacker ? 'justify-center' : 'justify-between'
|
||||
} bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}
|
||||
} bg-[var(--vscode-editor-background)] text-[var(--frontmatter-secondary-text)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
{isBacker ? (
|
||||
<span>
|
||||
|
||||
55
src/dashboardWebView/components/Media/CustomActions.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { runCustomScript } from '../../utils';
|
||||
import { CommandLineIcon } from '@heroicons/react/24/outline';
|
||||
import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ICustomActionsProps {
|
||||
filePath: string;
|
||||
scripts?: CustomScript[];
|
||||
showTrigger?: boolean;
|
||||
}
|
||||
|
||||
export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
|
||||
filePath,
|
||||
scripts,
|
||||
showTrigger = false,
|
||||
}: React.PropsWithChildren<ICustomActionsProps>) => {
|
||||
|
||||
const customActions = React.useMemo(() => {
|
||||
return (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.title}
|
||||
onClick={() => runCustomScript(script, filePath)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
|
||||
if (showTrigger) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
title={l10n.t(LocalizationKey.commonOpenCustomActions)}
|
||||
className='px-2 text-[var(--frontmatter-secondary-text)] hover:text-[var(--frontmatter-button-hoverBackground)] focus-visible:outline-none'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonOpenCustomActions)}</span>
|
||||
<CommandLineIconSolid className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{customActions}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{customActions}</>;
|
||||
};
|
||||
@@ -10,7 +10,7 @@ export const DetailsItem: React.FunctionComponent<IDetailsItemProps> = ({ title,
|
||||
<>
|
||||
<div className="py-3 flex justify-between text-sm font-medium">
|
||||
<dt className={`text-[var(--vscode-editor-foreground)]`}>{title}</dt>
|
||||
<dd className={`text-right text-[var(--vscode-foreground)]`}>
|
||||
<dd className={`text-right text-[var(--frontmatter-text)]`}>
|
||||
{details}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@@ -67,23 +67,23 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
fields?.forEach((field) => {
|
||||
if (field.name === "title") {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
|
||||
<DetailsItem key="title" title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
|
||||
);
|
||||
} else if (field.name === "caption") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
|
||||
<DetailsItem key="caption" title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
|
||||
);
|
||||
}
|
||||
} else if (field.name === "alt") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
|
||||
<DetailsItem key="alt" title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
|
||||
);
|
||||
}
|
||||
} else {
|
||||
items.push(
|
||||
<DetailsItem title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
|
||||
<DetailsItem key={field.name} title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -147,7 +147,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
)}
|
||||
<div className="mt-4 flex items-start justify-between">
|
||||
<div>
|
||||
<h2 className={`text-lg font-medium text-[var(--vscode-foreground)]`}>
|
||||
<h2 className={`text-lg font-medium text-[var(--frontmatter-text)]`}>
|
||||
{media.filename}
|
||||
</h2>
|
||||
<p className={`text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
@@ -169,7 +169,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
|
||||
{!showForm && (
|
||||
<>
|
||||
<h3 className={`text-base flex items-center text-[var(--vscode-foreground)]`}>
|
||||
<h3 className={`text-base flex items-center text-[var(--frontmatter-text)]`}>
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormMetadataTitle)}</span>
|
||||
<button onClick={onEdit}>
|
||||
<PencilSquareIcon className="w-4 h-4 ml-2" aria-hidden="true" />
|
||||
@@ -185,7 +185,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
|
||||
{!showForm && (
|
||||
<div>
|
||||
<h3 className={`text-base text-[var(--vscode-foreground)]`}>
|
||||
<h3 className={`text-base text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormInformationTitle)}
|
||||
</h3>
|
||||
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
AllContentFoldersAtom,
|
||||
AllStaticFoldersAtom,
|
||||
SelectedMediaFolderAtom,
|
||||
SettingsSelector,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
@@ -18,13 +17,15 @@ import { extname } from 'path';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
import { RefreshDashboardData } from '../Header/RefreshDashboardData';
|
||||
|
||||
export interface IFolderCreationProps { }
|
||||
|
||||
export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
props: React.PropsWithChildren<IFolderCreationProps>
|
||||
_: React.PropsWithChildren<IFolderCreationProps>
|
||||
) => {
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
|
||||
const { selectedFolder } = useMediaFolder();
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const allStaticFolders = useRecoilValue(AllStaticFoldersAtom);
|
||||
const allContentFolders = useRecoilValue(AllContentFoldersAtom);
|
||||
@@ -90,7 +91,7 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
|
||||
if (scripts.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<div className="flex flex-1 justify-start">
|
||||
{renderPostAssetsButton}
|
||||
<ChoiceButton
|
||||
title={l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}
|
||||
@@ -107,7 +108,7 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 justify-end">
|
||||
<div className="flex flex-1 justify-start space-x-2">
|
||||
{renderPostAssetsButton}
|
||||
<button
|
||||
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium focus:outline-none rounded text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50`}
|
||||
@@ -117,6 +118,8 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (
|
||||
<FolderPlusIcon className={`mr-2 h-6 w-6`} />
|
||||
<span className={``}>{l10n.t(LocalizationKey.dashboardMediaFolderCreationFolderCreate)}</span>
|
||||
</button>
|
||||
|
||||
<RefreshDashboardData />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FolderIcon } from '@heroicons/react/24/solid';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedMediaFolderAtom } from '../../state';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useMediaFolder from '../../hooks/useMediaFolder';
|
||||
|
||||
export interface IFolderItemProps {
|
||||
folder: string;
|
||||
@@ -15,7 +16,7 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
wsFolder,
|
||||
staticFolder
|
||||
}: React.PropsWithChildren<IFolderItemProps>) => {
|
||||
const [, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const { updateFolder } = useMediaFolder();
|
||||
|
||||
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
|
||||
|
||||
@@ -29,14 +30,14 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
|
||||
className={`group relative hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}
|
||||
>
|
||||
<button
|
||||
title={isContentFolder ? 'Content directory folder' : 'Public directory folder'}
|
||||
title={isContentFolder ? l10n.t(LocalizationKey.dashboardMediaFolderItemContentDirectory) : l10n.t(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
|
||||
className={`p-4 w-full flex flex-row items-center h-full`}
|
||||
onClick={() => setSelectedFolder(folder)}
|
||||
onClick={() => updateFolder(folder)}
|
||||
>
|
||||
<div className="relative mr-4">
|
||||
<FolderIcon className={`h-12 w-12`} />
|
||||
{isContentFolder && (
|
||||
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--vscode-foreground)]`}>
|
||||
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--frontmatter-text)]`}>
|
||||
C
|
||||
</span>
|
||||
)}
|
||||
|
||||
111
src/dashboardWebView/components/Media/FooterActions.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ClipboardIcon, CodeBracketIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
import { CustomScript, MediaInfo, Snippet, ViewData } from '../../../models';
|
||||
import { copyToClipboard } from '../../utils';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { CustomActions } from './CustomActions';
|
||||
|
||||
export interface IFooterActionsProps {
|
||||
media: MediaInfo;
|
||||
snippets: Snippet[];
|
||||
relPath?: string;
|
||||
viewData?: ViewData | undefined
|
||||
scripts?: CustomScript[];
|
||||
insertIntoArticle: () => void;
|
||||
insertSnippet: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
relPath,
|
||||
media,
|
||||
snippets,
|
||||
viewData,
|
||||
scripts,
|
||||
insertIntoArticle,
|
||||
insertSnippet,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IFooterActionsProps>) => {
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
return (
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]`}>
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'view'
|
||||
})}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
})}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonInsertSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => copyToClipboard(parseWinPath(relPath) || '')}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
<CustomActions
|
||||
filePath={media.fsPath}
|
||||
scripts={scripts}
|
||||
showTrigger />
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PlusIcon,
|
||||
VideoCameraIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
@@ -17,19 +17,22 @@ import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
LightboxAtom,
|
||||
SelectedItemActionAtom,
|
||||
SelectedMediaFolderSelector,
|
||||
SettingsSelector,
|
||||
ViewDataSelector
|
||||
} from '../../state';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { InfoDialog } from '../Modals/InfoDialog';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import { MediaSnippetForm } from './MediaSnippetForm';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ItemMenu } from './ItemMenu';
|
||||
import { getRelPath } from '../../utils';
|
||||
import { Snippet } from '../../../models';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { FooterActions } from './FooterActions';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -39,17 +42,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
media,
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const [, setLightbox] = useRecoilState(LightboxAtom);
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showSnippetSelection, setShowSnippetSelection] = useState(false);
|
||||
const [snippet, setSnippet] = useState<Snippet | undefined>(undefined);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [showSnippetFormDialog, setShowSnippetFormDialog] = useState(false);
|
||||
const [mediaData, setMediaData] = useState<any | undefined>(undefined);
|
||||
const [filename, setFilename] = useState<string | null>(null);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const { mediaFolder, mediaDetails, isAudio, isImage, isVideo } = useMediaInfo(media);
|
||||
|
||||
const relPath = useMemo(() => {
|
||||
return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder);
|
||||
@@ -74,19 +77,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return viewData?.data?.position && mediaSnippets.length > 0;
|
||||
}, [viewData, mediaSnippets]);
|
||||
|
||||
const getFolder = () => {
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
let relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
|
||||
return dirname(parseWinPath(relPath) || '');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getFileName = () => {
|
||||
return basename(parseWinPath(media.fsPath) || '');
|
||||
};
|
||||
@@ -190,77 +180,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getDimensions = () => {
|
||||
if (media.dimensions) {
|
||||
return `${media.dimensions.width} x ${media.dimensions.height}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getSize = () => {
|
||||
if (media?.size) {
|
||||
const size = media.size / (1024 * 1024);
|
||||
if (size > 1) {
|
||||
return `${size.toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size * 1024).toFixed(2)} KB`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getMediaDetails = () => {
|
||||
let sizeDetails = [];
|
||||
|
||||
const dimensions = getDimensions();
|
||||
if (dimensions) {
|
||||
sizeDetails.push(dimensions);
|
||||
}
|
||||
|
||||
const size = getSize();
|
||||
if (size) {
|
||||
sizeDetails.push(size);
|
||||
}
|
||||
|
||||
return sizeDetails.join(' - ');
|
||||
};
|
||||
|
||||
const openLightbox = useCallback(() => {
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
setLightbox(media.vsPath || '');
|
||||
}
|
||||
}, [media.vsPath]);
|
||||
|
||||
const updateMetadata = () => {
|
||||
setShowForm(true);
|
||||
setShowDetails(true);
|
||||
};
|
||||
|
||||
const isVideoFile = useMemo(() => {
|
||||
if (media.mimeType?.startsWith('video/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isAudioFile = useMemo(() => {
|
||||
if (media.mimeType?.startsWith('audio/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const isImageFile = useMemo(() => {
|
||||
if (
|
||||
media.mimeType?.startsWith('image/') &&
|
||||
!media.mimeType?.startsWith('image/vnd.adobe.photoshop')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [media]);
|
||||
|
||||
const renderMediaIcon = useMemo(() => {
|
||||
const path = media.fsPath;
|
||||
const extension = path.split('.').pop();
|
||||
@@ -273,15 +198,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
return <PhotoIcon className={`h-1/2 ${colors}`} />;
|
||||
}
|
||||
|
||||
if (isVideoFile) {
|
||||
if (isVideo) {
|
||||
icon = <VideoCameraIcon className={`h-4/6 ${colors}`} />;
|
||||
}
|
||||
|
||||
if (isAudioFile) {
|
||||
if (isAudio) {
|
||||
icon = <MusicalNoteIcon className={`h-4/6 ${colors}`} />;
|
||||
}
|
||||
|
||||
@@ -293,18 +218,18 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}, [media, isImageFile, isVideoFile, isAudioFile]);
|
||||
}, [media, isImage, isVideo, isAudio]);
|
||||
|
||||
const renderMedia = useMemo(() => {
|
||||
if (isAudioFile) {
|
||||
if (isAudio) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isVideoFile) {
|
||||
if (isVideo) {
|
||||
return <video src={media.vsPath} className="mx-auto object-cover" controls muted />;
|
||||
}
|
||||
|
||||
if (isImageFile) {
|
||||
if (isImage) {
|
||||
return (
|
||||
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
|
||||
);
|
||||
@@ -334,9 +259,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className={`group relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<li className={`group flex flex-col relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<button
|
||||
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImageFile ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
|
||||
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImage ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
|
||||
onClick={hasViewData ? undefined : openLightbox}
|
||||
>
|
||||
<div
|
||||
@@ -349,6 +274,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
>
|
||||
{renderMedia}
|
||||
</div>
|
||||
|
||||
<ItemSelection filePath={media.fsPath} />
|
||||
|
||||
{hasViewData && (
|
||||
<div
|
||||
className={`hidden group-hover/button:flex absolute top-0 right-0 bottom-0 left-0 items-center justify-center bg-black bg-opacity-70`}
|
||||
@@ -379,10 +307,13 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ItemSelection filePath={media.fsPath} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-12`}>
|
||||
|
||||
<div className={`relative py-4 pl-4 pr-12 grow`}>
|
||||
<ItemMenu
|
||||
media={media}
|
||||
relPath={relPath}
|
||||
@@ -391,50 +322,65 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
snippets={mediaSnippets}
|
||||
scripts={settings?.scripts}
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
showUpdateMedia={updateMetadata}
|
||||
showMediaDetails={() => setShowDetails(true)}
|
||||
showUpdateMedia={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
})}
|
||||
showMediaDetails={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'view'
|
||||
})}
|
||||
processSnippet={processSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
|
||||
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--vscode-foreground)]}`}>
|
||||
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--frontmatter-text)]`}>
|
||||
{basename(parseWinPath(media.fsPath) || '')}
|
||||
</p>
|
||||
{!isImageFile && media.metadata.title && (
|
||||
{!isImage && media.metadata.title && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.title}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.title}</span>
|
||||
</p>
|
||||
)}
|
||||
{media.metadata.caption && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.caption}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.caption}</span>
|
||||
</p>
|
||||
)}
|
||||
{!media.metadata.caption && media.metadata.alt && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
<b className={`mr-2 text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.alt}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>{media.metadata.alt}</span>
|
||||
</p>
|
||||
)}
|
||||
{(media?.size || media?.dimensions) && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-1`}>
|
||||
<b className={`mr-1 text-[var(--frontmatter-text)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonSize)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>
|
||||
{getMediaDetails()}
|
||||
<span className={`block mt-1 text-xs text-[var(--frontmatter-secondary-text)]`}>
|
||||
{mediaDetails}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FooterActions
|
||||
media={media}
|
||||
relPath={relPath}
|
||||
snippets={mediaSnippets}
|
||||
viewData={viewData?.data}
|
||||
scripts={settings?.scripts}
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
</li>
|
||||
|
||||
{showSnippetSelection && (
|
||||
@@ -459,29 +405,10 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</InfoDialog>
|
||||
)}
|
||||
|
||||
{showDetails && (
|
||||
<DetailsSlideOver
|
||||
imgSrc={media.vsPath || ''}
|
||||
size={getSize()}
|
||||
dimensions={getDimensions()}
|
||||
folder={getFolder()}
|
||||
media={media}
|
||||
showForm={showForm}
|
||||
isImageFile={isImageFile}
|
||||
isVideoFile={isVideoFile}
|
||||
onEdit={() => setShowForm(true)}
|
||||
onEditClose={() => setShowForm(false)}
|
||||
onDismiss={() => {
|
||||
setShowDetails(false);
|
||||
setShowForm(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showAlert && (
|
||||
<Alert
|
||||
title={`${l10n.t(LocalizationKey.commonDelete)}: ${basename(parseWinPath(media.fsPath) || '')}`}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaItemAlertDeleteDescription, getFolder())}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaItemAlertDeleteDescription, mediaFolder)}
|
||||
okBtnText={l10n.t(LocalizationKey.commonDelete)}
|
||||
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
|
||||
dismiss={() => setShowAlert(false)}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { CustomScript, MediaInfo, ScriptType, Snippet, ViewData } from '../../../models';
|
||||
import { ClipboardIcon, CodeBracketIcon, EllipsisHorizontalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { CustomScript, MediaInfo, Snippet, ViewData } from '../../../models';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { copyToClipboard } from '../../utils';
|
||||
import { CustomActions } from './CustomActions';
|
||||
|
||||
export interface IItemMenuProps {
|
||||
media: MediaInfo;
|
||||
@@ -17,7 +18,6 @@ export interface IItemMenuProps {
|
||||
snippets: Snippet[];
|
||||
scripts?: CustomScript[];
|
||||
insertIntoArticle: () => void;
|
||||
insertSnippet: () => void;
|
||||
showUpdateMedia: () => void;
|
||||
showMediaDetails: () => void;
|
||||
processSnippet: (snippet: Snippet) => void;
|
||||
@@ -32,26 +32,16 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
snippets,
|
||||
scripts,
|
||||
insertIntoArticle,
|
||||
insertSnippet,
|
||||
showUpdateMedia,
|
||||
showMediaDetails,
|
||||
processSnippet,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IItemMenuProps>) => {
|
||||
|
||||
const copyToClipboard = React.useCallback(() => {
|
||||
if (relPath) {
|
||||
messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || '');
|
||||
}
|
||||
const onCopyToClipboard = React.useCallback(() => {
|
||||
copyToClipboard(parseWinPath(relPath) || '');
|
||||
}, [relPath]);
|
||||
|
||||
const runCustomScript = React.useCallback((script: CustomScript) => {
|
||||
messageHandler.send(DashboardMessage.runCustomScript, {
|
||||
script,
|
||||
path: media.fsPath
|
||||
});
|
||||
}, [media]);
|
||||
|
||||
const revealMedia = React.useCallback(() => {
|
||||
messageHandler.send(DashboardMessage.revealMedia, {
|
||||
file: media.fsPath,
|
||||
@@ -59,81 +49,22 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
});
|
||||
}, [selectedFolder]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
return (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
return (
|
||||
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-1 -mr-2 -mt-1 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<div className="relative z-10 flex text-left">
|
||||
<div className="hidden group-hover/actions:flex">
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)} onClick={showMediaDetails}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} onClick={showUpdateMedia}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={showMediaDetails}>
|
||||
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonView)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={showUpdateMedia}>
|
||||
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
@@ -160,21 +91,19 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
|
||||
{customScriptActions}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuItem onClick={copyToClipboard}>
|
||||
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{customScriptActions}
|
||||
</>
|
||||
<DropdownMenuItem onClick={onCopyToClipboard}>
|
||||
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
<CustomActions
|
||||
filePath={media.fsPath}
|
||||
scripts={scripts} />
|
||||
|
||||
<DropdownMenuItem onClick={revealMedia}>
|
||||
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)}</span>
|
||||
|
||||
@@ -20,13 +20,15 @@ import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { FolderItem } from './FolderItem';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import { STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
|
||||
import { GeneralCommands, STATIC_FOLDER_PLACEHOLDER, TelemetryEvent } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { basename, extname, join } from 'path';
|
||||
import { MediaInfo } from '../../../models';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { MediaItemPanel } from './MediaItemPanel';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
|
||||
export interface IMediaProps { }
|
||||
|
||||
@@ -148,9 +150,16 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsMedia));
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewMediaView
|
||||
});
|
||||
|
||||
Messenger.send(GeneralCommands.toVSCode.logging.info, {
|
||||
message: `Media view loaded`,
|
||||
location: 'DASHBOARD'
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { getRootProps, isDragActive } = useDropzone({
|
||||
@@ -162,111 +171,115 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="w-full h-full pb-6" {...getRootProps()}>
|
||||
{viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
|
||||
<p className={`opacity-80 text-base`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDragActive && (
|
||||
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
|
||||
<ArrowUpTrayIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder
|
||||
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
|
||||
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMedia.length === 0 && folders.length === 0 && !loading && (
|
||||
<div className={`flex items-center justify-center h-full`}>
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon
|
||||
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
|
||||
/>
|
||||
|
||||
<p className={`text-xl font-medium`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
|
||||
<FilesProvider files={allMedia}>
|
||||
<PageLayout>
|
||||
<div className="w-full h-full pb-6" {...getRootProps()}>
|
||||
{viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
|
||||
<p className={`opacity-80 text-base`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{contentFolders &&
|
||||
contentFolders.length > 0 &&
|
||||
contentFolders.map(
|
||||
(group, idx) =>
|
||||
group.folders &&
|
||||
group.folders.length > 0 && (
|
||||
<div key={`group-${idx}`} className={`mb-8`}>
|
||||
<h2 className="text-lg mb-8 first-letter:uppercase">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
|
||||
</h2>
|
||||
|
||||
<List gap={0}>
|
||||
{group.folders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{publicFolders && publicFolders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
{contentFolders && contentFolders.length > 0 && (
|
||||
<h2 className="text-lg mb-8">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
|
||||
{currentStaticFolder && (
|
||||
<span>
|
||||
: <b>{currentStaticFolder}</b>
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
{isDragActive && (
|
||||
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--frontmatter-text)] bg-[var(--vscode-editor-background)] opacity-75`}>
|
||||
<ArrowUpTrayIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder
|
||||
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
|
||||
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMedia.length === 0 && folders.length === 0 && !loading && (
|
||||
<div className={`flex items-center justify-center h-full`}>
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon
|
||||
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
|
||||
/>
|
||||
|
||||
<p className={`text-xl font-medium`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{contentFolders &&
|
||||
contentFolders.length > 0 &&
|
||||
contentFolders.map(
|
||||
(group, idx) =>
|
||||
group.folders &&
|
||||
group.folders.length > 0 && (
|
||||
<div key={`group-${idx}`} className={`mb-8`}>
|
||||
<h2 className="text-lg mb-8 first-letter:uppercase">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
|
||||
</h2>
|
||||
|
||||
<List gap={0}>
|
||||
{group.folders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
<List gap={0}>
|
||||
{publicFolders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)}
|
||||
{publicFolders && publicFolders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
{contentFolders && contentFolders.length > 0 && (
|
||||
<h2 className="text-lg mb-8">
|
||||
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
|
||||
{currentStaticFolder && (
|
||||
<span>
|
||||
: <b>{currentStaticFolder}</b>
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<List>
|
||||
{allMedia.map((file, idx) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
<List gap={0}>
|
||||
{publicFolders.map((folder) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
folder={folder}
|
||||
staticFolder={currentStaticFolder}
|
||||
wsFolder={settings?.wsFolder}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && <Spinner />}
|
||||
<List>
|
||||
{allMedia.map((file, idx) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
|
||||
<Lightbox />
|
||||
<MediaItemPanel allMedia={allMedia} />
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
{loading && <Spinner />}
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
|
||||
</PageLayout>
|
||||
<Lightbox />
|
||||
|
||||
<SponsorMsg
|
||||
beta={settings?.beta}
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
|
||||
</PageLayout>
|
||||
</FilesProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,14 +81,14 @@ export const MediaHeaderTop: React.FunctionComponent<
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`py-3 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
|
||||
className={`py-2 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<Searchbox placeholder={l10n.t(LocalizationKey.dashboardMediaMediaHeaderTopSearchboxPlaceholder)} />
|
||||
<FolderCreation />
|
||||
|
||||
<PaginationStatus />
|
||||
|
||||
<FolderCreation />
|
||||
<Searchbox placeholder={l10n.t(LocalizationKey.dashboardMediaMediaHeaderTopSearchboxPlaceholder)} />
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
67
src/dashboardWebView/components/Media/MediaItemPanel.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
import { MediaInfo } from '../../../models';
|
||||
import { DetailsSlideOver } from './DetailsSlideOver';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
|
||||
export interface IMediaItemPanelProps {
|
||||
allMedia: MediaInfo[];
|
||||
}
|
||||
|
||||
export const MediaItemPanel: React.FunctionComponent<IMediaItemPanelProps> = ({ allMedia }: React.PropsWithChildren<IMediaItemPanelProps>) => {
|
||||
const [crntPath, setCrntPath] = useState<string>('');
|
||||
const [media, setMedia] = useState<MediaInfo | undefined>(undefined);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [selectedItemAction, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const { mediaFolder, mediaSize, mediaDimensions, isImage, isVideo } = useMediaInfo(media);
|
||||
|
||||
const onDismiss = () => {
|
||||
setShowDetails(false);
|
||||
setShowForm(false);
|
||||
setMedia(undefined);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedItemAction && selectedItemAction.path) {
|
||||
const mediaFile = allMedia.find((m) => m.fsPath === selectedItemAction.path);
|
||||
setMedia(mediaFile);
|
||||
setCrntPath(selectedItemAction.path);
|
||||
|
||||
if (selectedItemAction.action === 'edit') {
|
||||
setShowForm(true);
|
||||
setShowDetails(true);
|
||||
} else if (selectedItemAction.action === 'view') {
|
||||
setShowForm(false);
|
||||
setShowDetails(true);
|
||||
}
|
||||
|
||||
setSelectedItemAction(undefined);
|
||||
} else if (crntPath) {
|
||||
const mediaFile = allMedia.find((m) => m.fsPath === crntPath);
|
||||
setMedia(mediaFile);
|
||||
}
|
||||
}, [allMedia, selectedItemAction, crntPath]);
|
||||
|
||||
if (showDetails && media) {
|
||||
return (
|
||||
<DetailsSlideOver
|
||||
imgSrc={media.vsPath || ''}
|
||||
size={mediaSize}
|
||||
dimensions={mediaDimensions}
|
||||
folder={mediaFolder}
|
||||
media={media}
|
||||
showForm={showForm}
|
||||
isImageFile={isImage}
|
||||
isVideoFile={isVideo}
|
||||
onEdit={() => setShowForm(true)}
|
||||
onEditClose={() => setShowForm(false)}
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { ViewDataSelector } from '../../state';
|
||||
import SnippetForm, { SnippetFormHandle } from '../SnippetsView/SnippetForm';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { SnippetSlideOver } from './SnippetSlideOver';
|
||||
import { SlideOver } from '../Modals/SlideOver';
|
||||
|
||||
export interface IMediaSnippetFormProps {
|
||||
media: MediaInfo;
|
||||
@@ -32,7 +32,7 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
|
||||
};
|
||||
|
||||
return (
|
||||
<SnippetSlideOver
|
||||
<SlideOver
|
||||
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.metadata.title || media.filename)}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.metadata.title || media.filename)}
|
||||
isSaveDisabled={false}
|
||||
@@ -48,6 +48,6 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
|
||||
selection={viewData?.data?.selection}
|
||||
onInsert={onInsert}
|
||||
/>
|
||||
</SnippetSlideOver>
|
||||
</SlideOver>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
disabled
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<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)]`}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({
|
||||
type="button"
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)}
|
||||
className={cn(`px-2 group inline-flex justify-center text-sm font-medium text-[var(--frontmatter-text)] hover:text-[var(--frontmatter-button-hoverBackground)]`, className)}
|
||||
>
|
||||
{children}
|
||||
<span className="sr-only">{title}</span>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Fragment, useRef } from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ISnippetSlideOverProps {
|
||||
export interface ISlideOverProps {
|
||||
title: string;
|
||||
description: string;
|
||||
okBtnText: string;
|
||||
@@ -16,7 +16,7 @@ export interface ISnippetSlideOverProps {
|
||||
trigger: () => void;
|
||||
}
|
||||
|
||||
export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> = ({
|
||||
export const SlideOver: React.FunctionComponent<ISlideOverProps> = ({
|
||||
title,
|
||||
description,
|
||||
okBtnText,
|
||||
@@ -25,7 +25,7 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
|
||||
dismiss,
|
||||
trigger,
|
||||
children
|
||||
}: React.PropsWithChildren<ISnippetSlideOverProps>) => {
|
||||
}: React.PropsWithChildren<ISlideOverProps>) => {
|
||||
const cancelButtonRef = useRef(null);
|
||||
|
||||
return (
|
||||
@@ -46,11 +46,12 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
|
||||
>
|
||||
<div className="pointer-events-auto w-screen max-w-md">
|
||||
<div className={`flex h-full flex-col overflow-y-scroll border-l pb-6 shadow-xl bg-[var(--vscode-sideBar-background)] border-[var(--frontmatter-border)]`}>
|
||||
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)]">
|
||||
<div className="py-6 sticky top-0 z-10 px-4 sm:px-6 bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBarTitle-foreground)]">
|
||||
<div className="flex items-start justify-between">
|
||||
<Dialog.Title className={`text-lg font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
<Dialog.Title className={`text-lg font-medium`}>
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
|
||||
<div className="ml-3 flex h-7 items-center">
|
||||
<button
|
||||
type="button"
|
||||
@@ -62,12 +63,14 @@ export const SnippetSlideOver: React.FunctionComponent<ISnippetSlideOverProps> =
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-1">
|
||||
<p className="text-sm">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex-1 px-4 sm:px-6">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">{description}</p>
|
||||
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
@@ -8,11 +8,14 @@ import { AstroContentTypes } from '../Configuration/Astro/AstroContentTypes';
|
||||
import { ContentFolders } from '../Configuration/Common/ContentFolders';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { COMMAND_NAME } from '../../../constants';
|
||||
import { COMMAND_NAME, TelemetryEvent } 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';
|
||||
|
||||
export interface ISettingsViewProps { }
|
||||
|
||||
@@ -20,6 +23,14 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.commonSettings));
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewSettings
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||
import { useCallback } from 'react';
|
||||
import { openFile } from '../../utils/MessageHandlers';
|
||||
|
||||
export interface IFooterActionsProps {
|
||||
insertEnabled: boolean;
|
||||
sourcePath?: string;
|
||||
onEdit?: () => void;
|
||||
onInsert: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
insertEnabled,
|
||||
sourcePath,
|
||||
onEdit,
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IFooterActionsProps>) => {
|
||||
|
||||
const showFile = useCallback(() => {
|
||||
openFile(sourcePath);
|
||||
}, [sourcePath]);
|
||||
|
||||
if (!onEdit && !onDelete && !sourcePath && !insertEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)] z-50`}>
|
||||
{insertEnabled && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonInsertSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={onInsert}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.commonInsertSnippet)}</span>
|
||||
</QuickAction>
|
||||
)}
|
||||
|
||||
{
|
||||
!sourcePath ? (
|
||||
<>
|
||||
{
|
||||
onEdit && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={onEdit}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}</span>
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
onDelete && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}</span>
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={showFile}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}</span>
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,8 @@
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import {
|
||||
CodeBracketIcon,
|
||||
DocumentTextIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
EyeIcon,
|
||||
PencilIcon,
|
||||
PhotoIcon,
|
||||
PlusIcon,
|
||||
TrashIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
} from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@@ -18,13 +12,14 @@ import { SnippetParser } from '../../../helpers/SnippetParser';
|
||||
import { Snippet, Snippets } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { FormDialog } from '../Modals/FormDialog';
|
||||
import { NewForm } from './NewForm';
|
||||
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { FooterActions } from './FooterActions';
|
||||
import { ItemMenu } from './ItemMenu';
|
||||
import { SlideOver } from '../Modals/SlideOver';
|
||||
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
|
||||
|
||||
export interface IItemProps {
|
||||
snippetKey: string;
|
||||
@@ -64,10 +59,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setMediaSnippet(false);
|
||||
};
|
||||
|
||||
const showFile = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.openFile, snippet.sourcePath);
|
||||
}, [snippet]);
|
||||
|
||||
const onOpenEdit = useCallback(() => {
|
||||
setSnippetTitle(snippet.title || snippetKey);
|
||||
setSnippetDescription(snippet.description);
|
||||
@@ -161,95 +152,67 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className={`group relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border p-4 space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
<CodeBracketIcon className={`w-64 h-64 opacity-5 text-[var(--vscode-foreground)]`} />
|
||||
<li className={`group flex flex-col relative overflow-hidden shadow-md hover:shadow-xl dark:shadow-none border space-y-2 rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border-[var(--frontmatter-border)]`}>
|
||||
<div className='p-4 grow space-y-2'>
|
||||
<h2
|
||||
className="font-bold flex items-center"
|
||||
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
|
||||
>
|
||||
<CodeBracketIcon className="w-5 h-5 mr-2" aria-hidden={true} />
|
||||
|
||||
{snippet.title || snippetKey}
|
||||
</h2>
|
||||
|
||||
<FeatureFlag
|
||||
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
|
||||
flag={FEATURE_FLAG.dashboard.snippets.manage}
|
||||
alternative={
|
||||
<ItemMenu
|
||||
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
|
||||
sourcePath={snippet.sourcePath}
|
||||
onInsert={() => setShowInsertDialog(true)} />
|
||||
}
|
||||
>
|
||||
<ItemMenu
|
||||
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
|
||||
sourcePath={snippet.sourcePath}
|
||||
onEdit={onOpenEdit}
|
||||
onInsert={() => setShowInsertDialog(true)}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
</FeatureFlag>
|
||||
|
||||
<div className='inline-block mr-1 mt-1 text-xs text-[var(--vscode-button-secondaryForeground)] bg-[var(--vscode-button-secondaryBackground)] border border-[var(--frontmatter-border)] rounded px-1 py-0.5'>
|
||||
{
|
||||
snippet.isMediaSnippet ? l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeContent) : l10n.t(LocalizationKey.dashboardSnippetsViewItemTypeMedia)
|
||||
}
|
||||
</div>
|
||||
|
||||
<p className={`text-xs text-[var(--frontmatter-text)]`}>{snippet.description}</p>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
className="mt-2 mb-2 font-bold flex items-center"
|
||||
title={snippet.isMediaSnippet ? 'Media snippet' : 'Content snippet'}
|
||||
>
|
||||
{snippet.isMediaSnippet ? (
|
||||
<PhotoIcon className="w-5 h-5 mr-1" aria-hidden={true} />
|
||||
) : (
|
||||
<DocumentTextIcon className="w-5 h-5 mr-1" aria-hidden={true} />
|
||||
)}
|
||||
|
||||
{snippet.title || snippetKey}
|
||||
</h2>
|
||||
|
||||
<FeatureFlag
|
||||
features={mode?.features || []}
|
||||
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
|
||||
flag={FEATURE_FLAG.dashboard.snippets.manage}
|
||||
alternative={
|
||||
insertToContent ? (
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
|
||||
<div className="group-hover:hidden">
|
||||
<EllipsisHorizontalIcon className="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div className="hidden group-hover:flex">
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonInsertSnippet)}
|
||||
onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
<FooterActions
|
||||
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
|
||||
sourcePath={snippet.sourcePath}
|
||||
onInsert={() => setShowInsertDialog(true)} />
|
||||
}
|
||||
>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover:bg-[var(--vscode-sideBar-background)] group-hover:border-[var(--frontmatter-border)]`}>
|
||||
<div className="group-hover:hidden">
|
||||
<EllipsisHorizontalIcon className="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div className="hidden group-hover:flex">
|
||||
{insertToContent && !snippet.isMediaSnippet && (
|
||||
<>
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!snippet.sourcePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionEditSnippet)}
|
||||
onClick={onOpenEdit}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionDeleteSnippet)}
|
||||
onClick={() => setShowAlert(true)}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</>
|
||||
) : (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemQuickActionViewSnippet)}
|
||||
onClick={showFile}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FooterActions
|
||||
insertEnabled={!!(insertToContent && !snippet.isMediaSnippet)}
|
||||
sourcePath={snippet.sourcePath}
|
||||
onEdit={onOpenEdit}
|
||||
onInsert={() => setShowInsertDialog(true)}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
</FeatureFlag>
|
||||
|
||||
<p className={`text-xs text-[var(--vscode-foreground)]`}>{snippet.description}</p>
|
||||
</li>
|
||||
|
||||
{showInsertDialog && (
|
||||
<FormDialog
|
||||
<SlideOver
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogTitle, snippet.title || snippetKey)}
|
||||
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
|
||||
description={snippet.description || l10n.t(LocalizationKey.dashboardSnippetsViewItemInsertFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
|
||||
isSaveDisabled={!insertToContent}
|
||||
trigger={insertToArticle}
|
||||
dismiss={() => setShowInsertDialog(false)}
|
||||
@@ -263,11 +226,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
filePath={viewData?.data?.filePath}
|
||||
fieldInfo={viewData?.data?.snippetInfo?.fields}
|
||||
selection={viewData?.data?.selection} />
|
||||
</FormDialog>
|
||||
</SlideOver>
|
||||
)}
|
||||
|
||||
{showEditDialog && (
|
||||
<FormDialog
|
||||
<SlideOver
|
||||
title={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogTitle, snippet.title || snippetKey)}
|
||||
description={l10n.t(LocalizationKey.dashboardSnippetsViewItemEditFormDialogDescription, (snippet.title || snippetKey).toLowerCase())}
|
||||
isSaveDisabled={!snippetTitle || !snippetOriginalBody}
|
||||
@@ -286,7 +249,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
|
||||
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)}
|
||||
/>
|
||||
</FormDialog>
|
||||
</SlideOver>
|
||||
)}
|
||||
|
||||
{showAlert && (
|
||||
|
||||