mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Compare commits
3 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a387d5eb89 | ||
|
|
f1ae60f280 | ||
|
|
0e2aea626f |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,46 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## [10.10.0] - 2025-xx-xx
|
||||
|
||||
- Removed the chatbot command and all related code and references
|
||||
- [#983](https://github.com/estruyf/vscode-front-matter/issues/983): Removal of the `frontMatter.sponsors.ai.enabled` features
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#937](https://github.com/estruyf/vscode-front-matter/issues/937): Dashboard "Structure" view for documentation sites *WIP*
|
||||
- [#965](https://github.com/estruyf/vscode-front-matter/issues/965): Added SEO support for the keyword in the first paragraph
|
||||
- [#973](https://github.com/estruyf/vscode-front-matter/issues/973): Support for number fields in the snippets
|
||||
- [#990](https://github.com/estruyf/vscode-front-matter/issues/990): Schema and validation for front matter in markdown files. It can be turned off by the `frontMatter.validation.enabled` setting.
|
||||
- [#1005](https://github.com/estruyf/vscode-front-matter/issues/1005): Support the integrated VSCode browser for the preview command
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#950](https://github.com/estruyf/vscode-front-matter/issues/950): Fix for template is not applied to new content type when created
|
||||
- [#958](https://github.com/estruyf/vscode-front-matter/issues/958): Fix variable frontmatter leads to error
|
||||
- [#984](https://github.com/estruyf/vscode-front-matter/issues/984): Fix in `frontMatter.global.timezone` is invalid
|
||||
- [#1004](https://github.com/estruyf/vscode-front-matter/issues/1004): Fix for `mediaDB.json` containing full paths on Windows instead of relative paths
|
||||
- [#1006](https://github.com/estruyf/vscode-front-matter/issues/1006): Fix output channel colorizer schema to only apply to the Front Matter output channel
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#969](https://github.com/estruyf/vscode-front-matter/issues/969): Fix typo on welcome screen
|
||||
|
||||
## [10.9.0] - 2025-07-01 - [Release notes](https://beta.frontmatter.codes/updates/v10.9.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- [#962](https://github.com/estruyf/vscode-front-matter/issues/962): Added Simplified Chinese localization thanks to [Randerion(HaoJun0823)](https://github.com/HaoJun0823)
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- [#922](https://github.com/estruyf/vscode-front-matter/issues/922): Added the `{{slugifiedFileName}}` for better naming
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#933](https://github.com/estruyf/vscode-front-matter/issues/933): Timezone setting integration in the DateTime field
|
||||
- [#942](https://github.com/estruyf/vscode-front-matter/issues/942): Fix to typo on welcome screen thanks to [Stephanie Wertman](https://github.com/stephanie-wertman)
|
||||
- [#957](https://github.com/estruyf/vscode-front-matter/issues/957): Fix media assets retrieval where `mtime` is not defined. Fallback to the `mtimeMs` property if available.
|
||||
|
||||
## [10.8.0] - 2025-02-27 - [Release notes](https://beta.frontmatter.codes/updates/v10.8.0)
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
@@ -117,7 +117,7 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
|
||||
You can get the extension via:
|
||||
|
||||
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
|
||||
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
|
||||
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
|
||||
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
|
||||
|
||||
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
|
||||
@@ -129,7 +129,7 @@ If you have the courage to test out the beta features, we made available a beta
|
||||
- Uninstall the main Front Matter version
|
||||
- Install the beta version
|
||||
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
|
||||
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
|
||||
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
|
||||
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
|
||||
|
||||
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).
|
||||
|
||||
@@ -115,7 +115,7 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
|
||||
You can get the extension via:
|
||||
|
||||
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
|
||||
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
|
||||
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
|
||||
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
|
||||
|
||||
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
|
||||
@@ -127,7 +127,7 @@ If you have the courage to test out the beta features, we made available a beta
|
||||
- Uninstall the main Front Matter version
|
||||
- Install the beta version
|
||||
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
|
||||
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
|
||||
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
|
||||
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
|
||||
|
||||
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).
|
||||
|
||||
@@ -109,7 +109,6 @@
|
||||
"dashboard.header.tabs.taxonomies": "Taxonomien",
|
||||
"dashboard.header.viewSwitch.toGrid": "Zur Rasteransicht wechseln",
|
||||
"dashboard.header.viewSwitch.toList": "Zur Listenansicht wechseln",
|
||||
"dashboard.header.viewSwitch.toStructure": "Zur Strukturansicht wechseln",
|
||||
"dashboard.layout.sponsor.support.msg": "Unterstützen Sie Front Matter",
|
||||
"dashboard.layout.sponsor.review.label": "Bewerten",
|
||||
"dashboard.layout.sponsor.review.msg": "Bewerten Sie Front Matter",
|
||||
|
||||
@@ -109,7 +109,6 @@
|
||||
"dashboard.header.tabs.taxonomies": "Taxonomies",
|
||||
"dashboard.header.viewSwitch.toGrid": "Afficher en grille",
|
||||
"dashboard.header.viewSwitch.toList": "Afficher en liste",
|
||||
"dashboard.header.viewSwitch.toStructure": "Afficher en structure",
|
||||
"dashboard.layout.sponsor.support.msg": "Soutenir Front Matter",
|
||||
"dashboard.layout.sponsor.review.label": "Donnez votre avis",
|
||||
"dashboard.layout.sponsor.review.msg": "Donnez votre avis sur Front Matter",
|
||||
|
||||
@@ -214,7 +214,6 @@
|
||||
|
||||
"dashboard.header.viewSwitch.toGrid": "グリッド表示",
|
||||
"dashboard.header.viewSwitch.toList": "リスト表示",
|
||||
"dashboard.header.viewSwitch.toStructure": "構造表示",
|
||||
|
||||
"dashboard.layout.sponsor.support.msg": "Front Matterをサポートする",
|
||||
"dashboard.layout.sponsor.review.label": "評価する",
|
||||
|
||||
@@ -222,7 +222,6 @@
|
||||
|
||||
"dashboard.header.viewSwitch.toGrid": "Change to grid",
|
||||
"dashboard.header.viewSwitch.toList": "Change to list",
|
||||
"dashboard.header.viewSwitch.toStructure": "Change to structure",
|
||||
|
||||
"dashboard.layout.sponsor.support.msg": "Support Front Matter",
|
||||
"dashboard.layout.sponsor.review.label": "Review",
|
||||
@@ -333,7 +332,7 @@
|
||||
"dashboard.steps.stepsToGetStarted.tags.name": "Import all tags and categories (optional)",
|
||||
"dashboard.steps.stepsToGetStarted.tags.description": "Now that Front Matter knows all the content folders. Would you like to import all tags and categories from the available content?",
|
||||
"dashboard.steps.stepsToGetStarted.git.name": "Do you want to enable Git synchronization?",
|
||||
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to easily sync your changes with your repository.",
|
||||
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to eaily sync your changes with your repository.",
|
||||
"dashboard.steps.stepsToGetStarted.showDashboard.name": "Show the dashboard",
|
||||
"dashboard.steps.stepsToGetStarted.showDashboard.description": "Once all actions are completed, the dashboard can be loaded.",
|
||||
"dashboard.steps.stepsToGetStarted.template.name": "Use a configuration template",
|
||||
@@ -505,7 +504,6 @@
|
||||
"panel.seoKeywords.density": "Keyword density",
|
||||
"panel.seoKeywordInfo.validInfo.label": "Heading(s)",
|
||||
"panel.seoKeywordInfo.validInfo.content": "Content",
|
||||
"panel.seoKeywordInfo.validInfo.firstParagraph": "First paragraph",
|
||||
"panel.seoKeywordInfo.density.tooltip": "Recommended frequency: 0.75% - 1.5%",
|
||||
|
||||
"panel.seoKeywords.title": "Keywords",
|
||||
|
||||
@@ -1,822 +0,0 @@
|
||||
{
|
||||
"common.add": "添加",
|
||||
"common.edit": "编辑",
|
||||
"common.delete": "删除",
|
||||
"common.cancel": "取消",
|
||||
"common.apply": "应用",
|
||||
"common.clear": "清除",
|
||||
"common.clear.value": "清除值",
|
||||
"common.search": "搜索",
|
||||
"common.save": "保存",
|
||||
"common.menu": "菜单",
|
||||
"common.insert": "插入",
|
||||
"common.insert.snippet": "插入片段",
|
||||
"common.title": "标题",
|
||||
"common.description": "描述",
|
||||
"common.retry": "重试",
|
||||
"common.update": "更新",
|
||||
"common.information": "信息",
|
||||
"common.important": "重要",
|
||||
"common.sync": "同步",
|
||||
"common.slug": "别名",
|
||||
"common.support": "支持",
|
||||
"common.remove.value": "移除 {0}",
|
||||
"common.filter": "筛选",
|
||||
"common.filter.value": "按 {0} 筛选",
|
||||
"common.error.message": "抱歉,出错了。",
|
||||
"common.openOnWebsite": "在网站上打开",
|
||||
"common.settings": "设置",
|
||||
"common.refreshSettings": "刷新设置",
|
||||
"common.pin": "固定",
|
||||
"common.unpin": "取消固定",
|
||||
"common.noResults": "无结果",
|
||||
"common.error": "抱歉,出错了。",
|
||||
"common.yes": "是",
|
||||
"common.no": "否",
|
||||
"common.openSettings": "打开设置",
|
||||
"common.back": "返回",
|
||||
"common.open": "打开",
|
||||
"common.openWithValue": "打开:{0}",
|
||||
"common.openCustomActions": "打开自定义操作",
|
||||
"common.view": "查看",
|
||||
"common.translate": "翻译",
|
||||
"common.languages": "语言",
|
||||
"common.scripts": "脚本",
|
||||
"common.rename": "重命名",
|
||||
"common.docs": "文档",
|
||||
|
||||
"loading.initPages": "正在加载内容",
|
||||
|
||||
"notifications.outputChannel.link": "输出窗口",
|
||||
"notifications.outputChannel.description": "更多详情请查看 {0}。",
|
||||
|
||||
"settings.view.common": "通用",
|
||||
"settings.view.contentFolders": "内容文件夹",
|
||||
"settings.view.astro": "Astro",
|
||||
"settings.view.integration": "集成",
|
||||
|
||||
"settings.openOnStartup": "启动时打开仪表盘",
|
||||
"settings.openPanelForSupportedFiles": "为支持的文件打开面板",
|
||||
"settings.openPanelForSupportedFiles.label": "是否要为支持的文件打开面板?",
|
||||
"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 密钥",
|
||||
"settings.integrationsView.deepl.intput.placeholder": "输入您的 Deepl API 密钥",
|
||||
|
||||
"settings.integrationsView.azure.title": "Azure AI 翻译服务",
|
||||
"settings.integrationsView.azure.intput.label": "订阅密钥",
|
||||
"settings.integrationsView.azure.intput.placeholder": "输入您的 Azure AI 翻译 - 订阅密钥",
|
||||
"settings.integrationsView.azure.region.label": "区域",
|
||||
"settings.integrationsView.azure.region.placeholder": "输入您的 Azure AI 翻译 - 区域。例如:westeurope",
|
||||
|
||||
"developer.title": "开发者模式",
|
||||
"developer.reload.title": "重新加载仪表盘",
|
||||
"developer.reload.label": "重新加载",
|
||||
"developer.devTools.title": "打开开发者工具",
|
||||
"developer.devTools.label": "开发者工具",
|
||||
|
||||
"field.required": "必填字段",
|
||||
"field.unknown": "未知字段",
|
||||
|
||||
"dashboard.chatbot.answer.answer": "答案",
|
||||
"dashboard.chatbot.answer.resources": "资源",
|
||||
"dashboard.chatbot.answer.warning": "警告:答案可能有误。如有疑问,请查阅文档。",
|
||||
|
||||
"dashboard.chatbot.chatbot.loading": "助手正在准备中",
|
||||
"dashboard.chatbot.chatbot.ready": "我准备好了,您想知道什么?",
|
||||
|
||||
"dashboard.chatbot.chatbox.placeholder": "如何配置 Front Matter?",
|
||||
|
||||
"dashboard.chatbot.header.heading": "询问 Front Matter AI",
|
||||
"dashboard.chatbot.header.description": "我们的 AI 由 mendable.ai 提供支持,已处理文档,可以协助您解决任何关于 Front Matter 的疑问。请随时提问!",
|
||||
|
||||
"dashboard.common.choiceButton.open": "打开选项",
|
||||
|
||||
"dashboard.contents.contentActions.actionMenuButton.title": "菜单",
|
||||
"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": "<无效描述>",
|
||||
|
||||
"dashboard.contents.list.title": "标题",
|
||||
"dashboard.contents.list.date": "日期",
|
||||
"dashboard.contents.list.status": "状态",
|
||||
|
||||
"dashboard.contents.overview.noMarkdown": "无 Markdown 可显示",
|
||||
"dashboard.contents.overview.noFolders": "请确保在项目中注册了一个内容文件夹,以便 Front Matter 能够找到内容。",
|
||||
"dashboard.contents.overview.pinned": "已固定",
|
||||
|
||||
"dashboard.contents.status.draft": "草稿",
|
||||
"dashboard.contents.status.published": "已发布",
|
||||
"dashboard.contents.status.scheduled": "已排期",
|
||||
|
||||
"dashboard.dataView.dataForm.modify": "修改数据",
|
||||
"dashboard.dataView.dataForm.add": "添加新数据",
|
||||
|
||||
"dashboard.dataView.dataView.select": "选择您的数据类型",
|
||||
"dashboard.dataView.dataView.title": "您的 {0} 数据项",
|
||||
"dashboard.dataView.dataView.add": "添加新条目",
|
||||
"dashboard.dataView.dataView.empty": "未找到 {0} 数据条目",
|
||||
"dashboard.dataView.dataView.createOrModify": "创建或修改您的 {0} 数据",
|
||||
"dashboard.dataView.dataView.getStarted": "选择一个数据类型开始",
|
||||
"dashboard.dataView.dataView.noDataFiles": "未找到数据文件",
|
||||
"dashboard.dataView.dataView.getStarted.link": "阅读更多以开始使用数据文件",
|
||||
"dashboard.dataView.dataView.update.message": "已更新您的数据条目",
|
||||
"dashboard.dataView.dataView.createNew": "创建新数据文件",
|
||||
"dashboard.dataView.dataView.selectDataFolder": "选择数据文件夹",
|
||||
"dashboard.dataView.dataView.closeSelectedDataFile": "关闭数据文件",
|
||||
|
||||
"dashboard.dataView.emptyView.heading": "请先选择您的数据类型",
|
||||
"dashboard.dataView.emptyView.heading.create": "通过创建新的数据文件开始",
|
||||
|
||||
"dashboard.dataView.sortableItem.editButton.title": "编辑 \"{0}\"",
|
||||
"dashboard.dataView.sortableItem.deleteButton.title": "删除 \"{0}\"",
|
||||
"dashboard.dataView.sortableItem.alert.title": "删除数据条目",
|
||||
"dashboard.dataView.sortableItem.alert.description": "您确定要删除该数据条目吗?",
|
||||
|
||||
"dashboard.errorView.description": "请关闭仪表盘并重试。",
|
||||
|
||||
"dashboard.filters.languageFilter.label": "语言",
|
||||
"dashboard.filters.languageFilter.all": "全部",
|
||||
|
||||
"dashboard.header.actionsBar.itemsSelected": "已选 {0} 项",
|
||||
"dashboard.header.actionsBar.selectAll": "全选",
|
||||
"dashboard.header.actionsBar.alertDelete.title": "删除所选文件",
|
||||
"dashboard.header.actionsBar.alertDelete.description": "您确定要删除所选文件吗?",
|
||||
|
||||
"dashboard.header.breadcrumb.home": "首页",
|
||||
|
||||
"dashboard.header.clearFilters.title": "清除筛选、分组和排序",
|
||||
|
||||
"dashboard.header.filter.default": "无筛选条件",
|
||||
|
||||
"dashboard.header.folders.default": "所有类型",
|
||||
"dashboard.header.folders.menuButton.showing": "显示",
|
||||
|
||||
"dashboard.header.grouping.option.none": "无",
|
||||
"dashboard.header.grouping.option.year": "年份",
|
||||
"dashboard.header.grouping.option.draft": "草稿/已发布",
|
||||
"dashboard.header.grouping.menuButton.label": "分组方式",
|
||||
|
||||
"dashboard.header.navigation.allArticles": "所有文章",
|
||||
"dashboard.header.navigation.published": "已发布",
|
||||
"dashboard.header.navigation.scheduled": "已排期",
|
||||
"dashboard.header.navigation.draft": "草稿中",
|
||||
|
||||
"dashboard.header.header.createContent": "创建内容",
|
||||
"dashboard.header.header.createByContentType": "按内容类型创建",
|
||||
"dashboard.header.header.createByTemplate": "按模板创建",
|
||||
|
||||
"dashboard.header.pagination.first": "首页",
|
||||
"dashboard.header.pagination.previous": "上一页",
|
||||
"dashboard.header.pagination.next": "下一页",
|
||||
"dashboard.header.pagination.last": "末页",
|
||||
|
||||
"dashboard.header.paginationStatus.text": "显示第 {0} 到 {1} 条,共 {2} 条结果",
|
||||
|
||||
"dashboard.header.projectSwitcher.label": "项目",
|
||||
|
||||
"dashboard.header.refreshDashboard.label": "刷新仪表盘",
|
||||
|
||||
"dashboard.header.sorting.lastModified.asc": "最后修改时间 (升序)",
|
||||
"dashboard.header.sorting.lastModified.desc": "最后修改时间 (降序)",
|
||||
"dashboard.header.sorting.filename.asc": "按文件名 (升序)",
|
||||
"dashboard.header.sorting.filename.desc": "按文件名 (降序)",
|
||||
"dashboard.header.sorting.published.asc": "发布日期 (升序)",
|
||||
"dashboard.header.sorting.published.desc": "发布日期 (降序)",
|
||||
"dashboard.header.sorting.size.asc": "大小 (升序)",
|
||||
"dashboard.header.sorting.size.desc": "大小 (降序)",
|
||||
"dashboard.header.sorting.caption.asc": "说明文字 (升序)",
|
||||
"dashboard.header.sorting.caption.desc": "说明文字 (降序)",
|
||||
"dashboard.header.sorting.alt.asc": "替代文本 (升序)",
|
||||
"dashboard.header.sorting.alt.desc": "替代文本 (降序)",
|
||||
"dashboard.header.sorting.label": "排序方式",
|
||||
|
||||
"dashboard.header.startup.label": "启动时打开?",
|
||||
|
||||
"dashboard.header.tabs.contents": "内容",
|
||||
"dashboard.header.tabs.media": "媒体",
|
||||
"dashboard.header.tabs.snippets": "片段",
|
||||
"dashboard.header.tabs.data": "数据",
|
||||
"dashboard.header.tabs.taxonomies": "分类法",
|
||||
|
||||
"dashboard.header.viewSwitch.toGrid": "切换到网格视图",
|
||||
"dashboard.header.viewSwitch.toList": "切换到列表视图",
|
||||
"dashboard.header.viewSwitch.toStructure": "切换到结构视图",
|
||||
|
||||
"dashboard.layout.sponsor.support.msg": "支持 Front Matter",
|
||||
"dashboard.layout.sponsor.review.label": "评价",
|
||||
"dashboard.layout.sponsor.review.msg": "评价 Front Matter",
|
||||
|
||||
"dashboard.media.common.title": "标题",
|
||||
"dashboard.media.common.caption": "说明文字",
|
||||
"dashboard.media.common.alt": "替代文本",
|
||||
"dashboard.media.common.size": "大小",
|
||||
|
||||
"dashboard.media.dialog.title": "查看详情",
|
||||
"dashboard.media.panel.close": "关闭面板",
|
||||
"dashboard.media.metadata.panel.title": "更新元数据",
|
||||
"dashboard.media.metadata.panel.description": "请指定您要为文件设置的元数据。",
|
||||
"dashboard.media.metadata.panel.field.fileName": "文件名",
|
||||
"dashboard.media.metadata.panel.form.metadata.title": "元数据",
|
||||
"dashboard.media.metadata.panel.form.information.title": "信息",
|
||||
"dashboard.media.metadata.panel.form.information.createdDate": "创建时间",
|
||||
"dashboard.media.metadata.panel.form.information.modifiedDate": "最后修改时间",
|
||||
"dashboard.media.metadata.panel.form.information.dimensions": "尺寸",
|
||||
"dashboard.media.metadata.panel.form.information.folder": "文件夹",
|
||||
|
||||
"dashboard.media.folderCreation.hexo.create": "创建文章资源文件夹",
|
||||
"dashboard.media.folderCreation.folder.create": "创建新文件夹",
|
||||
|
||||
"dashboard.media.folderItem.contentDirectory": "内容目录",
|
||||
"dashboard.media.folderItem.publicDirectory": "公共目录",
|
||||
"dashboard.media.folderItem.deleteDescription": "您确定要删除该文件夹 ({0}) 吗?",
|
||||
|
||||
"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": "显示媒体位置",
|
||||
"dashboard.media.item.infoDialog.snippet.description": "选择要用于当前媒体文件的媒体片段。",
|
||||
"dashboard.media.item.alert.delete.description": "您确定要从 {0} 文件夹中删除该文件吗?",
|
||||
|
||||
"dashboard.media.media.description": "选择要添加到您内容中的媒体文件。",
|
||||
"dashboard.media.media.dragAndDrop": "您也可以从桌面拖放图片,上传后选择它们。",
|
||||
"dashboard.media.media.folder.upload": "上传到 {0}",
|
||||
"dashboard.media.media.folder.default": "未选择文件夹,您拖放的文件将被添加到 {0} 文件夹",
|
||||
"dashboard.media.media.placeholder": "无媒体文件可显示。按住 [Shift] 键可以拖放新文件。",
|
||||
"dashboard.media.media.contentFolder": "内容文件夹",
|
||||
"dashboard.media.media.publicFolder": "公共文件夹",
|
||||
|
||||
"dashboard.media.mediaHeaderTop.searchbox.placeholder": "在文件夹中搜索",
|
||||
|
||||
"dashboard.media.mediaSnippetForm.formDialog.title": "插入媒体:{0}",
|
||||
"dashboard.media.mediaSnippetForm.formDialog.description": "将 {0} 媒体文件插入当前文章",
|
||||
|
||||
"dashboard.preview.input.placeholder": "输入 URL",
|
||||
"dashboard.preview.button.navigate.title": "导航",
|
||||
"dashboard.preview.button.refresh.title": "刷新",
|
||||
"dashboard.preview.button.open.title": "打开",
|
||||
|
||||
"dashboard.snippetsView.item.type.content": "内容片段",
|
||||
"dashboard.snippetsView.item.type.media": "媒体片段",
|
||||
"dashboard.snippetsView.item.quickAction.editSnippet": "编辑片段",
|
||||
"dashboard.snippetsView.item.quickAction.deleteSnippet": "删除片段",
|
||||
"dashboard.snippetsView.item.quickAction.viewSnippet": "查看片段文件",
|
||||
"dashboard.snippetsView.item.insert.formDialog.title": "插入片段:{0}",
|
||||
"dashboard.snippetsView.item.insert.formDialog.description": "将 {0} 片段插入当前文章",
|
||||
"dashboard.snippetsView.item.edit.formDialog.title": "编辑片段:{0}",
|
||||
"dashboard.snippetsView.item.edit.formDialog.description": "编辑 {0} 片段",
|
||||
"dashboard.snippetsView.item.alert.title": "删除片段:{0}",
|
||||
"dashboard.snippetsView.item.alert.description": "您确定要删除 {0} 片段吗?",
|
||||
|
||||
"dashboard.snippetsView.newForm.snippetInput.title.placeholder": "片段标题",
|
||||
"dashboard.snippetsView.newForm.snippetInput.description.label": "描述",
|
||||
"dashboard.snippetsView.newForm.snippetInput.description.placeholder": "片段描述",
|
||||
"dashboard.snippetsView.newForm.snippetInput.snippet.label": "片段",
|
||||
"dashboard.snippetsView.newForm.snippetInput.snippet.placeholder": "片段内容",
|
||||
"dashboard.snippetsView.newForm.snippetInput.isMediaSnippet.label": "是媒体片段吗?",
|
||||
"dashboard.snippetsView.newForm.snippetInput.isMediaSnippet.checkbox.label": "媒体片段",
|
||||
"dashboard.snippetsView.newForm.snippetInput.isMediaSnippet.checkbox.description": "使用当前片段将媒体文件插入您的内容中。",
|
||||
"dashboard.snippetsView.newForm.snippetInput.docsButton.title": "阅读更多关于使用媒体片段占位符的信息",
|
||||
"dashboard.snippetsView.newForm.snippetInput.docsButton.description": "查看我们的媒体片段占位符文档,了解可以使用的占位符。",
|
||||
|
||||
"dashboard.snippetsView.snippets.ariaLabel": "片段标题",
|
||||
"dashboard.snippetsView.snippets.button.create": "创建新片段",
|
||||
"dashboard.snippetsView.snippets.select.description": "选择要添加到您内容中的片段。",
|
||||
"dashboard.snippetsView.snippets.empty.message": "未找到片段",
|
||||
"dashboard.snippetsView.snippets.readMore": "阅读更多以开始使用片段",
|
||||
"dashboard.snippetsView.snippets.formDialog.title": "创建片段",
|
||||
|
||||
"dashboard.steps.stepsToGetStarted.button.addFolder.title": "添加为 Front Matter 的内容文件夹",
|
||||
"dashboard.steps.stepsToGetStarted.initializeProject.name": "初始化项目",
|
||||
"dashboard.steps.stepsToGetStarted.initializeProject.description": "初始化项目将创建使用 Front Matter CMS 所需的文件和文件夹。点击此操作开始。",
|
||||
"dashboard.steps.stepsToGetStarted.framework.name": "框架预设",
|
||||
"dashboard.steps.stepsToGetStarted.framework.description": "选择您的站点生成器或框架以预填充一些推荐设置。",
|
||||
"dashboard.steps.stepsToGetStarted.framework.select": "选择您的框架",
|
||||
"dashboard.steps.stepsToGetStarted.framework.select.other": "其他",
|
||||
"dashboard.steps.stepsToGetStarted.assetsFolder.name": "您的资源文件夹是什么?",
|
||||
"dashboard.steps.stepsToGetStarted.assetsFolder.description": "选择包含您资源的文件夹。此文件夹将用于存储您文章的所有媒体文件。",
|
||||
"dashboard.steps.stepsToGetStarted.assetsFolder.public.title": "使用 'public' 文件夹",
|
||||
"dashboard.steps.stepsToGetStarted.assetsFolder.assets.title": "使用 Astro 资源文件夹 (src/assets)",
|
||||
"dashboard.steps.stepsToGetStarted.assetsFolder.other.description": "如果您想配置其他文件夹,可以手动在 frontmatter.json 文件中进行设置。",
|
||||
"dashboard.steps.stepsToGetStarted.contentFolders.name": "注册内容文件夹",
|
||||
"dashboard.steps.stepsToGetStarted.contentFolders.description": "将我们在您项目中找到的一个文件夹添加为内容文件夹。设置文件夹后,Front Matter 可以列出所有内容并允许您创建内容。",
|
||||
"dashboard.steps.stepsToGetStarted.contentFolders.label": "包含内容的文件夹:",
|
||||
"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": "使用配置模板",
|
||||
"dashboard.steps.stepsToGetStarted.template.description": "选择一个模板,用推荐设置预填充 frontmatter.json 文件。",
|
||||
"dashboard.steps.stepsToGetStarted.template.warning": "选择模板会将整个配置应用到您的项目,并关闭此配置视图。",
|
||||
"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": "移动到其他分类法类型",
|
||||
"dashboard.taxonomyView.button.delete.title": "删除 {0}",
|
||||
|
||||
"dashboard.taxonomyView.taxonomyLookup.button.title": "显示包含 {1} 中 {0} 的内容",
|
||||
|
||||
"dashboard.taxonomyView.taxonomyManager.description": "创建、编辑和管理您网站的 {0}",
|
||||
"dashboard.taxonomyView.taxonomyManager.button.create": "创建新的 {0} 值",
|
||||
"dashboard.taxonomyView.taxonomyManager.table.heading.name": "名称",
|
||||
"dashboard.taxonomyView.taxonomyManager.table.heading.count": "数量",
|
||||
"dashboard.taxonomyView.taxonomyManager.table.heading.action": "操作",
|
||||
"dashboard.taxonomyView.taxonomyManager.table.row.empty": "未找到 {0}",
|
||||
"dashboard.taxonomyView.taxonomyManager.table.unmapped.title": "在您的设置中缺失",
|
||||
"dashboard.taxonomyView.taxonomyManager.filterInput.placeholder": "筛选",
|
||||
|
||||
"dashboard.taxonomyView.taxonomyTagging.pageTitle": "将您的内容映射到:{0}",
|
||||
"dashboard.taxonomyView.taxonomyTagging.checkbox": "使用 {0} 标记页面",
|
||||
|
||||
"dashboard.taxonomyView.taxonomyView.navigationBar.title": "选择分类法",
|
||||
"dashboard.taxonomyView.taxonomyView.button.import": "导入分类法",
|
||||
"dashboard.taxonomyView.taxonomyView.navigationItem.tags": "标签",
|
||||
"dashboard.taxonomyView.taxonomyView.navigationItem.categories": "分类",
|
||||
|
||||
"dashboard.unkownView.title": "视图不存在",
|
||||
"dashboard.unkownView.description": "您似乎进入了一个不存在的视图。请重新打开仪表盘。",
|
||||
|
||||
"dashboard.welcomeScreen.title": "使用 Front Matter 管理您的静态站点",
|
||||
"dashboard.welcomeScreen.thanks": "感谢您使用 Front Matter!",
|
||||
"dashboard.welcomeScreen.description": "我们致力于让 Front Matter 尽可能易于使用。如果您有任何问题或建议,请在 GitHub 上联系我们。",
|
||||
"dashboard.welcomeScreen.link.github.title": "GitHub",
|
||||
"dashboard.welcomeScreen.link.github.label": "GitHub",
|
||||
"dashboard.welcomeScreen.link.documentation.label": "文档",
|
||||
"dashboard.welcomeScreen.link.sponsor.title": "成为赞助商",
|
||||
"dashboard.welcomeScreen.link.sponsor.label": "赞助",
|
||||
"dashboard.welcomeScreen.link.review.title": "写评价",
|
||||
"dashboard.welcomeScreen.link.review.label": "评价",
|
||||
"dashboard.welcomeScreen.actions.heading": "执行以下步骤以开始使用该扩展",
|
||||
"dashboard.welcomeScreen.actions.description": "您也可以从 Front Matter 侧边栏使用该扩展。在那里您将找到可以专门为您的页面执行的操作。",
|
||||
"dashboard.welcomeScreen.actions.thanks": "我们希望您喜欢 Front Matter!",
|
||||
|
||||
"dashboard.media.detailsSlideOver.unmapped.description": "您是否要重新映射未映射文件的元数据?",
|
||||
|
||||
"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": "我们注意到内容类型和 front matter 数据之间存在字段差异。\n 您想为此内容创建、更新或设置内容类型吗?",
|
||||
"panel.contentType.contentTypeValidator.button.create": "创建内容类型",
|
||||
"panel.contentType.contentTypeValidator.button.add": "将缺失字段添加到内容类型",
|
||||
"panel.contentType.contentTypeValidator.button.change": "更改文件的内容类型",
|
||||
|
||||
"panel.dataBlock.dataBlockField.group.selected.edit": "正在编辑:{0}",
|
||||
"panel.dataBlock.dataBlockField.group.selected.create": "创建新的 {0}",
|
||||
"panel.dataBlock.dataBlockField.group.select": "选择一个分组",
|
||||
"panel.dataBlock.dataBlockField.add": "添加 {0}",
|
||||
|
||||
"panel.dataBlock.dataBlockRecord.edit": "编辑记录",
|
||||
"panel.dataBlock.dataBlockRecord.delete": "删除记录",
|
||||
|
||||
"panel.dataBlock.dataBlockRecords.label": "记录",
|
||||
|
||||
"panel.dataBlock.dataBlockSelector.label": "区块类型",
|
||||
|
||||
"panel.errorBoundary.fieldBoundary.label": "查看字段失败",
|
||||
|
||||
"panel.fields.choiceField.select": "选择 {0}",
|
||||
"panel.fields.choiceField.clear": "清除值",
|
||||
|
||||
"panel.fields.contentTypeRelationshipField.loading": "正在获取可能的值...",
|
||||
|
||||
"panel.fields.dateTimeField.button.pick": "选择日期",
|
||||
"panel.fields.dateTimeField.time": "时间:",
|
||||
|
||||
"panel.fields.fieldMessage.required": "{0} 字段是必填的",
|
||||
|
||||
"panel.fields.fileField.delete": "删除文件",
|
||||
"panel.fields.fileField.add": "添加您的 {0}",
|
||||
|
||||
"panel.fields.imageFallback.label": "图片加载失败",
|
||||
|
||||
"panel.fields.listField.edit": "编辑记录",
|
||||
"panel.fields.listField.delete": "删除记录",
|
||||
|
||||
"panel.fields.previewImage.remove": "移除图片",
|
||||
|
||||
"panel.fields.previewImageField.add": "添加您的 {0}",
|
||||
|
||||
"panel.fields.slugField.update": "有更新可用",
|
||||
"panel.fields.slugField.generate": "生成别名",
|
||||
|
||||
"panel.fields.textField.ai.message": "使用 Front Matter AI 建议 {0}",
|
||||
"panel.fields.textField.copilot.message": "使用 Copilot 建议 {0}",
|
||||
"panel.fields.textField.ai.generate": "正在生成建议...",
|
||||
"panel.fields.textField.loading": "正在加载字段",
|
||||
"panel.fields.textField.limit": "字段限制已达 {0}",
|
||||
|
||||
"panel.fields.wrapperField.unknown": "未知字段类型:{0}",
|
||||
|
||||
"panel.fields.fieldCustomAction.button.title": "自定义操作",
|
||||
"panel.fields.fieldCustomAction.executing": "正在执行字段操作...",
|
||||
|
||||
"panel.actions.title": "操作",
|
||||
|
||||
"panel.articleDetails.headings": "标题",
|
||||
"panel.articleDetails.paragraphs": "段落",
|
||||
"panel.articleDetails.internalLinks": "内部链接",
|
||||
"panel.articleDetails.externalLinks": "外部链接",
|
||||
"panel.articleDetails.images": "图片",
|
||||
|
||||
"panel.baseView.initialize": "初始化项目",
|
||||
"panel.baseView.actions.title": "操作",
|
||||
"panel.baseView.action.openDashboard": "打开仪表盘",
|
||||
"panel.baseView.action.createContent": "创建内容",
|
||||
"panel.baseView.empty": "打开文件以查看更多操作",
|
||||
|
||||
"panel.fileList.label.singular": "个文件",
|
||||
"panel.fileList.label.plural": "个文件",
|
||||
|
||||
"panel.folderAndFiles.title": "最近修改",
|
||||
|
||||
"panel.globalSettings.title": "全局设置",
|
||||
"panel.globalSettings.action.modifiedDate.label": "修改日期",
|
||||
"panel.globalSettings.action.modifiedDate.description": "自动更新修改日期",
|
||||
"panel.globalSettings.action.frontMatter.label": "Front Matter 高亮",
|
||||
"panel.globalSettings.action.frontMatter.description": "高亮显示 Front Matter",
|
||||
"panel.globalSettings.action.preview.label": "本地预览",
|
||||
"panel.globalSettings.action.preview.placeholder": "例如:{0}",
|
||||
"panel.globalSettings.action.server.label": "本地服务器命令",
|
||||
"panel.globalSettings.action.server.placeholder": "例如:{0}",
|
||||
|
||||
"panel.metadata.title": "元数据",
|
||||
"panel.metadata.focusProblems": "查看问题视图获取更多信息",
|
||||
|
||||
"panel.otherActions.title": "其他操作",
|
||||
"panel.otherActions.writingSettings.enabled": "写作设置已启用",
|
||||
"panel.otherActions.writingSettings.disabled": "启用写作设置",
|
||||
"panel.otherActions.centerMode": "切换居中模式",
|
||||
"panel.otherActions.createTemplate": "创建模板",
|
||||
"panel.otherActions.revealFile": "在文件夹中显示文件",
|
||||
"panel.otherActions.openProject": "显示项目文件夹",
|
||||
"panel.otherActions.documentation": "打开文档",
|
||||
"panel.otherActions.settings": "设置概览",
|
||||
"panel.otherActions.issue": "报告问题",
|
||||
|
||||
"panel.preview.title": "打开预览",
|
||||
|
||||
"panel.publishAction.publish": "发布",
|
||||
"panel.publishAction.unpublish": "恢复为草稿",
|
||||
|
||||
"panel.seoDetails.recommended": "推荐",
|
||||
|
||||
"panel.seoKeywords.checks": "检查项",
|
||||
"panel.seoKeywords.density.tableTitle": "频率",
|
||||
"panel.seoKeywords.density": "关键词密度",
|
||||
"panel.seoKeywordInfo.validInfo.label": "标题",
|
||||
"panel.seoKeywordInfo.validInfo.content": "内容",
|
||||
"panel.seoKeywordInfo.density.tooltip": "推荐频率:0.75% - 1.5%",
|
||||
|
||||
"panel.seoKeywords.title": "关键词",
|
||||
"panel.seoKeywords.header.keyword": "关键词",
|
||||
"panel.seoKeywords.header.details": "详情",
|
||||
"panel.seoKeywords.density.description": "* 在大多数情况下,1-1.5% 的关键词密度就足够了。",
|
||||
|
||||
"panel.seoStatus.title": "洞察",
|
||||
"panel.seoStatus.header.property": "属性",
|
||||
"panel.seoStatus.header.valid": "有效",
|
||||
"panel.seoStatus.seoFieldInfo.characters": "{0} 个字符",
|
||||
"panel.seoStatus.seoFieldInfo.words": "{0} 个单词",
|
||||
"panel.seoStatus.seoFieldInfo.article": "文章长度",
|
||||
"panel.seoStatus.collapsible.title": "SEO 状态",
|
||||
"panel.seoStatus.required": "{0} 或 {1} 是必需的。",
|
||||
|
||||
"panel.slugAction.title": "优化别名",
|
||||
|
||||
"panel.spinner.loading": "加载中...",
|
||||
|
||||
"panel.startServerbutton.start": "启动服务器",
|
||||
"panel.startServerbutton.stop": "停止服务器",
|
||||
|
||||
"panel.tag.add": "将 {0} 添加到您的设置中",
|
||||
|
||||
"panel.tagPicker.inputPlaceholder.empty": "选择您的 {0}",
|
||||
"panel.tagPicker.inputPlaceholder.disabled": "您已达到 {0} 的限制",
|
||||
"panel.tagPicker.ai.suggest": "使用 Front Matter AI 建议 {0}",
|
||||
"panel.tagPicker.copilot.suggest": "使用 GitHub Copilot 建议 {0}",
|
||||
"panel.tagPicker.ai.generating": "正在生成建议...",
|
||||
"panel.tagPicker.limit": "最大:{0}",
|
||||
"panel.tagPicker.unkown": "添加未知标签",
|
||||
|
||||
"panel.tags.tag.warning": "请注意,标签 \"{0}\" 未保存在您的设置中。一旦移除,它将永久消失。",
|
||||
|
||||
"panel.viewPanel.mediaInsert": "继续在媒体仪表盘中选择您想要插入的图片。",
|
||||
|
||||
"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": "缓存已清除",
|
||||
|
||||
"commands.chatbot.title": "问我任何问题",
|
||||
|
||||
"commands.content.option.contentType.label": "按内容类型创建内容",
|
||||
"commands.content.option.contentType.description": "选择是否要根据可用的内容类型创建新内容",
|
||||
"commands.content.option.template.label": "按模板创建内容",
|
||||
"commands.content.option.template.description": "选择是否要根据可用的模板创建新内容",
|
||||
"commands.content.quickPick.title": "创建内容",
|
||||
"commands.content.quickPick.placeholder": "选择您想要创建新内容的方式",
|
||||
|
||||
"commands.dashboard.title": "仪表盘",
|
||||
|
||||
"commands.folders.addMediaFolder.inputBox.title": "添加媒体文件夹",
|
||||
"commands.folders.addMediaFolder.inputBox.prompt": "您想给您的文件夹起什么名字(使用 \"/\" 创建多级文件夹)?",
|
||||
"commands.folders.addMediaFolder.noFolder.warning": "未指定文件夹名称。",
|
||||
"commands.folders.create.folderExists.warning": "文件夹已注册",
|
||||
"commands.folders.create.input.title": "注册文件夹",
|
||||
"commands.folders.create.input.prompt": "您想为此文件夹指定什么名称?",
|
||||
"commands.folders.create.input.placeholder": "文件夹名称",
|
||||
"commands.folders.create.success": "文件夹已注册",
|
||||
"commands.folders.getWorkspaceFolder.workspaceFolderPick.placeholder": "请选择 Front Matter 要使用的主工作区文件夹。",
|
||||
"commands.folders.get.notificationError.title": "文件夹 \"{0}\" 不存在。请从设置中移除它。",
|
||||
"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.createOrOpen.quickPick.title": "打开或创建翻译",
|
||||
"commands.i18n.createOrOpen.quickPick.category.existing": "现有翻译",
|
||||
"commands.i18n.createOrOpen.quickPick.action.open": "打开 \"{0}\"",
|
||||
"commands.i18n.createOrOpen.quickPick.category.new": "新翻译",
|
||||
"commands.i18n.createOrOpen.quickPick.action.create": "创建 \"{0}\"",
|
||||
"commands.i18n.translate.progress.title": "正在翻译内容...",
|
||||
|
||||
"commands.preview.panel.title": "预览:{0}",
|
||||
"commands.preview.askUserToPickFolder.title": "选择要预览的文章所在文件夹",
|
||||
|
||||
"commands.project.initialize.success": "项目初始化成功。",
|
||||
"commands.project.switchProject.title": "您想切换到哪个项目?",
|
||||
"commands.project.createSampleTemplate.info": "示例模板已创建。",
|
||||
|
||||
"commands.settings.create.input.prompt": "插入您要添加到配置中的 {0} 的值。",
|
||||
"commands.settings.create.input.placeholder": "{0} 的名称",
|
||||
"commands.settings.create.warning": "提供的 {0} 已存在。",
|
||||
"commands.settings.create.quickPick.placeholder": "您要将新的 {0} 添加到页面吗?",
|
||||
"commands.settings.export.progress.title": "{0}: 正在导出标签和分类",
|
||||
"commands.settings.export.progress.success": "导出完成。标签:{0} - 分类:{1}。",
|
||||
"commands.settings.remap.quickpick.title": "重新映射",
|
||||
"commands.settings.remap.quickpick.placeholder": "您想重新映射什么?",
|
||||
"commands.settings.remap.noTaxonomy.warning": "未配置 {0}。",
|
||||
"commands.settings.remap.selectTaxonomy.placeholder": "选择要插入的 {0}。",
|
||||
"commands.settings.remap.newOption.input.prompt": "指定您想用哪个 {0} 的值来重新映射 \"{1}\"。如果要从所有文章中移除该 {0},请将输入留空。",
|
||||
"commands.settings.remap.newOption.input.placeholder": "{0} 的名称",
|
||||
"commands.settings.remap.delete.placeholder": "删除 {0} {1}?",
|
||||
|
||||
"commands.statusListener.verifyRequiredFields.diagnostic.emptyField": "{0} 字段是必填的。请为该字段定义一个值。",
|
||||
"commands.statusListener.verifyRequiredFields.notification.error": "以下字段必须包含值:{0}",
|
||||
|
||||
"commands.template.generate.input.title": "模板标题",
|
||||
"commands.template.generate.input.prompt": "您想给模板起什么名字?",
|
||||
"commands.template.generate.input.placeholder": "文章",
|
||||
"commands.template.generate.noTitle.warning": "您未指定模板标题。",
|
||||
"commands.template.generate.keepContents.title": "保留内容",
|
||||
"commands.template.generate.keepContents.placeholder": "您想保留模板的内容吗?",
|
||||
"commands.template.generate.keepContents.noOption.warning": "您没有选择保留模板内容的任何选项。",
|
||||
"commands.template.generate.keepContents.success": "模板已创建,现在可在您的 {0} 文件夹中使用。",
|
||||
"commands.template.getTemplates.warning": "未找到模板。",
|
||||
"commands.template.create.folderPath.warning": "检索到的项目文件夹路径不正确。",
|
||||
"commands.template.create.noTemplates.warning": "未找到模板。",
|
||||
"commands.template.create.selectTemplate.title": "选择一个模板",
|
||||
"commands.template.create.selectTemplate.placeholder": "选择要使用的内容模板",
|
||||
"commands.template.create.selectTemplate.noTemplate.warning": "未选择模板。",
|
||||
"commands.template.create.selectTemplate.notFound.warning": "找不到内容模板。",
|
||||
"commands.template.create.success": "您的新内容现已可用。",
|
||||
|
||||
"commands.wysiwyg.command.unorderedList.label": "无序列表",
|
||||
"commands.wysiwyg.command.unorderedList.detail": "添加无序列表",
|
||||
"commands.wysiwyg.command.orderedList.label": "有序列表",
|
||||
"commands.wysiwyg.command.orderedList.detail": "添加有序列表",
|
||||
"commands.wysiwyg.command.taskList.label": "任务列表",
|
||||
"commands.wysiwyg.command.taskList.detail": "添加任务列表",
|
||||
"commands.wysiwyg.command.code.label": "代码",
|
||||
"commands.wysiwyg.command.code.detail": "添加内联代码片段",
|
||||
"commands.wysiwyg.command.codeblock.label": "代码块",
|
||||
"commands.wysiwyg.command.codeblock.detail": "添加代码块",
|
||||
"commands.wysiwyg.command.blockquote.label": "引用",
|
||||
"commands.wysiwyg.command.blockquote.detail": "添加引用块",
|
||||
"commands.wysiwyg.command.strikethrough.label": "删除线",
|
||||
"commands.wysiwyg.command.strikethrough.detail": "添加删除线文本",
|
||||
"commands.wysiwyg.quickPick.title": "WYSIWYG 选项",
|
||||
"commands.wysiwyg.quickPick.placeholder": "您想插入哪种类型的标记?",
|
||||
"commands.wysiwyg.addHyperlink.hyperlinkInput.title": "WYSIWYG 超链接",
|
||||
"commands.wysiwyg.addHyperlink.hyperlinkInput.prompt": "输入 URL",
|
||||
"commands.wysiwyg.addHyperlink.textInput.title": "WYSIWYG 文本",
|
||||
"commands.wysiwyg.addHyperlink.textInput.prompt": "输入超链接的文本",
|
||||
"commands.wysiwyg.insertText.heading.input.title": "标题级别",
|
||||
"commands.wysiwyg.insertText.heading.input.placeholder": "您想插入哪个标题级别?",
|
||||
|
||||
"helpers.articleHelper.createContent.pageBundle.error": "名为 {0} 的页面捆绑包已存在于 {1} 中。",
|
||||
"helpers.articleHelper.createContent.contentExists.warning": "标题的内容已存在。请指定一个新标题。",
|
||||
"helpers.articleHelper.processCustomPlaceholders.placeholder.error": "处理 {0} 占位符时出错。",
|
||||
"helpers.articleHelper.parseFile.diagnostic.error": "解析 {0} 的 front matter 时出错。",
|
||||
|
||||
"helpers.contentType.generate.noFrontMatter.error": "未找到 front matter 数据来生成内容类型。",
|
||||
"helpers.contentType.generate.override.quickPick.title": "覆盖默认内容类型",
|
||||
"helpers.contentType.generate.override.quickPick.placeholder": "您是否想用当前字段中使用的字段覆盖默认内容类型配置?",
|
||||
"helpers.contentType.generate.contentTypeInput.title": "生成内容类型",
|
||||
"helpers.contentType.generate.contentTypeInput.prompt": "输入要生成的内容类型的名称",
|
||||
"helpers.contentType.generate.contentTypeInput.validation.enterName": "请输入内容类型的名称。",
|
||||
"helpers.contentType.generate.contentTypeInput.validation.nameExists": "已存在具有此名称的内容类型。",
|
||||
"helpers.contentType.generate.noContentTypeName.warning": "您未指定内容类型的名称。",
|
||||
"helpers.contentType.generate.pageBundle.quickPick.title": "用作页面捆绑包",
|
||||
"helpers.contentType.generate.pageBundle.quickPick.placeHolder": "您想将此内容类型用作页面捆绑包吗?",
|
||||
"helpers.contentType.generate.updated.success": "内容类型 {0} 已更新。",
|
||||
"helpers.contentType.generate.generated.success": "内容类型 {0} 已生成。",
|
||||
"helpers.contentType.addMissingFields.noFrontMatter.warning": "未找到 front matter 数据来添加缺失字段。",
|
||||
"helpers.contentType.addMissingFields.updated.success": "内容类型 {0} 已更新。",
|
||||
"helpers.contentType.setContentType.noFrontMatter.warning": "未找到 front matter 数据来设置内容类型。",
|
||||
"helpers.contentType.setContentType.quickPick.title": "选择内容类型",
|
||||
"helpers.contentType.setContentType.quickPick.placeholder": "您想使用哪个内容类型?",
|
||||
"helpers.contentType.create.allowSubContent.title": "您想将其创建为子内容吗?",
|
||||
"helpers.contentType.create.allowSubContent.placeHolder": "您想将其创建为子内容吗?",
|
||||
"helpers.contentType.create.allowSubContent.showOpenDialog.openLabel": "选择文件夹",
|
||||
"helpers.contentType.create.allowSubContent.showOpenDialog.title": "选择创建内容的文件夹",
|
||||
"helpers.contentType.create.pageBundle.title": "创建为页面捆绑包?",
|
||||
"helpers.contentType.create.pageBundle.placeHolder": "您想将子内容创建为页面捆绑包吗?",
|
||||
"helpers.contentType.create.progress.title": "{0}: 正在创建内容...",
|
||||
"helpers.contentType.create.success": "您的新内容已创建。",
|
||||
"helpers.contentType.verify.warning": "内容类型操作在此模式下不可用。",
|
||||
|
||||
"helpers.customScript.executing": "正在执行:{0}",
|
||||
"helpers.customScript.singleRun.article.warning": "{0}: 无法检索到文章。",
|
||||
"helpers.customScript.bulkRun.noFiles.warning": "{0}: 未找到文件",
|
||||
"helpers.customScript.runMediaScript.noFolder.warning": "{0}: 未指定文件夹或媒体路径。",
|
||||
"helpers.customScript.showOutput.frontMatter.success": "{0}: front matter 已更新。",
|
||||
"helpers.customScript.showOutput.copyOutput.action": "复制输出",
|
||||
"helpers.customScript.showOutput.success": "{0}: 已执行您的自定义脚本。",
|
||||
"helpers.customScript.validateCommand.error": "无效命令:{0}",
|
||||
|
||||
"helpers.dataFileHelper.process.error": "处理数据文件时出错。",
|
||||
|
||||
"helpers.extension.getVersion.changelog": "查看更新日志",
|
||||
"helpers.extension.getVersion.starIt": "给它一个 ⭐️",
|
||||
"helpers.extension.getVersion.update.notification": "{0} 已更新至 v{1} — 查看新功能!",
|
||||
"helpers.extension.migrateSettings.templates.quickPick.title": "{0} - 模板",
|
||||
"helpers.extension.migrateSettings.templates.quickPick.placeholder": "您想继续使用模板功能吗?",
|
||||
"helpers.extension.checkIfExtensionCanRun.warning": "Front Matter BETA 无法在安装了稳定版本的情况下使用。请确保您只安装了一个版本。",
|
||||
|
||||
"helpers.mediaHelper.saveFile.folder.error": "我们找不到您选择的文件夹。",
|
||||
"helpers.mediaHelper.saveFile.file.uploaded.success": "文件 {0} 已上传至:{1}",
|
||||
"helpers.mediaHelper.saveFile.file.uploaded.failed": "抱歉,上传 {0} 时出错",
|
||||
"helpers.mediaHelper.deleteFile.file.deletion.failed": "抱歉,删除 {0} 时出错",
|
||||
|
||||
"helpers.mediaLibrary.remove.warning": "名称 \"{0}\" 在文件位置已存在。",
|
||||
"helpers.mediaLibrary.remove.error": "抱歉,将 \"{0}\" 更新为 \"{1}\" 时出错。",
|
||||
|
||||
"helpers.openFileInEditor.error": "无法打开文件。",
|
||||
|
||||
"helpers.questions.contentTitle.aiInput.title": "标题或描述",
|
||||
"helpers.questions.contentTitle.aiInput.prompt": "您想写什么?",
|
||||
"helpers.questions.contentTitle.aiInput.placeholder": "您想写什么?",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.title.separator": "您的标题/描述",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.ai.separator": "AI 生成的标题",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.copilot.separator": "GitHub Copilot 建议",
|
||||
"helpers.questions.contentTitle.aiInput.select.title": "选择一个标题",
|
||||
"helpers.questions.contentTitle.aiInput.select.placeholder": "为您的内容选择一个标题",
|
||||
"helpers.questions.contentTitle.aiInput.failed": "获取 AI 标题失败。请尝试使用您自己的标题或稍后再试。",
|
||||
"helpers.questions.contentTitle.copilotInput.failed": "获取 GitHub Copilot 标题建议失败。请尝试使用您自己的标题或稍后再试。",
|
||||
"helpers.questions.contentTitle.aiInput.warning": "您未指定内容的标题。",
|
||||
"helpers.questions.contentTitle.titleInput.title": "内容标题",
|
||||
"helpers.questions.contentTitle.titleInput.prompt": "您想为要创建的内容使用什么标题?",
|
||||
"helpers.questions.contentTitle.titleInput.placeholder": "内容标题",
|
||||
"helpers.questions.contentTitle.titleInput.warning": "您未指定内容的标题。",
|
||||
"helpers.questions.selectContentFolder.quickPick.title": "选择一个文件夹",
|
||||
"helpers.questions.selectContentFolder.quickPick.placeholder": "选择您要创建内容的位置",
|
||||
"helpers.questions.selectContentFolder.quickPick.noSelection.warning": "您没有选择要创建内容的位置。",
|
||||
"helpers.questions.selectContentType.noContentType.warning": "未找到内容类型。请先创建内容类型。",
|
||||
"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} 个字符以内。",
|
||||
|
||||
"helpers.settingsHelper.checkToPromote.message": "您有本地设置。是否要将它们提升为全局设置(\"frontmatter.json\")?",
|
||||
"helpers.settingsHelper.promote.success": "所有设置已提升到团队级别。",
|
||||
"helpers.settingsHelper.readConfig.progress.title": "{0}: 正在读取动态配置文件...",
|
||||
"helpers.settingsHelper.readConfig.error": "读取配置时出错。",
|
||||
"helpers.settingsHelper.refreshConfig.success": "设置已刷新。",
|
||||
"helpers.settingsHelper.safeUpdate.warning": "无法更新设置 \"{0}\",因为您已扩展或拆分 Front Matter CMS 配置。请手动添加您的更改。请查看输出以获取设置更新。",
|
||||
|
||||
"helpers.taxonomyHelper.rename.input.title": "重命名 {0}",
|
||||
"helpers.taxonomyHelper.rename.validate.equalValue": "新值必须与旧值不同。",
|
||||
"helpers.taxonomyHelper.rename.validate.noValue": "必须提供新值。",
|
||||
"helpers.taxonomyHelper.merge.quickPick.title": "将 \"{0}\" 与另一个 {1} 值合并",
|
||||
"helpers.taxonomyHelper.merge.quickPick.placeholder": "选择要合并的 {0} 值",
|
||||
"helpers.taxonomyHelper.delete.quickPick.title": "删除 \"{0}\" {1} 值",
|
||||
"helpers.taxonomyHelper.delete.quickPick.placeholder": "您确定要删除 \"{0}\" {1} 值吗?",
|
||||
"helpers.taxonomyHelper.createNew.input.title": "创建新的 {0} 值",
|
||||
"helpers.taxonomyHelper.createNew.input.placeholder": "输入您要添加的值",
|
||||
"helpers.taxonomyHelper.createNew.input.validate.noValue": "必须提供一个值。",
|
||||
"helpers.taxonomyHelper.createNew.input.validate.exists": "该值已存在。",
|
||||
"helpers.taxonomyHelper.process.insert": "{0}: 正在将 \"{1}\" 插入您选择的页面。",
|
||||
"helpers.taxonomyHelper.process.edit": "{0}: 正在将 \"{1}\" 从 {2} 重命名为 {3}。",
|
||||
"helpers.taxonomyHelper.process.merge": "{0}: 正在将 \"{1}\" 从 {2} 合并到 {3}。",
|
||||
"helpers.taxonomyHelper.process.delete": "{0}: 正在从 {2} 中删除 \"{1}\"。",
|
||||
"helpers.taxonomyHelper.process.insert.success": "插入完成。",
|
||||
"helpers.taxonomyHelper.process.edit.success": "编辑完成。",
|
||||
"helpers.taxonomyHelper.process.merge.success": "合并完成。",
|
||||
"helpers.taxonomyHelper.process.delete.success": "删除完成。",
|
||||
"helpers.taxonomyHelper.move.quickPick.title": "将 \"{0}\" 移动到其他类型",
|
||||
"helpers.taxonomyHelper.move.quickPick.placeholder": "选择要移动到的类型",
|
||||
"helpers.taxonomyHelper.move.progress.title": "{0}: 正在将 \"{1}\" 从 {2} 移动到 \"{3}\"。",
|
||||
"helpers.taxonomyHelper.move.success": "移动完成。",
|
||||
|
||||
"listeners.dashboard.dashboardListener.openConfig.notification": "如果您想查看配置,请打开 \"frontmatter.json\" 文件。",
|
||||
"listeners.dashboard.dashboardListener.pinItem.noPath.error": "未提供路径。",
|
||||
"listeners.dashboard.dashboardListener.pinItem.coundNotPin.error": "无法固定项目。",
|
||||
"listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error": "无法取消固定项目。",
|
||||
|
||||
"listeners.dashboard.mediaListeners.deleteMediaFolder.progress.title": "正在删除文件夹...",
|
||||
"listeners.dashboard.mediaListeners.updateMediaFolder.progress.title": "正在更新文件夹...",
|
||||
|
||||
"listeners.dashboard.settingsListener.triggerTemplate.notification": "模板文件已复制。",
|
||||
"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": "片段缺少标题或正文",
|
||||
"listeners.dashboard.snippetListener.addSnippet.exists.warning": "已存在具有相同标题的片段",
|
||||
"listeners.dashboard.snippetListener.updateSnippet.noSnippets.warning": "没有要更新的片段",
|
||||
|
||||
"listeners.general.gitListener.push.error": "推送子模块失败。",
|
||||
|
||||
"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 时出错。请检查文件内容。",
|
||||
"listeners.panel.dataListener.createDataFile.inputTitle": "数据文件的名称是什么?",
|
||||
"listeners.panel.dataListener.createDataFile.error": "未定义数据文件 ID 或路径。",
|
||||
"listeners.panel.dataListener.createDataFile.noFileName": "未提供文件名。",
|
||||
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "无活动编辑器",
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error": "无文章数据",
|
||||
|
||||
"services.copilot.getChatResponse.error": "未能从 GitHub Copilot 获得响应。",
|
||||
|
||||
"services.modeSwitch.switchMode.quickPick.placeholder": "选择您要使用的模式",
|
||||
"services.modeSwitch.switchMode.quickPick.title": "{0}: 模式选择",
|
||||
"services.modeSwitch.setText.mode": "模式:{0}",
|
||||
|
||||
"services.pagesParser.parsePages.statusBar.text": "正在处理...",
|
||||
"services.pagesParser.parsePages.file.error": "文件错误:{0}",
|
||||
|
||||
"services.sponsorAi.getTitles.warning": "AI 标题生成耗时过长。请稍后再试。",
|
||||
"services.sponsorAi.getDescription.warning": "AI 描述生成耗时过长。请稍后再试。",
|
||||
"services.sponsorAi.getTaxonomySuggestions.warning": "AI 分类法生成耗时过长。请稍后再试。",
|
||||
|
||||
"services.terminal.openLocalServerTerminal.terminalOption.message": "正在启动本地服务器"
|
||||
}
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.9.0",
|
||||
"version": "10.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-front-matter-beta",
|
||||
"version": "10.9.0",
|
||||
"version": "10.8.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
@@ -18041,7 +18041,6 @@
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz",
|
||||
"integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@discoveryjs/json-ext": "0.5.7",
|
||||
"acorn": "^8.0.4",
|
||||
|
||||
38
package.json
38
package.json
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter CMS",
|
||||
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "10.9.0",
|
||||
"version": "10.8.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -139,6 +139,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"frontMatter.sponsors.ai.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "%setting.frontMatter.sponsors.ai.enabled.markdownDescription%",
|
||||
"scope": "Sponsors"
|
||||
},
|
||||
"frontMatter.extensibility.scripts": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%setting.frontMatter.extensibility.scripts.markdownDescription%",
|
||||
@@ -2104,12 +2110,6 @@
|
||||
"markdownDescription": "%setting.frontMatter.templates.prefix.markdownDescription%",
|
||||
"scope": "Templates"
|
||||
},
|
||||
"frontMatter.validation.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "%setting.frontMatter.validation.enabled.markdownDescription%",
|
||||
"scope": "Validation"
|
||||
},
|
||||
"frontMatter.website.host": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%setting.frontMatter.website.host.markdownDescription%"
|
||||
@@ -2391,6 +2391,15 @@
|
||||
"category": "Front Matter",
|
||||
"icon": "$(book)"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.chatbot",
|
||||
"title": "%command.frontMatter.chatbot%",
|
||||
"category": "Front Matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/chatbot-light.svg",
|
||||
"dark": "assets/icons/chatbot-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.promoteSettings",
|
||||
"title": "%command.frontMatter.promoteSettings%",
|
||||
@@ -2557,6 +2566,11 @@
|
||||
"command": "frontMatter.dashboard.close",
|
||||
"group": "navigation@-98",
|
||||
"when": "frontMatter:enabled == true && frontMatter:dashboard:open == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@-97",
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
@@ -2766,6 +2780,11 @@
|
||||
"group": "navigation@-1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.mode.switch",
|
||||
"group": "navigation@1",
|
||||
@@ -2795,7 +2814,10 @@
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "frontmatter.project.output"
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"setting.frontMatter.projects.markdownDescription": "Geben Sie die Liste der Projekte an, die in Front Matter CMS geladen werden sollen. [Dokumentation prüfen](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Geben Sie den Namen des Projekts an.",
|
||||
"setting.frontMatter.projects.items.properties.default.markdownDescription": "Geben Sie an, ob dieses Projekt das Standardprojekt zum Laden ist.",
|
||||
"setting.frontMatter.sponsors.ai.enabled.markdownDescription": "Geben Sie an, ob Sie KI-Vorschläge aktivieren möchten. [Dokumentation prüfen](https://frontmatter.codes/docs/settings/overview#frontmatter.sponsors.ai.enabled)",
|
||||
"setting.frontMatter.extensibility.scripts.markdownDescription": "Geben Sie die Liste der Skripte an, die in Front Matter CMS geladen werden sollen. [Dokumentation prüfen](https://frontmatter.codes/docs/settings/overview#frontmatter.extensibility.scripts)",
|
||||
"setting.frontMatter.experimental.markdownDescription": "Geben Sie an, ob Sie experimentelle Funktionen aktivieren möchten. [Dokumentation prüfen](https://frontmatter.codes/docs/settings/overview#frontmatter.experimental)",
|
||||
"setting.frontMatter.extends.markdownDescription": "Geben Sie die Liste der Pfade/URLs an, um die Front Matter CMS-Konfiguration zu erweitern. [Dokumentation prüfen](https://frontmatter.codes/docs/settings/overview#frontmatter.extends)",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"setting.frontMatter.projects.markdownDescription": "Front Matter CMSを利用するプロジェクトを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.projects)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "プロジェクトの名前を指定します。",
|
||||
"setting.frontMatter.projects.items.properties.default.markdownDescription": "このプロジェクトを読み込む既定のプロジェクトにするかどうかを指定します。",
|
||||
"setting.frontMatter.sponsors.ai.enabled.markdownDescription": "AIによる提案を利用します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.sponsors.ai.enabled)",
|
||||
"setting.frontMatter.extensibility.scripts.markdownDescription": "Front Matter CMSで読み込むスクリプトのリストを指定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.extensibility.scripts)",
|
||||
"setting.frontMatter.experimental.markdownDescription": "実験的な機能をオンにします。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.experimental)",
|
||||
"setting.frontMatter.extends.markdownDescription": "Front Matter CMSの構成を拡張するパス/URLのリストを設定します。[ドキュメントを確認](https://frontmatter.codes/docs/settings/overview#frontmatter.extends)",
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"setting.frontMatter.projects.markdownDescription": "Specify the list of projects to load in the Front Matter CMS. [Local](https://file%2B.vscode-resource.vscode-cdn.net/Users/eliostruyf/nodejs/frontmatter-test-projects/astro-blog/test.html) - [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.projects) - [View in VS Code](vscode://simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.projects%22%5D)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Specify the name of the project.",
|
||||
"setting.frontMatter.projects.items.properties.default.markdownDescription": "Specify if this project is the default project to load.",
|
||||
"setting.frontMatter.sponsors.ai.enabled.markdownDescription": "Specify if you want to enable AI suggestions. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.sponsors.ai.enabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.sponsors.ai.enabled%22%5D)",
|
||||
"setting.frontMatter.extensibility.scripts.markdownDescription": "Specify the list of scripts to load in the Front Matter CMS. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.extensibility.scripts) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.extensibility.scripts%22%5D)",
|
||||
"setting.frontMatter.experimental.markdownDescription": "Specify if you want to enable the experimental features. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.experimental) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.experimental%22%5D)",
|
||||
"setting.frontMatter.extends.markdownDescription": "Specify the list of paths/URLs to extend the Front Matter CMS config. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.extends) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.extends%22%5D)",
|
||||
@@ -276,7 +277,6 @@
|
||||
"setting.frontMatter.taxonomy.tags.markdownDescription": "Specifies the tags which can be used in the Front Matter. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.tags%22%5D)",
|
||||
"setting.frontMatter.telemetry.disable.markdownDescription": "Specify if you want to disable the telemetry. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.telemetry.disable%22%5D)",
|
||||
"setting.frontMatter.templates.enabled.markdownDescription": "Specify if you want to use templates. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.enabled%22%5D)",
|
||||
"setting.frontMatter.validation.enabled.markdownDescription": "Specify if you want to enable front matter validation. When enabled, the extension will validate your front matter against the content type schema. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.validation.enabled) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.validation.enabled%22%5D)",
|
||||
"setting.frontMatter.templates.folder.markdownDescription": "Specify the folder to use for your article templates. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.folder) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.folder%22%5D)",
|
||||
"setting.frontMatter.templates.prefix.markdownDescription": "Specify the prefix you want to add for your new article filenames. [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.prefix) - [View in VS Code](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.prefix%22%5D)",
|
||||
"setting.frontMatter.dashboard.mediaSnippet.deprecationMessage": "This setting is deprecated and will be removed in the next major version. Please define your media snippet in the `frontMatter.content.snippet` setting.",
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
{
|
||||
"command.frontMatter.project.switch": "切换项目",
|
||||
"command.frontMatter.config.reload": "重新加载配置",
|
||||
"command.frontMatter.authenticate": "身份验证",
|
||||
"command.frontMatter.contenttype.generate": "从当前文件生成内容类型",
|
||||
"command.frontMatter.contenttype.addMissingFields": "将缺失字段从前端元数据添加到内容类型",
|
||||
"command.frontMatter.contenttype.setContentType": "设置当前文件使用的内容类型",
|
||||
"command.frontMatter.markup.blockquote": "引用",
|
||||
"command.frontMatter.markup.bold": "加粗",
|
||||
"command.frontMatter.dashboard.close": "关闭仪表盘",
|
||||
"command.frontMatter.markup.code": "代码",
|
||||
"command.frontMatter.markup.codeblock": "代码块",
|
||||
"command.frontMatter.markup.hyperlink": "超链接",
|
||||
"command.frontMatter.collapseSections": "折叠部分",
|
||||
"command.frontMatter.initTemplate": "初始化模板文件夹",
|
||||
"command.frontMatter.createTemplate": "从当前文件创建模板",
|
||||
"command.frontMatter.createCategory": "创建分类",
|
||||
"command.frontMatter.createContent": "创建新内容",
|
||||
"command.frontMatter.createTag": "创建标签",
|
||||
"command.frontMatter.diagnostics": "诊断日志",
|
||||
"command.frontMatter.exportTaxonomy": "将所有标签和分类导出到设置",
|
||||
"command.frontMatter.createFromTemplate": "从模板创建新文章",
|
||||
"command.frontMatter.registerFolder": "注册文件夹",
|
||||
"command.frontMatter.unregisterFolder": "取消注册文件夹",
|
||||
"command.frontMatter.generateSlug": "基于内容标题生成别名",
|
||||
"command.frontMatter.markup.heading": "标题",
|
||||
"command.frontMatter.init": "初始化项目",
|
||||
"command.frontMatter.insertCategories": "插入分类",
|
||||
"command.frontMatter.insertMedia": "将媒体插入内容",
|
||||
"command.frontMatter.insertSnippet": "将片段插入内容",
|
||||
"command.frontMatter.insertTags": "插入标签",
|
||||
"command.frontMatter.markup.italic": "斜体",
|
||||
"command.frontMatter.dashboard": "打开仪表盘",
|
||||
"command.frontMatter.dashboard.data": "打开数据仪表盘",
|
||||
"command.frontMatter.dashboard.media": "打开媒体仪表盘",
|
||||
"command.frontMatter.dashboard.snippets": "打开片段仪表盘",
|
||||
"command.frontMatter.dashboard.taxonomy": "打开分类法仪表盘",
|
||||
"command.frontMatter.markup.orderedlist": "有序列表",
|
||||
"command.frontMatter.markup.options": "其他标记选项",
|
||||
"command.frontMatter.preview": "预览内容",
|
||||
"command.frontMatter.docs": "文档",
|
||||
"command.frontMatter.chatbot": "向 Front Matter AI 寻求帮助",
|
||||
"command.frontMatter.promoteSettings": "将设置从本地提升到团队级别",
|
||||
"command.frontMatter.remap": "在所有文章中重新映射或移除标签/分类",
|
||||
"command.frontMatter.setLastModifiedDate": "设置最后修改日期",
|
||||
"command.frontMatter.markup.strikethrough": "删除线",
|
||||
"command.frontMatter.mode.switch": "切换模式",
|
||||
"command.frontMatter.markup.tasklist": "任务列表",
|
||||
"command.frontMatter.markup.unorderedlist": "无序列表",
|
||||
"command.frontMatter.git.sync": "同步",
|
||||
"command.frontMatter.cache.clear": "清除缓存",
|
||||
"command.frontMatter.i18n.create": "创建新翻译",
|
||||
"command.frontMatter.i18n.createOrOpen": "创建或打开翻译",
|
||||
"settings.configuration.title": "Front Matter: 使用 frontmatter.json 进行团队共享设置",
|
||||
"setting.frontMatter.projects.markdownDescription": "指定要在 Front Matter CMS 中加载的项目列表。[本地](https://file%2B.vscode-resource.vscode-cdn.net/Users/eliostruyf/nodejs/frontmatter-test-projects/astro-blog/test.html) - [文档](https://frontmatter.codes/docs/settings/overview#frontmatter.projects) - [在 VS Code 中查看](vscode://simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.projects%22%5D)",
|
||||
"setting.frontMatter.projects.items.properties.name.markdownDescription": "指定项目名称。",
|
||||
"setting.frontMatter.projects.items.properties.default.markdownDescription": "指定此项目是否为默认加载项目。",
|
||||
"setting.frontMatter.extensibility.scripts.markdownDescription": "指定要在 Front Matter CMS 中加载的脚本列表。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.extensibility.scripts) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.extensibility.scripts%22%5D)",
|
||||
"setting.frontMatter.experimental.markdownDescription": "指定是否启用实验性功能。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.experimental) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.experimental%22%5D)",
|
||||
"setting.frontMatter.extends.markdownDescription": "指定扩展 Front Matter CMS 配置的路径/URL 列表。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.extends) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.extends%22%5D)",
|
||||
"setting.frontMatter.content.autoUpdateDate.markdownDescription": "指定是否自动更新文章/页面的修改日期(仅限内容文件夹中的内容)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.autoupdatedate) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.autoupdatedate%22%5D)",
|
||||
"setting.frontMatter.content.defaultFileType.markdownDescription": "指定要创建内容的默认文件类型。[文档](https://frontmatter.codes/docs/settings/overview%23frontmatter.content.defaultfiletype) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.defaultfiletype%22%5D)",
|
||||
"setting.frontMatter.content.defaultSorting.markdownDescription": "指定内容仪表盘的默认排序选项。您可以使用枚举中的值或定义自己的 ID。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.defaultsorting) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.defaultsorting%22%5D)",
|
||||
"setting.frontMatter.content.draftField.markdownDescription": "定义要用于管理内容的草稿字段。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.draftfield) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.draftfield%22%5D)",
|
||||
"setting.frontMatter.content.draftField.properties.type.description": "要使用的草稿字段类型",
|
||||
"setting.frontMatter.content.draftField.properties.name.description": "要使用的字段名称",
|
||||
"setting.frontMatter.content.draftField.properties.invert.description": "默认情况下,当内容为草稿时,draft 字段值为 true。如需将draft字段设为 false,请将此值设置为 true。",
|
||||
"setting.frontMatter.content.draftField.properties.choices.description": "字段的选择列表",
|
||||
"setting.frontMatter.content.fmHighlight.markdownDescription": "指定是否在 Markdown 文件中高亮显示 Front Matter。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.fmhighlight) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.fmhighlight%22%5D)",
|
||||
"setting.frontMatter.content.hideFm.markdownDescription": "指定是否在 Markdown 文件中隐藏 Front Matter。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.hidefm) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.hidefm%22%5D)",
|
||||
"setting.frontMatter.content.hideFmMessage.markdownDescription": "指定隐藏 Front Matter 时显示的消息。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.hidefmMessage) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.hidefmMessage%22%5D)",
|
||||
"setting.frontMatter.content.pageFolders.markdownDescription": "此文件夹数组定义扩展程序可以检索或创建新页面的位置。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.pagefolders) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.pagefolders%22%5D)",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.title.description": "文件夹名称",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.path.description": "文件夹路径",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description": "排除子目录",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.excludePaths.description": "排除路径(例如 api, _*.*)",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.previewPath.description": "为文件夹定义自定义预览路径。",
|
||||
"setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description": "指定是否在预览 URL 中添加尾部斜杠。",
|
||||
"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.pageFolders.items.properties.slugTemplate.description": "定义自定义别名模板。",
|
||||
"setting.frontMatter.content.i18n.markdownDescription": "指定要用于网站的区域设置。此设置可在页面文件夹级别覆盖。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.i18n) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.i18n%22%5D)",
|
||||
"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": "此占位符数组定义了可在内容类型和模板中使用的占位符,用于自动填充内容的前端元数据。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.placeholders) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.placeholders%22%5D)",
|
||||
"setting.frontMatter.content.placeholders.items.properties.id.description": "占位符 ID,在内容类型或模板中按如下方式使用:{{placeholder}}",
|
||||
"setting.frontMatter.content.placeholders.items.properties.value.description": "占位符的值",
|
||||
"setting.frontMatter.content.placeholders.items.properties.script.description": "用于获取占位符值的要执行的脚本",
|
||||
"setting.frontMatter.content.publicFolder.markdownDescription": "指定所有资源所在的文件夹名称。例如在 Hugo 中这是 `static` 文件夹。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.publicfolder) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.publicfolder%22%5D)",
|
||||
"setting.frontMatter.content.publicFolder.properties.path.description": "指定资源文件夹的路径",
|
||||
"setting.frontMatter.content.publicFolder.properties.relative.description": "定义媒体文件的路径是否相对于内容文件?",
|
||||
"setting.frontMatter.snippets.wrapper.enabled.markdownDescription": "指定是否包装片段。[文档](https://frontmatter.codes/docs/settings/overview#frontMatter.snippets.wrapper.enabled) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.snippets.wrapper.enabled%22%5D)",
|
||||
"setting.frontMatter.content.snippets.markdownDescription": "定义要在内容中使用的片段。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.snippets) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.snippets%22%5D)",
|
||||
"setting.frontMatter.content.grouping.markdownDescription": "指定仪表盘内容的分组选项。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.grouping) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.grouping%22%5D)",
|
||||
"setting.frontMatter.content.grouping.items.properties.id.description": "分组选项的 ID。",
|
||||
"setting.frontMatter.content.grouping.items.properties.title.description": "将在 UI 中显示的分组标题。",
|
||||
"setting.frontMatter.content.grouping.items.properties.name.description": "要按之分组的 content-type 字段名称。",
|
||||
"setting.frontMatter.content.sorting.markdownDescription": "定义仪表盘内容的排序选项。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.sorting) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.sorting%22%5D)",
|
||||
"setting.frontMatter.content.sorting.items.properties.id.description": "排序选项的 ID。将用于存储上次使用的排序选项或默认选项。",
|
||||
"setting.frontMatter.content.sorting.items.properties.title.description": "排序标签的名称",
|
||||
"setting.frontMatter.content.sorting.items.properties.name.description": "要按之排序的元数据字段名称",
|
||||
"setting.frontMatter.content.sorting.items.properties.order.description": "排序顺序",
|
||||
"setting.frontMatter.content.sorting.items.properties.type.description": "字段值的类型",
|
||||
"setting.frontMatter.content.supportedFileTypes.markdownDescription": "指定要在 Front Matter 中使用的文件类型。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.supportedfiletypes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.supportedfiletypes%22%5D)",
|
||||
"setting.frontMatter.content.wysiwyg.markdownDescription": "指定是否启用所见即所得 (WYSIWYG) Markdown 控件。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.wysiwyg) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.wysiwyg%22%5D)",
|
||||
"setting.frontMatter.content.filters.markdownDescription": "指定内容仪表盘要使用的筛选器。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.content.filters) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.content.filters%22%5D)",
|
||||
"setting.frontMatter.custom.scripts.markdownDescription": "指定要执行的 Node.js 脚本的路径。当前文件路径将作为参数提供。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.custom.scripts) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.custom.scripts%22%5D)",
|
||||
"setting.frontMatter.custom.scripts.items.properties.id.description": "脚本的 ID。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.title.description": "要为脚本指定的标题。将显示为按钮的标题。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.script.description": "要执行的脚本路径",
|
||||
"setting.frontMatter.custom.scripts.items.properties.nodeBin.description": "node 可执行文件的路径。使用 NVM 时需要此选项,以避免混淆使用哪个 node 版本。(已弃用:改用 command 属性)",
|
||||
"setting.frontMatter.custom.scripts.items.properties.bulk.description": "对所有内容文件运行脚本",
|
||||
"setting.frontMatter.custom.scripts.items.properties.output.description": "定义脚本输出的位置。默认为通知,但可以指定在编辑器面板中显示。",
|
||||
"setting.frontMatter.custom.scripts.items.properties.outputType.description": "编辑器面板的输出类型。例如,可更改为 'markdown'",
|
||||
"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": "要执行的脚本路径",
|
||||
"setting.frontMatter.custom.scripts.items.properties.contentTypes.description": "定义脚本将使用的内容类型。如果未定义任何类型,则对所有类型可用。",
|
||||
"setting.frontMatter.dashboard.content.pagination.markdownDescription": "指定是否为内容启用分页。最多可定义页码为 52。默认每页项目数为 `16`。通过设置为 `false` 可禁用分页。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.dashboard.content.pagination) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.dashboard.content.pagination%22%5D)",
|
||||
"setting.frontMatter.dashboard.content.cardTags.markdownDescription": "指定将用于在内容卡片上显示标签的元数据字段名称。为空或 null 时,将从卡片中隐藏标签。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.dashboard.content.cardtags) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.dashboard.content.cardtags%22%5D)",
|
||||
"setting.frontMatter.dashboard.content.card.fields.state.markdownDescription": "指定是否在内容卡片视图上显示状态/草稿状态。[文档](https://frontmatter.codes/docs/settings/overview#frontMatter.dashboard.content.card.fields.state) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.dashboard.content.card.fields.state%22%5D)",
|
||||
"setting.frontMatter.dashboard.content.card.fields.date.markdownDescription": "指定是否在内容卡片视图上显示日期。[文档](https://frontmatter.codes/docs/settings/overview#frontMatter.dashboard.content.card.fields.date) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.dashboard.content.card.fields.date%22%5D)",
|
||||
"setting.frontMatter.dashboard.content.card.fields.description.markdownDescription": "指定将用于在内容卡片上显示描述的元数据字段名称。[文档](https://frontmatter.codes/docs/settings/overview#frontMatter.dashboard.content.card.fields.description) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.dashboard.content.card.fields.description%22%5D)",
|
||||
"setting.frontMatter.dashboard.content.card.fields.title.markdownDescription": "指定将用于在内容卡片上显示标题的元数据字段名称。[文档](https://frontmatter.codes/docs/settings/overview#frontMatter.dashboard.content.card.fields.title) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontMatter.dashboard.content.card.fields.title%22%5D)",
|
||||
"setting.frontMatter.dashboard.mediaSnippet.markdownDescription": "指定自定义媒体插入标记的片段。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.dashboard.mediasnippet) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.dashboard.mediasnippet%22%5D)",
|
||||
"setting.frontMatter.dashboard.mediaSnippet.items.description": "在片段中使用 `{mediaUrl}`、`{caption}`、`{alt}`、`{filename}`、`{mediaHeight}` 和 `{mediaWidth}` 占位符以自动插入媒体信息。",
|
||||
"setting.frontMatter.dashboard.openOnStart.markdownDescription": "指定启动 VS Code 时是否打开仪表盘。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.dashboard.openonstart) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.dashboard.openonstart%22%5D)",
|
||||
"setting.frontMatter.data.files.markdownDescription": "指定要用于网站的数据文件。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.data.files) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.data.files%22%5D)",
|
||||
"setting.frontMatter.data.files.items.properties.id.description": "要为数据文件使用的唯一 ID。",
|
||||
"setting.frontMatter.data.files.items.properties.title.description": "要为数据文件指定的标题。",
|
||||
"setting.frontMatter.data.files.items.properties.labelField.description": "要用作数据条目标签的字段。",
|
||||
"setting.frontMatter.data.files.items.properties.file.description": "要加载的文件路径。仅支持 JSON 或 YAML 文件。",
|
||||
"setting.frontMatter.data.files.items.properties.fileType.description": "定义如何解析文件。默认为 JSON。",
|
||||
"setting.frontMatter.data.files.items.properties.schema.description": "数据的 JSON 模式,将用于渲染数据表单。",
|
||||
"setting.frontMatter.data.files.items.properties.schema.properties.title.description": "表单标题。",
|
||||
"setting.frontMatter.data.files.items.properties.schema.properties.type.description": "定义表单的类型。默认为 'object'。",
|
||||
"setting.frontMatter.data.files.items.properties.schema.properties.required.description": "定义表单的必填字段。",
|
||||
"setting.frontMatter.data.files.items.properties.schema.properties.properties.description": "定义表单的字段。",
|
||||
"setting.frontMatter.data.files.items.properties.type.description": "如果使用数据类型,可指定类型 ID。",
|
||||
"setting.frontMatter.data.files.items.properties.singleEntry.description": "是否要对数据文件使用单个条目。",
|
||||
"setting.frontMatter.data.folders.markdownDescription": "指定要用于网站的数据文件夹。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.data.folders) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.data.folders%22%5D)",
|
||||
"setting.frontMatter.data.folders.items.properties.id.description": "要为数据文件夹使用的唯一 ID。",
|
||||
"setting.frontMatter.data.folders.items.properties.labelField.description": "要用作数据条目标签的字段。",
|
||||
"setting.frontMatter.data.folders.items.properties.path.description": "要加载文件的文件夹路径。",
|
||||
"setting.frontMatter.data.folders.items.properties.type.description": "如果使用数据类型,可指定类型 ID。",
|
||||
"setting.frontMatter.data.folders.items.properties.singleEntry.description": "是否要对文件夹中的数据文件使用单个条目。",
|
||||
"setting.frontMatter.data.folders.items.properties.enableFileCreation.description": "启用文件夹中创建新数据文件。",
|
||||
"setting.frontMatter.data.folders.items.properties.fileType.description": "启用文件创建时定义文件类型。默认为 JSON。",
|
||||
"setting.frontMatter.data.types.markdownDescription": "指定数据类型。这些类型可用于数据文件。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.data.types) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.data.types%22%5D)",
|
||||
"setting.frontMatter.data.types.items.properties.id.description": "要为数据类型使用的唯一 ID。",
|
||||
"setting.frontMatter.file.preserveCasing.markdownDescription": "指定是否保留文件名的标题大小写。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.file.preservecasing) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.file.preservecasing%22%5D)",
|
||||
"setting.frontMatter.framework.id.markdownDescription": "指定用于网站的静态站点生成器或框架的 ID。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.framework.id) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.framework.id%22%5D)",
|
||||
"setting.frontMatter.framework.startCommand.markdownDescription": "指定用于启动静态站点生成器或框架的命令。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.framework.startcommand) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.framework.startcommand%22%5D)",
|
||||
"setting.frontMatter.git.enabled.markdownDescription": "指定是否要为网站使用 Git 操作。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.enabled) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.enabled%22%5D)",
|
||||
"setting.frontMatter.git.commitMessage.markdownDescription": "指定同步要使用的提交消息。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.commitmessage) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.commitmessage%22%5D)",
|
||||
"setting.frontMatter.git.submodule.pull.markdownDescription": "指定同步时是否拉取子模块。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.submodule.pull) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.submodule.pull%22%5D)",
|
||||
"setting.frontMatter.git.submodule.push.markdownDescription": "指定同步时是否推送子模块。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.submodule.push) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.submodule.push%22%5D)",
|
||||
"setting.frontMatter.git.submodule.branch.markdownDescription": "指定要签出的子模块分支。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.submodule.branch) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.submodule.branch%22%5D)",
|
||||
"setting.frontMatter.git.submodule.folder.markdownDescription": "指定内容的子模块文件夹,当使用多个子模块时可能有用。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.submodule.folder) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.submodule.folder%22%5D)",
|
||||
"setting.frontMatter.global.activeMode.markdownDescription": "指定 Front Matter 的激活模式。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.global.activemode) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.activemode%22%5D)",
|
||||
"setting.frontMatter.global.modes.markdownDescription": "指定要用于 Front Matter 的模式。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.global.modes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.modes%22%5D)",
|
||||
"setting.frontMatter.global.modes.items.properties.id.description": "模式的 ID。",
|
||||
"setting.frontMatter.global.modes.items.properties.features.description": "要为模式使用的功能。",
|
||||
"setting.frontMatter.global.notifications.markdownDescription": "指定要查看的通知类型。默认情况下,将显示所有通知类型。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.global.notifications) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.notifications%22%5D)",
|
||||
"setting.frontMatter.global.disabledNotifications.markdownDescription": "这是一个包含可为 Front Matter CMS 禁用的通知类型的数组。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.global.disablednotifications) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.global.disablednotifications%22%5D)",
|
||||
"setting.frontMatter.global.timezone.markdownDescription": "指定日期格式化的时区。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.datetimezone) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.datetimezone%22%5D)",
|
||||
"setting.frontMatter.media.defaultSorting.markdownDescription": "指定媒体仪表盘的默认排序选项。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.media.defaultsorting) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.media.defaultsorting%22%5D)",
|
||||
"setting.frontMatter.media.supportedMimeTypes.markdownDescription": "指定媒体文件支持的 MIME 类型。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.media.supportedmimetypes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.media.supportedmimetypes%22%5D)",
|
||||
|
||||
"setting.frontMatter.media.contentTypes.markdownDescription": "指定要在 Front Matter 中使用的媒体内容类型。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.media.contenttypes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.media.contenttypes%22%5D)",
|
||||
"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.openOnSupportedFile.markdownDescription": "指定打开支持的文件时是否打开面板。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.openonsupportedfile) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.openonsupportedfile%22%5D)",
|
||||
"setting.frontMatter.panel.freeform.markdownDescription": "指定是否允许在标签选择器中输入未知标签/分类(启用后,之后将可以选择存储它们)。默认值:true。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.freeform) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.freeform%22%5D)",
|
||||
"setting.frontMatter.panel.actions.disabled.markdownDescription": "指定要在面板中禁用的操作。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.panel.actions.disabled) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.panel.actions.disabled%22%5D)",
|
||||
"setting.frontMatter.preview.host.markdownDescription": "指定打开预览时要使用的主机 URL(例如:http://localhost:1313)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.host) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.preview.host%22%5D)",
|
||||
"setting.frontMatter.preview.trailingSlash.markdownDescription": "指定是否在预览 URL 中添加尾部斜杠。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.trailingslash) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.preview.trailingslash%22%5D)",
|
||||
"setting.frontMatter.preview.pathName.markdownDescription": "指定在主机和别名之间要添加的路径。例如,可用于包含年份/月份,如:`yyyy/MM`。日期将根据文章的日期字段值生成。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.preview.pathname) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.preview.pathname%22%5D)",
|
||||
"setting.frontMatter.site.baseURL.markdownDescription": "指定网站的基本 URL,这将用于 SEO 检查。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.site.baseurl) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.site.baseurl%22%5D)",
|
||||
"setting.frontMatter.taxonomy.alignFilename.markdownDescription": "生成新别名时使文件名与新别名对齐。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.alignfilename) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.alignfilename%22%5D)",
|
||||
"setting.frontMatter.taxonomy.categories.markdownDescription": "指定可在 Front Matter 中使用的分类。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.categories) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.categories%22%5D)",
|
||||
"setting.frontMatter.taxonomy.commaSeparatedFields.markdownDescription": "指定 Front Matter 应视为逗号分隔数组的字段名称。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.commaseparatedfields) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.commaseparatedfields%22%5D)",
|
||||
"setting.frontMatter.taxonomy.commaSeparatedFields.items.description": "要用作逗号分隔数组的字段名称。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.markdownDescription": "指定要用于文章/页面等的内容类型。确保在 front matter 中正确设置了 `type`。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.contenttypes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.contenttypes%22%5D)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.description": "定义要在 Front Matter 中使用的内容类型。",
|
||||
"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.items.description": "定义要在 Front Matter 中使用的内容类型。",
|
||||
"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 中显示的描述",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.default.description": "默认值",
|
||||
"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": "是否为所见即所得字段。可设置为 markdown 或 HTML。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "是否允许多选?",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "指定图像字段是否可用作预览。注意,每个内容类型只能有一个预览图像。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "是否要从元数据部分隐藏该字段?",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description": "分类法字段的 ID。不能包含 \"tags\" 或 \"categories\" 值。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.fileExtensions.description": "指定文件选择器允许的文件扩展名",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.fieldGroup.description": "在 `frontMatter.taxonomy.fieldGroups` 设置中定义的字段组 ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dataType.description": "在 `frontMatter.data.types` 设置中定义的数据类型 ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.description": "指定数字字段的选项",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.isDecimal.description": "指定数字是否为小数",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.min.description": "最小值",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.max.description": "最大值",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.numberOptions.properties.step.description": "步进值",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyLimit.description": "限制可选择分类法的数量。设为 0 表示无限制。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.singleValueAsString.description": "指定是否将单个数组值存储为字符串而非数组。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPublishDate.description": "指定该字段是否为发布日期字段",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isModifiedDate.description": "指定该字段是否为修改日期字段",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dataFileId.description": "指定用于此字段的数据文件 ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dataFileKey.description": "指定用于此字段的数据文件键",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dataFileValue.description": "指定将用于显示字段值的属性名称",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.editable.description": "指定字段是否可编辑",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.encodeEmoji.description": "指定字段是否应编码表情符号",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.dateFormat.description": "指定要使用的日期格式",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.required.description": "指定字段是否为必填项",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeName.description": "指定用于筛选内容的内容类型名称(用于内容关联字段)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description": "指定要插入内容关联字段的值",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description": "指定是否仅显示相同区域设置的内容",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description": "指定显示字段的条件",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.fieldRef.description": "要使用的字段 ID",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.operator.description": "要使用的运算符",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.value.description": "要比较的值",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "指定比较是否区分大小写。默认值:true",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.actions.description": "指定字段自定义操作",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "指定创建新内容时是否创建文件夹。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "为内容类型定义自定义预览路径。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description": "指定是否在预览 URL 中添加尾部斜杠。",
|
||||
"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": "定义文件名的前缀。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.defaultFileName.description": "创建新内容时使用的默认文件名。",
|
||||
"setting.frontMatter.taxonomy.customTaxonomy.markdownDescription": "指定自定义分类法字段数据。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.tags%22%5D)",
|
||||
"setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description": "分类法字段的 ID。不能包含 \"tags\" 或 \"categories\" 值。",
|
||||
"setting.frontMatter.taxonomy.customTaxonomy.items.properties.options.description": "可供选择的选项。",
|
||||
"setting.frontMatter.taxonomy.dateField.markdownDescription": "此设置用于定义文章的发布日期字段。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.datefield) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.datefield%22%5D)",
|
||||
"setting.frontMatter.taxonomy.dateFormat.markdownDescription": "指定文章的日期格式。查看 [date-fns 格式](https://date-fns.org/v2.0.1/docs/format) 获取更多信息。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.dateformat) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.dateformat%22%5D)",
|
||||
"setting.frontMatter.taxonomy.fieldGroups.markdownDescription": "定义要用于区块字段或集合字段的字段组。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.fieldgroups) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.fieldgroups%22%5D)",
|
||||
"setting.frontMatter.taxonomy.fieldGroups.items.properties.id.description": "字段组的名称",
|
||||
"setting.frontMatter.taxonomy.fieldGroups.items.properties.labelField.description": "要用作显示值的字段名称",
|
||||
"setting.frontMatter.taxonomy.frontMatterType.markdownDescription": "指定要使用的 Front Matter 类型。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.frontmattertype) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.frontmattertype%22%5D)",
|
||||
"setting.frontMatter.taxonomy.indentArrays.markdownDescription": "指定前端元数据中的数组是否缩进。默认值:true。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.indentarrays) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.indentarrays%22%5D)",
|
||||
"setting.frontMatter.taxonomy.modifiedField.markdownDescription": "此设置用于定义文章的修改日期字段。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.modifiedfield) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.modifiedfield%22%5D)",
|
||||
"setting.frontMatter.taxonomy.quoteStringValues.markdownDescription": "指定是否引用前端元数据中的字符串值。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.quotestringvalues) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.quotestringvalues%22%5D)",
|
||||
"setting.frontMatter.taxonomy.noPropertyValueQuotes.markdownDescription": "指定需要移除引号的属性。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.nopropertyvaluequotes) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.nopropertyvaluequotes%22%5D)",
|
||||
"setting.frontMatter.taxonomy.noPropertyValueQuotes.items.description": "要移除引号的属性名称。",
|
||||
"setting.frontMatter.taxonomy.seoContentLengh.markdownDescription": "指定文章的最佳最小长度。1,760 到 2,400 词是 SEO(2021年) 的绝对理想文章长度(设为 `-1` 可关闭)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seocontentlengh) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seocontentlengh%22%5D)",
|
||||
"setting.frontMatter.taxonomy.seoDescriptionField.markdownDescription": "指定页面的 SEO 描述字段名称。默认为 'description'。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seodescriptionfield) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seodescriptionfield%22%5D)",
|
||||
"setting.frontMatter.taxonomy.seoDescriptionLength.markdownDescription": "指定 SEO 的最佳描述长度(设为 `-1` 可关闭)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seodescriptionlength) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seodescriptionlength%22%5D)",
|
||||
"setting.frontMatter.taxonomy.seoSlugLength.markdownDescription": "指定 SEO 的最佳别名长度(设为 `-1` 可关闭)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seosluglength) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seosluglength%22%5D)",
|
||||
"setting.frontMatter.taxonomy.seoTitleField.markdownDescription": "指定页面的 SEO 标题字段名称。默认为 'title'。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlefield) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seotitlefield%22%5D)",
|
||||
"setting.frontMatter.taxonomy.seoTitleLength.markdownDescription": "指定 SEO 的最佳标题长度(设为 `-1` 可关闭)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.seotitlelength) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.seotitlelength%22%5D)",
|
||||
"setting.frontMatter.taxonomy.slugPrefix.markdownDescription": "指定别名的前缀。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugprefix) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.slugprefix%22%5D)",
|
||||
"setting.frontMatter.taxonomy.slugSuffix.markdownDescription": "指定别名的后缀。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugsuffix) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.slugsuffix%22%5D)",
|
||||
"setting.frontMatter.taxonomy.slugTemplate.markdownDescription": "定义要创建内容的自定义别名模板。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.slugtemplate) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.slugtemplate%22%5D)",
|
||||
"setting.frontMatter.taxonomy.tags.markdownDescription": "指定可在 Front Matter 中使用的标签。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.taxonomy.tags) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.taxonomy.tags%22%5D)",
|
||||
"setting.frontMatter.telemetry.disable.markdownDescription": "指定是否禁用遥测。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.telemetry.disable) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.telemetry.disable%22%5D)",
|
||||
"setting.frontMatter.templates.enabled.markdownDescription": "指定是否使用模板。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.enabled) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.enabled%22%5D)",
|
||||
"setting.frontMatter.templates.folder.markdownDescription": "指定文章模板要使用的文件夹。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.folder) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.folder%22%5D)",
|
||||
"setting.frontMatter.templates.prefix.markdownDescription": "指定要为新文章文件名添加的前缀。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.templates.prefix) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.templates.prefix%22%5D)",
|
||||
"setting.frontMatter.dashboard.mediaSnippet.deprecationMessage": "此设置已弃用,将在下一个主要版本中移除。请在 `frontMatter.content.snippet` 设置中定义媒体片段。",
|
||||
"setting.frontMatter.taxonomy.dateField.deprecationMessage": "此设置已弃用,将在下一个主要版本中移除。请在内容类型的日期字段中使用新的 `isPublishDate` 设置。",
|
||||
"setting.frontMatter.taxonomy.modifiedField.deprecationMessage": "此设置已弃用,将在下一个主要版本中移除。请在内容类型的日期字段中使用新的 `isModifiedDate` 设置。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.customType.description": "指定要使用的自定义字段类型的名称。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.clearEmpty.description": "指定是否应清除空值。",
|
||||
"setting.frontMatter.website.host.markdownDescription": "指定网站的主机 URL。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.website.url) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.website.url%22%5D)",
|
||||
"command.frontMatter.settings.refresh": "刷新 Front Matter 设置",
|
||||
"setting.frontMatter.config.dynamicFilePath.markdownDescription": "指定动态配置文件的路径(例如:[[workspace]]/config.js)。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.config.dynamicfilepath) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.config.dynamicfilepath%22%5D)",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.allowAsSubContent.description": "指定内容类型是否可用作子内容。",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.isSubContent.description": "指定内容类型是否为子内容。",
|
||||
|
||||
"setting.frontMatter.git.disableOnBranches.markdownDescription": "指定要禁用 Git 操作的分支。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.disableonbranches) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.disableonbranches%22%5D)",
|
||||
"setting.frontMatter.git.requiresCommitMessage.markdownDescription": "指定在发布指定分支的更改时是否需要提交消息。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.git.requirescommitmessage) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.git.requirescommitmessage%22%5D)",
|
||||
"setting.frontMatter.copilot.family.markdownDescription": "指定要使用的 Copilot 的 LLM 系列。[文档](https://frontmatter.codes/docs/settings/overview#frontmatter.copilot.family) - [在 VS Code 中查看](command:simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.copilot.family%22%5D)"
|
||||
}
|
||||
126
src/commands/Chatbot.ts
Normal file
126
src/commands/Chatbot.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { PreviewCommands, GeneralCommands } from './../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, window } from 'vscode';
|
||||
import { Extension } from '../helpers';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { getWebviewJsFiles } from '../utils';
|
||||
|
||||
export class Chatbot {
|
||||
/**
|
||||
* Open the Chatbot in the editor
|
||||
*/
|
||||
public static async open(extensionPath: string) {
|
||||
// Create the preview webview
|
||||
const webView = window.createWebviewPanel(
|
||||
'frontMatterChatbot',
|
||||
`Front Matter AI - ${l10n.t(LocalizationKey.commandsChatbotTitle)}`,
|
||||
{
|
||||
viewColumn: ViewColumn.Beside,
|
||||
preserveFocus: true
|
||||
},
|
||||
{
|
||||
enableScripts: true
|
||||
}
|
||||
);
|
||||
|
||||
webView.iconPath = {
|
||||
dark: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-dark.svg')),
|
||||
light: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-light.svg'))
|
||||
};
|
||||
|
||||
const cspSource = webView.webview.cspSource;
|
||||
|
||||
const fetchLocalization = async (requestId: string) => {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = await getLocalizationFile();
|
||||
|
||||
webView.webview.postMessage({
|
||||
command: GeneralCommands.toVSCode.getLocalization,
|
||||
requestId,
|
||||
payload: fileContents
|
||||
});
|
||||
};
|
||||
|
||||
webView.webview.onDidReceiveMessage(async (message) => {
|
||||
const { command, requestId, payload, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (payload || data) {
|
||||
commands.executeCommand('vscode.open', payload || data);
|
||||
}
|
||||
break;
|
||||
case GeneralCommands.toVSCode.getLocalization:
|
||||
fetchLocalization(requestId);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const webviewFile = 'dashboard.main.js';
|
||||
const localPort = `9000`;
|
||||
const localServerUrl = `localhost:${localPort}`;
|
||||
|
||||
const nonce = WebviewHelper.getNonce();
|
||||
|
||||
const ext = Extension.getInstance();
|
||||
const isProd = ext.isProductionMode;
|
||||
const version = ext.getVersion();
|
||||
const isBeta = ext.isBetaVersion();
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
`img-src ${cspSource} http: https:;`,
|
||||
`script-src ${
|
||||
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
} 'unsafe-eval'`,
|
||||
`style-src ${cspSource} 'self' 'unsafe-inline' http: https:`,
|
||||
`connect-src https://* ${
|
||||
isProd
|
||||
? ``
|
||||
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
}`
|
||||
];
|
||||
|
||||
let scriptUris = [];
|
||||
if (isProd) {
|
||||
scriptUris = await getWebviewJsFiles('dashboard', webView.webview);
|
||||
} else {
|
||||
scriptUris.push(`http://${localServerUrl}/${webviewFile}`);
|
||||
}
|
||||
|
||||
// By default, the chatbot is seen as experimental
|
||||
const experimental = true;
|
||||
|
||||
webView.webview.html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:100%;height:100%;margin:0;padding:0;">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="${csp.join('; ')}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>Front Matter Docs Chatbot</title>
|
||||
</head>
|
||||
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
|
||||
<div id="app" data-type="chatbot" data-isProd="${isProd}" data-environment="${
|
||||
isBeta ? 'BETA' : 'main'
|
||||
}" data-version="${version.usedVersion}" ${
|
||||
experimental ? `data-experimental="${experimental}"` : ''
|
||||
} style="width:100%;height:100%;margin:0;padding:0;"></div>
|
||||
|
||||
${scriptUris
|
||||
.map((uri) => `<script ${isProd ? `nonce="${nonce}"` : ''} src="${uri}"></script>`)
|
||||
.join('\n')}
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`chatbot-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,6 @@ export class Preview {
|
||||
return;
|
||||
}
|
||||
|
||||
const integratedBrowserCommand = await this.getIntegratedBrowserCommand();
|
||||
const browserLiteCommand = await this.getBrowserLiteCommand();
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
@@ -70,12 +69,6 @@ export class Preview {
|
||||
const slug = await this.getContentSlug(article, editor?.document.uri.fsPath);
|
||||
const localhostUrl = await this.getLocalServerUrl();
|
||||
|
||||
if (integratedBrowserCommand) {
|
||||
const pageUrl = joinUrl(localhostUrl.toString(), slug || '');
|
||||
commands.executeCommand(integratedBrowserCommand, pageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (browserLiteCommand) {
|
||||
const pageUrl = joinUrl(localhostUrl.toString(), slug || '');
|
||||
commands.executeCommand(browserLiteCommand, pageUrl);
|
||||
@@ -375,17 +368,6 @@ export class Preview {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Browser Lite is installed
|
||||
*/
|
||||
private static async getIntegratedBrowserCommand() {
|
||||
const allCommands = await commands.getCommands(true);
|
||||
if (allCommands.includes(`workbench.action.browser.open`)) {
|
||||
return `workbench.action.browser.open`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the localhost url
|
||||
* @returns
|
||||
|
||||
@@ -4,39 +4,22 @@ import {
|
||||
EXTENSION_NAME,
|
||||
NOTIFICATION_TYPE,
|
||||
SETTING_SEO_DESCRIPTION_LENGTH,
|
||||
SETTING_SEO_TITLE_LENGTH,
|
||||
SETTING_VALIDATION_ENABLED
|
||||
SETTING_SEO_TITLE_LENGTH
|
||||
} from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Notifications,
|
||||
SeoHelper,
|
||||
Settings,
|
||||
FrontMatterValidator,
|
||||
ValidationError
|
||||
} from '../helpers';
|
||||
import { ArticleHelper, Notifications, SeoHelper, Settings } from '../helpers';
|
||||
import { PanelProvider } from '../panelWebView/PanelProvider';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { DataListener } from '../listeners/panel';
|
||||
import { commands } from 'vscode';
|
||||
import { Field } from '../models';
|
||||
import { FrontMatterParser } from '../parsers';
|
||||
import { Preview } from './Preview';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { i18n } from './i18n';
|
||||
import { getDescriptionField, getTitleField } from '../utils';
|
||||
import * as yaml from 'yaml';
|
||||
|
||||
export class StatusListener {
|
||||
private static _validator: FrontMatterValidator | undefined;
|
||||
private static get validator(): FrontMatterValidator {
|
||||
if (!StatusListener._validator) {
|
||||
StatusListener._validator = new FrontMatterValidator();
|
||||
}
|
||||
return StatusListener._validator;
|
||||
}
|
||||
/**
|
||||
* Update the text of the status bar
|
||||
*
|
||||
@@ -87,12 +70,6 @@ export class StatusListener {
|
||||
// Check the required fields
|
||||
if (editor) {
|
||||
StatusListener.verifyRequiredFields(editor, article, collection);
|
||||
|
||||
// Schema validation
|
||||
const validationEnabled = Settings.get<boolean>(SETTING_VALIDATION_ENABLED, true);
|
||||
if (validationEnabled) {
|
||||
await StatusListener.verifySchemaValidation(editor, article, collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,245 +173,6 @@ export class StatusListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify schema validation
|
||||
* @param editor Text editor
|
||||
* @param article Parsed front matter
|
||||
* @param collection Diagnostic collection
|
||||
*/
|
||||
private static async verifySchemaValidation(
|
||||
editor: vscode.TextEditor,
|
||||
article: ParsedFrontMatter,
|
||||
collection: vscode.DiagnosticCollection
|
||||
) {
|
||||
try {
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
if (!contentType || !contentType.fields || contentType.fields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate against schema
|
||||
const errors = await StatusListener.validator.validate(article.data, contentType);
|
||||
|
||||
if (errors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = editor.document.getText();
|
||||
const schemaDiagnostics: vscode.Diagnostic[] = [];
|
||||
|
||||
for (const error of errors) {
|
||||
const range = StatusListener.findSchemaErrorRange(editor.document, text, error);
|
||||
|
||||
if (range) {
|
||||
const diagnostic: vscode.Diagnostic = {
|
||||
code: '',
|
||||
message: error.message,
|
||||
range,
|
||||
severity: vscode.DiagnosticSeverity.Warning,
|
||||
source: EXTENSION_NAME
|
||||
};
|
||||
|
||||
schemaDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if (schemaDiagnostics.length > 0) {
|
||||
if (collection.has(editor.document.uri)) {
|
||||
const otherDiag = collection.get(editor.document.uri) || [];
|
||||
collection.set(editor.document.uri, [...otherDiag, ...schemaDiagnostics]);
|
||||
} else {
|
||||
collection.set(editor.document.uri, [...schemaDiagnostics]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail validation errors to not disrupt the user experience
|
||||
// Logger can be used here if needed for debugging
|
||||
}
|
||||
}
|
||||
|
||||
private static findSchemaErrorRange(
|
||||
document: vscode.TextDocument,
|
||||
text: string,
|
||||
error: ValidationError
|
||||
): vscode.Range | undefined {
|
||||
const language = FrontMatterParser.getLanguageFromContent(text);
|
||||
|
||||
if (language === 'yaml') {
|
||||
const yamlRange = StatusListener.findYamlSchemaErrorRange(document, text, error);
|
||||
if (yamlRange) {
|
||||
return yamlRange;
|
||||
}
|
||||
}
|
||||
|
||||
return StatusListener.findTextSchemaErrorRange(document, text, error);
|
||||
}
|
||||
|
||||
private static findYamlSchemaErrorRange(
|
||||
document: vscode.TextDocument,
|
||||
text: string,
|
||||
error: ValidationError
|
||||
): vscode.Range | undefined {
|
||||
const frontMatter = StatusListener.getYamlFrontMatter(text);
|
||||
if (!frontMatter) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const path = StatusListener.getValidationPath(error);
|
||||
if (path.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const doc = yaml.parseDocument(frontMatter.content);
|
||||
const node = doc.getIn(path, true) as { range?: [number, number, number] } | null;
|
||||
|
||||
if (!node?.range || node.range.length < 2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalizedRange = StatusListener.normalizeYamlNodeRange(frontMatter.content, node.range);
|
||||
if (!normalizedRange) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new vscode.Range(
|
||||
document.positionAt(frontMatter.startOffset + normalizedRange.start),
|
||||
document.positionAt(frontMatter.startOffset + normalizedRange.end)
|
||||
);
|
||||
}
|
||||
|
||||
private static findTextSchemaErrorRange(
|
||||
document: vscode.TextDocument,
|
||||
text: string,
|
||||
error: ValidationError
|
||||
): vscode.Range | undefined {
|
||||
const path = StatusListener.getValidationPath(error);
|
||||
const fieldName = path.length > 0 ? String(path[path.length - 1]) : '';
|
||||
const arrayIndex =
|
||||
typeof path[path.length - 1] === 'number' ? (path[path.length - 1] as number) : undefined;
|
||||
const searchFieldName =
|
||||
arrayIndex !== undefined ? String(path[path.length - 2] || '') : fieldName;
|
||||
|
||||
if (!searchFieldName || searchFieldName === 'root') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const frontMatterMatch = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
const frontMatterEnd = frontMatterMatch ? frontMatterMatch[0].length : text.length;
|
||||
const searchText = text.substring(0, frontMatterEnd);
|
||||
const fieldIdx = searchText.indexOf(`${searchFieldName}:`);
|
||||
|
||||
if (fieldIdx === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let posStart = document.positionAt(fieldIdx);
|
||||
let posEnd = document.positionAt(fieldIdx + searchFieldName.length);
|
||||
|
||||
if (arrayIndex !== undefined) {
|
||||
const afterField = text.indexOf('\n', fieldIdx) + 1;
|
||||
let remaining = arrayIndex;
|
||||
let searchFrom = afterField;
|
||||
while (searchFrom < frontMatterEnd) {
|
||||
const lineEnd = text.indexOf('\n', searchFrom);
|
||||
const line = text.substring(searchFrom, lineEnd === -1 ? frontMatterEnd : lineEnd);
|
||||
if (/^\s*-\s/.test(line)) {
|
||||
if (remaining === 0) {
|
||||
const valueOffset = line.indexOf('- ') + 2;
|
||||
const rawItemValue = line.substring(valueOffset).trim();
|
||||
const isQuoted =
|
||||
rawItemValue.length > 1 &&
|
||||
((rawItemValue.startsWith('"') && rawItemValue.endsWith('"')) ||
|
||||
(rawItemValue.startsWith("'") && rawItemValue.endsWith("'")));
|
||||
const itemValue = isQuoted ? rawItemValue.slice(1, -1) : rawItemValue;
|
||||
const valueStartOffset = searchFrom + valueOffset + (isQuoted ? 1 : 0);
|
||||
posStart = document.positionAt(valueStartOffset);
|
||||
posEnd = document.positionAt(valueStartOffset + itemValue.length);
|
||||
break;
|
||||
}
|
||||
remaining--;
|
||||
} else if (line.trim() && !/^\s/.test(line)) {
|
||||
break;
|
||||
}
|
||||
searchFrom = (lineEnd === -1 ? frontMatterEnd : lineEnd) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new vscode.Range(posStart, posEnd);
|
||||
}
|
||||
|
||||
private static getValidationPath(error: ValidationError): Array<string | number> {
|
||||
const path =
|
||||
error.field && error.field !== 'root'
|
||||
? error.field
|
||||
.split('.')
|
||||
.filter(Boolean)
|
||||
.map((segment) => (/^\d+$/.test(segment) ? parseInt(segment, 10) : segment))
|
||||
: [];
|
||||
|
||||
if (error.keyword === 'required' && typeof error.params?.missingProperty === 'string') {
|
||||
return [...path, error.params.missingProperty];
|
||||
}
|
||||
|
||||
if (
|
||||
error.keyword === 'additionalProperties' &&
|
||||
typeof error.params?.additionalProperty === 'string'
|
||||
) {
|
||||
return [...path, error.params.additionalProperty];
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static getYamlFrontMatter(
|
||||
text: string
|
||||
): { content: string; startOffset: number } | undefined {
|
||||
const openMatch = text.match(/^---\r?\n/);
|
||||
if (!openMatch) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const startOffset = openMatch[0].length;
|
||||
const closeMatch = /\r?\n---/.exec(text.slice(startOffset));
|
||||
const endOffset = closeMatch ? startOffset + closeMatch.index : text.length;
|
||||
|
||||
return {
|
||||
content: text.slice(startOffset, endOffset),
|
||||
startOffset
|
||||
};
|
||||
}
|
||||
|
||||
private static normalizeYamlNodeRange(
|
||||
source: string,
|
||||
range: [number, number, number]
|
||||
): { start: number; end: number } | undefined {
|
||||
let start = range[0];
|
||||
let end = range[1];
|
||||
|
||||
if (start >= end) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let value = source.slice(start, end);
|
||||
const leadingWhitespace = value.match(/^\s*/)?.[0].length || 0;
|
||||
const trailingWhitespace = value.match(/\s*$/)?.[0].length || 0;
|
||||
|
||||
start += leadingWhitespace;
|
||||
end -= trailingWhitespace;
|
||||
value = source.slice(start, end);
|
||||
|
||||
if (
|
||||
value.length > 1 &&
|
||||
((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'")))
|
||||
) {
|
||||
start += 1;
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
return start < end ? { start, end } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the line of the field
|
||||
* @param text
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './Article';
|
||||
export * from './Backers';
|
||||
export * from './Cache';
|
||||
export * from './Chatbot';
|
||||
export * from './Content';
|
||||
export * from './Dashboard';
|
||||
export * from './Diagnostics';
|
||||
|
||||
@@ -28,6 +28,7 @@ export const COMMAND_NAME = {
|
||||
collapseSections: getCommandName('collapseSections'),
|
||||
preview: getCommandName('preview'),
|
||||
docs: getCommandName('docs'),
|
||||
chatbot: getCommandName('chatbot'),
|
||||
dashboard: getCommandName('dashboard'),
|
||||
dashboardMedia: getCommandName('dashboard.media'),
|
||||
dashboardSnippets: getCommandName('dashboard.snippets'),
|
||||
|
||||
@@ -120,7 +120,10 @@ export const SETTING_COPILOT_FAMILY = 'copilot.family';
|
||||
|
||||
export const SETTING_LOGGING = 'logging';
|
||||
|
||||
export const SETTING_VALIDATION_ENABLED = 'validation.enabled';
|
||||
/**
|
||||
* Sponsors only settings
|
||||
*/
|
||||
export const SETTING_SPONSORS_AI_ENABLED = 'sponsors.ai.enabled';
|
||||
|
||||
/**
|
||||
* Project override support
|
||||
|
||||
@@ -23,7 +23,6 @@ export enum DashboardMessage {
|
||||
createContent = 'createContent',
|
||||
createByContentType = 'createByContentType',
|
||||
createByTemplate = 'createByTemplate',
|
||||
createContentInFolder = 'createContentInFolder',
|
||||
refreshPages = 'refreshPages',
|
||||
searchPages = 'searchPages',
|
||||
openFile = 'openFile',
|
||||
@@ -32,7 +31,6 @@ export enum DashboardMessage {
|
||||
pinItem = 'pinItem',
|
||||
unpinItem = 'unpinItem',
|
||||
rename = 'rename',
|
||||
moveFile = 'moveFile',
|
||||
|
||||
// Media Dashboard
|
||||
getMedia = 'getMedia',
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { XCircleIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface INumberFieldProps {
|
||||
name: string;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
icon?: JSX.Element;
|
||||
disabled?: boolean;
|
||||
autoFocus?: boolean;
|
||||
onChange?: (value: string) => void;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
export const NumberField: React.FunctionComponent<INumberFieldProps> = ({
|
||||
name,
|
||||
value,
|
||||
placeholder,
|
||||
description,
|
||||
icon,
|
||||
autoFocus,
|
||||
disabled,
|
||||
onChange,
|
||||
onReset
|
||||
}: React.PropsWithChildren<INumberFieldProps>) => {
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
<input
|
||||
type="number"
|
||||
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>
|
||||
|
||||
{
|
||||
description && (
|
||||
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
|
||||
{description}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,5 @@
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import {
|
||||
EyeIcon,
|
||||
GlobeEuropeAfricaIcon,
|
||||
TrashIcon,
|
||||
LanguageIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
ArrowRightCircleIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, I18nConfig } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -65,11 +58,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
setSelectedItemAction({ path, action: 'delete' });
|
||||
}, [path]);
|
||||
|
||||
const onMove = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
setSelectedItemAction({ path, action: 'move' });
|
||||
}, [path]);
|
||||
|
||||
const onRename = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
messageHandler.send(DashboardMessage.rename, path);
|
||||
@@ -134,11 +122,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onMove}>
|
||||
<ArrowRightCircleIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>Move to folder</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onRename}>
|
||||
<RenameIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonRename)}</span>
|
||||
|
||||
@@ -14,7 +14,6 @@ import { GeneralCommands } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { FilesProvider } from '../../providers/FilesProvider';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { MoveFileDialog } from '../Modals/MoveFileDialog';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { deletePage } from '../../utils';
|
||||
|
||||
@@ -29,14 +28,12 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const { pageItems } = usePages(pages);
|
||||
const [showDeletionAlert, setShowDeletionAlert] = React.useState(false);
|
||||
const [showMoveDialog, setShowMoveDialog] = 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(() => {
|
||||
setShowMoveDialog(false);
|
||||
setShowDeletionAlert(false);
|
||||
setSelectedItemAction(undefined);
|
||||
}, []);
|
||||
@@ -49,29 +46,13 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
setSelectedItemAction(undefined);
|
||||
}, [page]);
|
||||
|
||||
const onMoveConfirm = useCallback((destinationFolder: string) => {
|
||||
if (page) {
|
||||
Messenger.send(DashboardMessage.moveFile, {
|
||||
filePath: page.fmFilePath,
|
||||
destinationFolder
|
||||
});
|
||||
}
|
||||
setShowMoveDialog(false);
|
||||
setSelectedItemAction(undefined);
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedItemAction && selectedItemAction.path) {
|
||||
const pageItem = pageItems.find((p) => p.fmFilePath === selectedItemAction.path);
|
||||
if (selectedItemAction && selectedItemAction.path && selectedItemAction.action === 'delete') {
|
||||
const page = pageItems.find((p) => p.fmFilePath === selectedItemAction.path);
|
||||
|
||||
if (pageItem) {
|
||||
setPage(pageItem);
|
||||
|
||||
if (selectedItemAction.action === 'delete') {
|
||||
setShowDeletionAlert(true);
|
||||
} else if (selectedItemAction.action === 'move') {
|
||||
setShowMoveDialog(true);
|
||||
}
|
||||
if (page) {
|
||||
setPage(page);
|
||||
setShowDeletionAlert(true);
|
||||
}
|
||||
|
||||
setSelectedItemAction(undefined);
|
||||
@@ -104,15 +85,6 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
|
||||
{showMoveDialog && page && (
|
||||
<MoveFileDialog
|
||||
page={page}
|
||||
availableFolders={pageFolders}
|
||||
dismiss={onDismiss}
|
||||
trigger={onMoveConfirm}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showDeletionAlert && page && (
|
||||
<Alert
|
||||
title={l10n.t(LocalizationKey.dashboardContentsContentActionsAlertTitle, page.title)}
|
||||
|
||||
@@ -17,8 +17,6 @@ export const List: React.FunctionComponent<IListProps> = ({
|
||||
className = `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-4`;
|
||||
} else if (view === DashboardViewType.List) {
|
||||
className = `-mx-4`;
|
||||
} else if (view === DashboardViewType.Structure) {
|
||||
className = `structure-view`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,6 @@ import { GroupOption } from '../../constants/GroupOption';
|
||||
import { GroupingSelector, PageAtom, PagedItems, ViewSelector } from '../../state';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
import { StructureView } from './StructureView';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
|
||||
@@ -146,21 +145,16 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
/>
|
||||
{settings && settings?.contentFolders?.length > 0 ? (
|
||||
<p className={`text-xl font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoMarkdown)}</p>
|
||||
|
||||
|
||||
) : (
|
||||
<p className={`text-lg font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoFolders)}</p>
|
||||
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle Structure view first - it overrides all other display modes
|
||||
if (view === DashboardViewType.Structure) {
|
||||
return <StructureView pages={pages} />;
|
||||
}
|
||||
|
||||
if (grouping !== GroupOption.none) {
|
||||
return (
|
||||
<>
|
||||
@@ -202,7 +196,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
|
||||
<h1 className='text-xl flex space-x-2 items-center mb-4'>
|
||||
<PinIcon className={`-rotate-45`} />
|
||||
<span>{localize(LocalizationKey.dashboardContentsOverviewPinned)}</span>
|
||||
|
||||
|
||||
</h1>
|
||||
<List>
|
||||
{pinnedPages.map((page, idx) => (
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { Page } from '../../models/Page';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { DateField } from '../Common/DateField';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { useMemo } from 'react';
|
||||
import { Status } from './Status';
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { openFile } from '../../utils';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { cn } from '../../../utils/cn';
|
||||
|
||||
export interface IStructureItemProps extends Page { }
|
||||
|
||||
export const StructureItem: React.FunctionComponent<IStructureItemProps> = ({
|
||||
...pageData
|
||||
}: React.PropsWithChildren<IStructureItemProps>) => {
|
||||
const { selectedFiles } = useSelectedItems();
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
|
||||
|
||||
const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
|
||||
|
||||
const onOpenFile = React.useCallback(() => {
|
||||
openFile(pageData.fmFilePath);
|
||||
}, [pageData.fmFilePath]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className={cn(
|
||||
`flex items-center space-x-3 py-1 px-2 rounded cursor-pointer hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)]`,
|
||||
isSelected && `bg-[var(--vscode-list-activeSelectionBackground)]`
|
||||
)}
|
||||
>
|
||||
<ItemSelection filePath={pageData.fmFilePath} show />
|
||||
|
||||
<MarkdownIcon className="w-4 h-4 text-[var(--vscode-symbolIcon-fileForeground)] flex-shrink-0" />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={onOpenFile}
|
||||
className="flex-1 text-left truncate font-medium"
|
||||
>
|
||||
{escapedTitle}
|
||||
</button>
|
||||
|
||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||
{pageData.date && (
|
||||
<DateField
|
||||
value={pageData.date}
|
||||
format={pageData.fmDateFormat}
|
||||
className="text-xs text-[var(--vscode-descriptionForeground)]"
|
||||
/>
|
||||
)}
|
||||
|
||||
{draftField && draftField.name && typeof pageData[draftField.name] !== "undefined" && (
|
||||
<Status draft={pageData[draftField.name]} published={pageData.fmPublished} />
|
||||
)}
|
||||
|
||||
<ContentActions
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={onOpenFile}
|
||||
listView
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,321 +0,0 @@
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import { ChevronRightIcon, FolderIcon, PlusIcon, HomeIcon, ArrowLeftIcon } from '@heroicons/react/24/solid';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Page } from '../../models';
|
||||
import { StructureItem } from './StructureItem';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { SelectedStructureFolderAtom, SettingsSelector } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface IStructureViewProps {
|
||||
pages: Page[];
|
||||
}
|
||||
|
||||
interface FolderNode {
|
||||
name: string;
|
||||
path: string;
|
||||
children: FolderNode[];
|
||||
pages: Page[];
|
||||
}
|
||||
|
||||
export const StructureView: React.FunctionComponent<IStructureViewProps> = ({
|
||||
pages
|
||||
}: React.PropsWithChildren<IStructureViewProps>) => {
|
||||
const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedStructureFolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const folderTree = useMemo(() => {
|
||||
const root: FolderNode = {
|
||||
name: '',
|
||||
path: '',
|
||||
children: [],
|
||||
pages: []
|
||||
};
|
||||
|
||||
const folderMap = new Map<string, FolderNode>();
|
||||
folderMap.set('', root);
|
||||
|
||||
// Helper to compute the normalized workspace-relative folder path for a page.
|
||||
// This returns the actual folder path relative to the workspace, not just titles.
|
||||
const computeNormalizedFolderPath = (page: Page): string => {
|
||||
if (!page.fmFolder) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const fmFolder = page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
||||
|
||||
// Use fmRelFilePath which is already workspace-relative
|
||||
if (page.fmRelFilePath) {
|
||||
const relPath = parseWinPath(page.fmRelFilePath).replace(/^\/+|\/+$/g, '');
|
||||
const relDir = relPath.includes('/') ? relPath.substring(0, relPath.lastIndexOf('/')).replace(/^\/+|\/+$/g, '') : '';
|
||||
if (relDir) {
|
||||
return relDir;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use fmFolder title if we can't determine the path
|
||||
return fmFolder;
|
||||
};
|
||||
|
||||
// First pass: create all folder nodes (ensure nodes exist even if a page lacks fmFilePath)
|
||||
for (const page of pages) {
|
||||
if (!page.fmFolder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
||||
if (!normalizedPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parts = normalizedPath.split('/').filter(part => part.length > 0);
|
||||
|
||||
let currentPath = '';
|
||||
let currentNode = root;
|
||||
|
||||
for (const part of parts) {
|
||||
const fullPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
|
||||
if (!folderMap.has(fullPath)) {
|
||||
const newNode: FolderNode = {
|
||||
name: part,
|
||||
path: fullPath,
|
||||
children: [],
|
||||
pages: []
|
||||
};
|
||||
folderMap.set(fullPath, newNode);
|
||||
currentNode.children.push(newNode);
|
||||
}
|
||||
|
||||
const nextNode = folderMap.get(fullPath);
|
||||
if (nextNode) {
|
||||
currentNode = nextNode;
|
||||
}
|
||||
currentPath = fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: assign pages to their exact folder node (including subfolders)
|
||||
for (const page of pages) {
|
||||
if (!page.fmFolder) {
|
||||
root.pages.push(page);
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
||||
const folderNode = normalizedPath ? folderMap.get(normalizedPath) : folderMap.get(page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''));
|
||||
if (folderNode) {
|
||||
folderNode.pages.push(page);
|
||||
} else {
|
||||
// If folder not found, add to root as fallback
|
||||
root.pages.push(page);
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}, [pages]);
|
||||
|
||||
// Filter the folder tree based on the selected folder
|
||||
const displayedNode = useMemo(() => {
|
||||
if (!selectedFolder) {
|
||||
return folderTree;
|
||||
}
|
||||
|
||||
// Find the selected folder node in the tree
|
||||
const findNode = (node: FolderNode, path: string): FolderNode | null => {
|
||||
if (node.path === path) {
|
||||
return node;
|
||||
}
|
||||
for (const child of node.children) {
|
||||
const found = findNode(child, path);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const foundNode = findNode(folderTree, selectedFolder);
|
||||
return foundNode || folderTree;
|
||||
}, [folderTree, selectedFolder]);
|
||||
|
||||
const handleFolderClick = (folderPath: string) => {
|
||||
setSelectedFolder(folderPath);
|
||||
};
|
||||
|
||||
const handleBackClick = () => {
|
||||
if (!selectedFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to parent folder
|
||||
const parts = selectedFolder.split('/');
|
||||
if (parts.length > 1) {
|
||||
const parentPath = parts.slice(0, -1).join('/');
|
||||
setSelectedFolder(parentPath);
|
||||
} else {
|
||||
setSelectedFolder(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHomeClick = () => {
|
||||
setSelectedFolder(null);
|
||||
};
|
||||
|
||||
const handleCreateContent = () => {
|
||||
Messenger.send(DashboardMessage.createContentInFolder, { folderPath: selectedFolder });
|
||||
};
|
||||
|
||||
const renderFolderNode = (node: FolderNode, depth = 0): React.ReactNode => {
|
||||
const hasContent = node.pages.length > 0 || node.children.length > 0;
|
||||
|
||||
if (!hasContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isRoot = depth === 0;
|
||||
const paddingLeft = depth * 20;
|
||||
|
||||
if (isRoot) {
|
||||
// For root node, render children and pages directly
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
{/* Root level folders */}
|
||||
{node.children.map(child => renderFolderNode(child, depth + 1))}
|
||||
|
||||
{/* Root level pages */}
|
||||
{node.pages.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-3 text-[var(--vscode-editor-foreground)]">
|
||||
Root Files
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
{node.pages.map((page, idx) => (
|
||||
<li key={`${page.slug}-${idx}`}>
|
||||
<StructureItem {...page} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={node.path} className="mb-4">
|
||||
<Disclosure defaultOpen={depth <= 1}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="flex items-center w-full gap-1" style={{ paddingLeft: `${paddingLeft}px` }}>
|
||||
<Disclosure.Button className="flex items-center text-left hover:bg-[var(--vscode-list-hoverBackground)] rounded px-2 py-1 transition-colors">
|
||||
<ChevronRightIcon
|
||||
className={`w-4 h-4 transform transition-transform ${open ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
|
||||
<button
|
||||
onClick={() => handleFolderClick(node.path)}
|
||||
className="flex items-center flex-1 px-2 py-1 hover:bg-[var(--vscode-list-hoverBackground)] rounded transition-colors"
|
||||
title={l10n.t(LocalizationKey.commonOpen)}
|
||||
>
|
||||
<FolderIcon className="w-4 h-4 mr-2 flex-shrink-0 text-[var(--vscode-symbolIcon-folderForeground)]" />
|
||||
<span className="flex items-center font-medium text-[var(--vscode-editor-foreground)] flex-1">
|
||||
<span className="mr-2">{node.name}</span>
|
||||
{node.pages.length > 0 && (
|
||||
<span className="text-sm text-[var(--vscode-descriptionForeground)]">
|
||||
({node.pages.length} {node.pages.length === 1 ? 'file' : 'files'})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<ChevronRightIcon className="w-4 h-4 flex-shrink-0 text-[var(--vscode-descriptionForeground)]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Disclosure.Panel className="mt-2">
|
||||
{/* Child folders */}
|
||||
{node.children.map(child => renderFolderNode(child, depth + 1))}
|
||||
|
||||
{/* Pages in this folder */}
|
||||
{node.pages.length > 0 && (
|
||||
<ul className="space-y-1 mb-3">
|
||||
{node.pages.map((page, idx) => (
|
||||
<li key={`${page.slug}-${idx}`} style={{ paddingLeft: `${paddingLeft + 20}px` }}>
|
||||
<StructureItem {...page} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="structure-view">
|
||||
{/* Toolbar */}
|
||||
<div className="mb-4 pb-3 border-b border-[var(--frontmatter-border)]">
|
||||
{/* Breadcrumb navigation */}
|
||||
{selectedFolder && (
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={handleHomeClick}
|
||||
className="p-1 hover:bg-[var(--vscode-list-hoverBackground)] rounded"
|
||||
title="Home"
|
||||
>
|
||||
<HomeIcon className="w-4 h-4 text-[var(--vscode-descriptionForeground)]" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleBackClick}
|
||||
className="flex items-center space-x-1 px-2 py-1 hover:bg-[var(--vscode-list-hoverBackground)] rounded text-sm"
|
||||
title={l10n.t(LocalizationKey.commonBack) || 'Back'}
|
||||
>
|
||||
<ArrowLeftIcon className="w-3 h-3" />
|
||||
<span>{l10n.t(LocalizationKey.commonBack) || 'Back'}</span>
|
||||
</button>
|
||||
<span className="text-sm text-[var(--vscode-descriptionForeground)]">
|
||||
/ {selectedFolder.split('/').join(' / ')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create content button */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={handleCreateContent}
|
||||
disabled={!settings?.initialized}
|
||||
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"
|
||||
title={selectedFolder
|
||||
? l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent) + ` in ${selectedFolder}`
|
||||
: l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
|
||||
>
|
||||
<PlusIcon className="w-4 h-4 mr-1" />
|
||||
<span>
|
||||
{selectedFolder
|
||||
? `${l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)} here`
|
||||
: l10n.t(LocalizationKey.dashboardHeaderHeaderCreateContent)}
|
||||
</span>
|
||||
</button>
|
||||
{selectedFolder && (
|
||||
<span className="text-xs text-[var(--vscode-descriptionForeground)]">
|
||||
in {selectedFolder}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Folder tree */}
|
||||
{renderFolderNode(displayedNode)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,10 @@ import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { MediaTotalSelector, PageAtom, SettingsAtom, ViewSelector } from '../../state';
|
||||
import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IPaginationProps {
|
||||
totalPages?: number;
|
||||
@@ -18,7 +17,6 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
|
||||
const [page, setPage] = useRecoilState(PageAtom);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const { pageSetNr, totalPagesNr } = usePagination(
|
||||
settings?.dashboardState.contents.pagination,
|
||||
totalPages,
|
||||
@@ -35,17 +33,17 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
|
||||
if (i >= 0 && i <= totalPagesNr) {
|
||||
buttons.push(
|
||||
<button
|
||||
key={i}
|
||||
disabled={i === page}
|
||||
onClick={() => {
|
||||
setPage(i);
|
||||
}}
|
||||
className={`max-h-8 rounded ${page === i
|
||||
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
|
||||
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
key={i}
|
||||
disabled={i === page}
|
||||
onClick={() => {
|
||||
setPage(i);
|
||||
}}
|
||||
className={`max-h-8 rounded ${page === i
|
||||
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
|
||||
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -60,10 +58,6 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
if (view === DashboardViewType.Structure) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center sm:justify-end space-x-2 text-sm">
|
||||
<PaginationButton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { ViewAtom, SettingsSelector } from '../../state';
|
||||
import { Bars4Icon, Squares2X2Icon, FolderIcon } from '@heroicons/react/24/solid';
|
||||
import { Bars4Icon, Squares2X2Icon } from '@heroicons/react/24/solid';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { DashboardViewType } from '../../models';
|
||||
@@ -16,7 +16,9 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
|
||||
const [view, setView] = useRecoilState(ViewAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const handleViewChange = (newView: DashboardViewType) => {
|
||||
const toggleView = () => {
|
||||
const newView =
|
||||
view === DashboardViewType.Grid ? DashboardViewType.List : DashboardViewType.Grid;
|
||||
setView(newView);
|
||||
Messenger.send(DashboardMessage.setPageViewType, newView);
|
||||
};
|
||||
@@ -34,7 +36,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
|
||||
}`}
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToGrid)}
|
||||
type={`button`}
|
||||
onClick={() => handleViewChange(DashboardViewType.Grid)}
|
||||
onClick={toggleView}
|
||||
>
|
||||
<Squares2X2Icon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>
|
||||
@@ -42,29 +44,17 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={`flex items-center px-2 py-1 ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
|
||||
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
|
||||
}`}
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
|
||||
type={`button`}
|
||||
onClick={() => handleViewChange(DashboardViewType.List)}
|
||||
onClick={toggleView}
|
||||
>
|
||||
<Bars4Icon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>
|
||||
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.Structure ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
|
||||
}`}
|
||||
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
|
||||
type={`button`}
|
||||
onClick={() => handleViewChange(DashboardViewType.Structure)}
|
||||
>
|
||||
<FolderIcon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>
|
||||
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { FolderIcon, ChevronRightIcon } from '@heroicons/react/24/solid';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { Page } from '../../models';
|
||||
|
||||
export interface IMoveFileDialogProps {
|
||||
page: Page;
|
||||
availableFolders: string[];
|
||||
dismiss: () => void;
|
||||
trigger: (destinationFolder: string) => void;
|
||||
}
|
||||
|
||||
interface FolderNode {
|
||||
name: string;
|
||||
path: string;
|
||||
children: FolderNode[];
|
||||
level: number;
|
||||
}
|
||||
|
||||
export const MoveFileDialog: React.FunctionComponent<IMoveFileDialogProps> = ({
|
||||
page,
|
||||
availableFolders,
|
||||
dismiss,
|
||||
trigger
|
||||
}: React.PropsWithChildren<IMoveFileDialogProps>) => {
|
||||
const [selectedFolder, setSelectedFolder] = useState<string>('');
|
||||
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
|
||||
|
||||
// Build folder tree structure
|
||||
const folderTree = useMemo(() => {
|
||||
const root: FolderNode[] = [];
|
||||
const folderMap = new Map<string, FolderNode>();
|
||||
|
||||
for (const folderPath of availableFolders) {
|
||||
const normalized = parseWinPath(folderPath).replace(/^\/+|\/+$/g, '');
|
||||
const parts = normalized.split('/').filter(Boolean);
|
||||
|
||||
let currentPath = '';
|
||||
let currentLevel: FolderNode[] = root;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const fullPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
|
||||
if (!folderMap.has(fullPath)) {
|
||||
const newNode: FolderNode = {
|
||||
name: part,
|
||||
path: fullPath,
|
||||
children: [],
|
||||
level: i
|
||||
};
|
||||
folderMap.set(fullPath, newNode);
|
||||
currentLevel.push(newNode);
|
||||
}
|
||||
|
||||
const node = folderMap.get(fullPath);
|
||||
if (node) {
|
||||
currentLevel = node.children;
|
||||
}
|
||||
currentPath = fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}, [availableFolders]);
|
||||
|
||||
const toggleFolder = (folderPath: string) => {
|
||||
const newExpanded = new Set(expandedFolders);
|
||||
if (newExpanded.has(folderPath)) {
|
||||
newExpanded.delete(folderPath);
|
||||
} else {
|
||||
newExpanded.add(folderPath);
|
||||
}
|
||||
setExpandedFolders(newExpanded);
|
||||
};
|
||||
|
||||
const renderFolderNode = (node: FolderNode): React.ReactNode => {
|
||||
const isExpanded = expandedFolders.has(node.path);
|
||||
const isSelected = selectedFolder === node.path;
|
||||
const hasChildren = node.children.length > 0;
|
||||
const paddingLeft = node.level * 20;
|
||||
|
||||
return (
|
||||
<div key={node.path}>
|
||||
<div
|
||||
className={`flex items-center py-1 px-2 cursor-pointer hover:bg-[var(--vscode-list-hoverBackground)] rounded ${isSelected ? 'bg-[var(--vscode-list-activeSelectionBackground)]' : ''
|
||||
}`}
|
||||
style={{ paddingLeft: `${paddingLeft}px` }}
|
||||
onClick={() => setSelectedFolder(node.path)}
|
||||
>
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleFolder(node.path);
|
||||
}}
|
||||
className="mr-1"
|
||||
>
|
||||
<ChevronRightIcon
|
||||
className={`w-3 h-3 transform transition-transform ${isExpanded ? 'rotate-90' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{!hasChildren && <span className="w-3 mr-1"></span>}
|
||||
<FolderIcon className="w-4 h-4 mr-2 text-[var(--vscode-symbolIcon-folderForeground)]" />
|
||||
<span className="text-sm text-[var(--vscode-editor-foreground)]">{node.name}</span>
|
||||
</div>
|
||||
{hasChildren && isExpanded && (
|
||||
<div>
|
||||
{node.children.map((child) => renderFolderNode(child))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handleMove = () => {
|
||||
if (selectedFolder) {
|
||||
trigger(selectedFolder);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-expand folders by default (first level)
|
||||
useEffect(() => {
|
||||
const firstLevelFolders = folderTree.map(node => node.path);
|
||||
setExpandedFolders(new Set(firstLevelFolders));
|
||||
}, [folderTree]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
|
||||
<div className="bg-[var(--vscode-editor-background)] border border-[var(--frontmatter-border)] rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] flex flex-col">
|
||||
<div className="p-6 border-b border-[var(--frontmatter-border)]">
|
||||
<h2 className="text-xl font-bold text-[var(--vscode-editor-foreground)]">
|
||||
Move File
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-[var(--vscode-descriptionForeground)]">
|
||||
Move <span className="font-medium">{page.title}</span> to a different folder
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="space-y-1">
|
||||
{folderTree.length > 0 ? (
|
||||
folderTree.map((node) => renderFolderNode(node))
|
||||
) : (
|
||||
<p className="text-sm text-[var(--vscode-descriptionForeground)]">
|
||||
No folders available
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-[var(--frontmatter-border)] flex justify-end space-x-2">
|
||||
<button
|
||||
onClick={dismiss}
|
||||
className="px-4 py-2 text-sm font-medium rounded text-[var(--vscode-button-foreground)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]"
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonCancel)}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleMove}
|
||||
disabled={!selectedFolder}
|
||||
className="px-4 py-2 text-sm font-medium rounded text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Move
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
import { Choice, SnippetField, SnippetInfoField } from '../../../models';
|
||||
import { useEffect } from 'react';
|
||||
import { TextField } from '../Common/TextField';
|
||||
import { NumberField } from '../Common/NumberField';
|
||||
|
||||
export interface ISnippetInputFieldProps {
|
||||
field: SnippetField;
|
||||
@@ -79,17 +78,6 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'number') {
|
||||
return (
|
||||
<NumberField
|
||||
name={field.name}
|
||||
value={field.value as string || ''}
|
||||
description={field.description}
|
||||
onChange={(e) => onValueChange(field, e)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
name={field.name}
|
||||
|
||||
@@ -21,7 +21,9 @@ import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatu
|
||||
|
||||
export interface ISnippetsProps { }
|
||||
|
||||
export const Snippets: React.FunctionComponent<ISnippetsProps> = () => {
|
||||
export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
_: React.PropsWithChildren<ISnippetsProps>
|
||||
) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export enum DashboardViewType {
|
||||
Grid = 1,
|
||||
List,
|
||||
Structure
|
||||
List
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContentFolder, I18nConfig } from '../../models';
|
||||
import { I18nConfig } from '../../models';
|
||||
|
||||
export interface Page {
|
||||
// Properties for caching
|
||||
@@ -20,16 +20,15 @@ export interface Page {
|
||||
fmCategories: string[];
|
||||
fmContentType: string;
|
||||
fmDateFormat: string | undefined;
|
||||
fmPageFolder: ContentFolder | undefined;
|
||||
|
||||
// i18n fields
|
||||
fmDefaultLocale?: boolean;
|
||||
fmLocale?: I18nConfig;
|
||||
fmTranslations?: {
|
||||
fmTranslations?: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
title: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { atom } from 'recoil';
|
||||
export const SelectedItemActionAtom = atom<
|
||||
| {
|
||||
path: string;
|
||||
action: 'view' | 'edit' | 'delete' | 'move';
|
||||
action: 'view' | 'edit' | 'delete';
|
||||
}
|
||||
| undefined
|
||||
>({
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const SelectedStructureFolderAtom = atom<string | null>({
|
||||
key: 'SelectedStructureFolderAtom',
|
||||
default: null
|
||||
});
|
||||
@@ -22,7 +22,6 @@ export * from './SearchAtom';
|
||||
export * from './SearchReadyAtom';
|
||||
export * from './SelectedItemActionAtom';
|
||||
export * from './SelectedMediaFolderAtom';
|
||||
export * from './SelectedStructureFolderAtom';
|
||||
export * from './SettingsAtom';
|
||||
export * from './SortingAtom';
|
||||
export * from './TabAtom';
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { selector } from 'recoil';
|
||||
import { SelectedStructureFolderAtom } from '..';
|
||||
|
||||
export const SelectedStructureFolderSelector = selector({
|
||||
key: 'SelectedStructureFolderSelector',
|
||||
get: ({ get }) => {
|
||||
return get(SelectedStructureFolderAtom);
|
||||
}
|
||||
});
|
||||
@@ -8,7 +8,6 @@ export * from './MediaTotalSelector';
|
||||
export * from './PageSelector';
|
||||
export * from './SearchSelector';
|
||||
export * from './SelectedMediaFolderSelector';
|
||||
export * from './SelectedStructureFolderSelector';
|
||||
export * from './SettingsSelector';
|
||||
export * from './SortingSelector';
|
||||
export * from './TabSelector';
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
Article,
|
||||
Settings,
|
||||
StatusListener,
|
||||
Chatbot,
|
||||
Taxonomy
|
||||
} from './commands';
|
||||
import { join } from 'path';
|
||||
@@ -193,12 +194,17 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.docs, () => {
|
||||
vscode.commands.executeCommand(
|
||||
`workbench.action.browser.open`,
|
||||
`simpleBrowser.show`,
|
||||
`https://${extension.isBetaVersion() ? `beta.` : ``}frontmatter.codes/docs`
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// Chat to the bot
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.chatbot, () => Chatbot.open(extensionPath))
|
||||
);
|
||||
|
||||
// Create the editor experience for bulk scripts
|
||||
subscriptions.push(
|
||||
vscode.workspace.registerTextDocumentContentProvider(
|
||||
|
||||
@@ -149,17 +149,12 @@ export class ArticleHelper {
|
||||
* @returns A promise that resolves to the contents of the file, or undefined if the file does not exist.
|
||||
*/
|
||||
public static async getContents(filePath: string): Promise<string | undefined> {
|
||||
try {
|
||||
const file = await workspace.fs.readFile(Uri.file(parseWinPath(filePath)));
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(file);
|
||||
} catch (error) {
|
||||
Logger.error(`ArticleHelper.getContents: Failed to read file ${filePath}: ${error}`);
|
||||
const file = await workspace.fs.readFile(Uri.file(parseWinPath(filePath)));
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(file);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -812,8 +807,7 @@ export class ArticleHelper {
|
||||
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
|
||||
|
||||
const headings = elms.filter((node) => node.type === 'heading');
|
||||
const paragraphNodes = elms.filter((node) => node.type === 'paragraph');
|
||||
const paragraphs = paragraphNodes.length;
|
||||
const paragraphs = elms.filter((node) => node.type === 'paragraph').length;
|
||||
const images = elms.filter((node) => node.type === 'image').length;
|
||||
const links: string[] = elms
|
||||
.filter((node) => node.type === 'link')
|
||||
@@ -842,21 +836,6 @@ export class ArticleHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract first paragraph text for SEO keyword checking
|
||||
let firstParagraph = '';
|
||||
if (paragraphNodes.length > 0) {
|
||||
const firstParagraphNode = paragraphNodes[0];
|
||||
const extractTextFromNode = (node: any): string => {
|
||||
if (node.type === 'text') {
|
||||
return node.value || '';
|
||||
} else if (node.children && Array.isArray(node.children)) {
|
||||
return node.children.map(extractTextFromNode).join('');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
firstParagraph = extractTextFromNode(firstParagraphNode);
|
||||
}
|
||||
|
||||
const wordCount = this.wordCount(0, mdTree);
|
||||
|
||||
return {
|
||||
@@ -867,8 +846,7 @@ export class ArticleHelper {
|
||||
internalLinks,
|
||||
externalLinks: externalLinks.length,
|
||||
wordCount,
|
||||
content: article.content,
|
||||
firstParagraph
|
||||
content: article.content
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import { Folders } from '../commands/Folders';
|
||||
import { Questions } from './Questions';
|
||||
import { Notifications } from './Notifications';
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from '../constants/ContentType';
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { basename } from 'path';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { encodeEmoji, existsAsync, fieldWhenClause, getTitleField, writeFileAsync } from '../utils';
|
||||
@@ -956,25 +957,8 @@ export class ContentType {
|
||||
let templatePath = contentType.template;
|
||||
let templateData: ParsedFrontMatter | null | undefined = null;
|
||||
if (templatePath) {
|
||||
try {
|
||||
templatePath = Folders.getAbsFilePath(templatePath);
|
||||
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
|
||||
if (!templateData) {
|
||||
Logger.warning(
|
||||
`ContentType.create: Template file not found or could not be parsed: ${templatePath}`
|
||||
);
|
||||
Notifications.warning(
|
||||
l10n.t(LocalizationKey.commonError) + ` Template not found: ${templatePath}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
`ContentType.create: Error loading template from ${templatePath}: ${error}`
|
||||
);
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.commonError) + ` Template loading failed: ${templatePath}`
|
||||
);
|
||||
}
|
||||
templatePath = Folders.getAbsFilePath(templatePath);
|
||||
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
|
||||
}
|
||||
|
||||
const newFilePath: string | undefined = await ArticleHelper.createContent(
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
import { ContentType, Field, FieldType, CustomTaxonomy } from '../models';
|
||||
import { Settings } from '../helpers/SettingsHelper';
|
||||
import { SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_CUSTOM } from '../constants';
|
||||
import { TaxonomyHelper } from './TaxonomyHelper';
|
||||
import { TaxonomyType } from '../models/TaxonomyType';
|
||||
|
||||
/**
|
||||
* JSON Schema type definition
|
||||
*/
|
||||
export interface JSONSchema {
|
||||
$schema?: string;
|
||||
type?: string | string[];
|
||||
properties?: { [key: string]: JSONSchema };
|
||||
required?: string[];
|
||||
items?: JSONSchema;
|
||||
enum?: any[];
|
||||
format?: string;
|
||||
anyOf?: JSONSchema[];
|
||||
oneOf?: JSONSchema[];
|
||||
allOf?: JSONSchema[];
|
||||
description?: string;
|
||||
default?: any;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSON Schema from Front Matter Content Type definitions
|
||||
*
|
||||
* This utility converts Front Matter content type definitions into JSON Schema format
|
||||
* which can then be used for validation. It handles all field types supported by
|
||||
* Front Matter CMS including nested fields, blocks, and field groups.
|
||||
*
|
||||
* Field Type Mappings:
|
||||
* - string, slug, image, file, customField → string
|
||||
* - number → number (with optional min/max)
|
||||
* - boolean, draft → boolean
|
||||
* - datetime → string with date-time format
|
||||
* - choice → string with enum (or array if multiple)
|
||||
* - tags, categories, taxonomy, list → array of strings
|
||||
* - fields → nested object with properties
|
||||
* - block → array of objects with oneOf for field groups
|
||||
* - json → any valid JSON type
|
||||
* - dataFile, contentRelationship → string or array
|
||||
*
|
||||
* Features:
|
||||
* - Required field validation
|
||||
* - Type validation
|
||||
* - Enum/choice validation
|
||||
* - Number range validation (min/max)
|
||||
* - Nested object support
|
||||
* - Block field support with multiple field group options
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* const schema = ContentTypeSchemaGenerator.generateSchema(contentType);
|
||||
* // Use schema for validation with AJV or other JSON Schema validators
|
||||
* ```
|
||||
*/
|
||||
export class ContentTypeSchemaGenerator {
|
||||
/**
|
||||
* Generate JSON Schema from a content type
|
||||
* @param contentType The content type to generate schema from
|
||||
* @returns JSON Schema object
|
||||
*/
|
||||
public static async generateSchema(contentType: ContentType): Promise<JSONSchema> {
|
||||
const schema: JSONSchema = {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
|
||||
if (!contentType.fields || contentType.fields.length === 0) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
// Process each field in the content type
|
||||
for (const field of contentType.fields) {
|
||||
const fieldSchema = await this.generateFieldSchema(field);
|
||||
if (fieldSchema && schema.properties) {
|
||||
schema.properties[field.name] = fieldSchema;
|
||||
|
||||
// Add to required array if field is required
|
||||
if (field.required && schema.required) {
|
||||
schema.required.push(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove required array if empty
|
||||
if (schema.required && schema.required.length === 0) {
|
||||
delete schema.required;
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JSON Schema for a single field
|
||||
* @param field The field to generate schema from
|
||||
* @returns JSON Schema object for the field
|
||||
*/
|
||||
private static async generateFieldSchema(field: Field): Promise<JSONSchema | null> {
|
||||
// Skip divider and heading fields as they are UI-only
|
||||
if (field.type === 'divider' || field.type === 'heading') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const schema: JSONSchema = {};
|
||||
|
||||
// Add description if available
|
||||
if (field.description) {
|
||||
schema.description = field.description;
|
||||
}
|
||||
|
||||
// Add default value if specified
|
||||
if (field.default !== undefined && field.default !== null) {
|
||||
schema.default = field.default;
|
||||
}
|
||||
|
||||
// Map field type to JSON Schema type
|
||||
switch (field.type) {
|
||||
case 'string':
|
||||
case 'slug':
|
||||
case 'image':
|
||||
case 'file':
|
||||
case 'customField':
|
||||
schema.type = 'string';
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
schema.type = 'number';
|
||||
if (field.numberOptions) {
|
||||
if (field.numberOptions.min !== undefined) {
|
||||
schema.minimum = field.numberOptions.min;
|
||||
}
|
||||
if (field.numberOptions.max !== undefined) {
|
||||
schema.maximum = field.numberOptions.max;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
case 'draft':
|
||||
schema.type = 'boolean';
|
||||
break;
|
||||
|
||||
case 'datetime':
|
||||
schema.type = 'string';
|
||||
schema.format = 'date-time';
|
||||
break;
|
||||
|
||||
case 'choice':
|
||||
if (field.multiple) {
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
if (field.choices && field.choices.length > 0) {
|
||||
schema.items.enum = this.extractChoiceValues(field.choices);
|
||||
}
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
if (field.choices && field.choices.length > 0) {
|
||||
schema.enum = this.extractChoiceValues(field.choices);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tags': {
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
|
||||
// Get available tags and add as enum for validation
|
||||
const availableTags = await TaxonomyHelper.get(TaxonomyType.Tag);
|
||||
if (availableTags && availableTags.length > 0) {
|
||||
schema.items.enum = availableTags;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'categories': {
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
|
||||
// Get available categories and add as enum for validation
|
||||
const availableCategories = await TaxonomyHelper.get(TaxonomyType.Category);
|
||||
if (availableCategories && availableCategories.length > 0) {
|
||||
schema.items.enum = availableCategories;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'taxonomy': {
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
|
||||
// Get custom taxonomy options if taxonomyId is specified
|
||||
if (field.taxonomyId) {
|
||||
const customTaxonomies = Settings.get<CustomTaxonomy[]>(SETTING_TAXONOMY_CUSTOM);
|
||||
if (customTaxonomies && customTaxonomies.length > 0) {
|
||||
const taxonomy = customTaxonomies.find((t) => t.id === field.taxonomyId);
|
||||
if (taxonomy && taxonomy.options && taxonomy.options.length > 0) {
|
||||
schema.items.enum = taxonomy.options;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list':
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'fields':
|
||||
schema.type = 'object';
|
||||
schema.properties = {};
|
||||
schema.required = [];
|
||||
|
||||
if (field.fields && field.fields.length > 0) {
|
||||
for (const subField of field.fields) {
|
||||
const subFieldSchema = await this.generateFieldSchema(subField);
|
||||
if (subFieldSchema && schema.properties) {
|
||||
schema.properties[subField.name] = subFieldSchema;
|
||||
|
||||
if (subField.required && schema.required) {
|
||||
schema.required.push(subField.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove required array if empty
|
||||
if (schema.required && schema.required.length === 0) {
|
||||
delete schema.required;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'block': {
|
||||
// Block fields can contain different field groups
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'object'
|
||||
};
|
||||
|
||||
// Try to get the field group schemas
|
||||
const blockSchemas = await this.getBlockFieldGroupSchemas(field);
|
||||
if (blockSchemas.length > 0) {
|
||||
schema.items = {
|
||||
oneOf: blockSchemas
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'json':
|
||||
// JSON fields can be any valid JSON
|
||||
schema.type = ['object', 'array', 'string', 'number', 'boolean', 'null'];
|
||||
break;
|
||||
|
||||
case 'dataFile':
|
||||
// Data file references are typically strings (IDs or keys)
|
||||
schema.type = 'string';
|
||||
break;
|
||||
|
||||
case 'contentRelationship':
|
||||
// Content relationships can be a string (slug/path) or array of strings
|
||||
if (field.multiple) {
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'string'
|
||||
};
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fieldCollection':
|
||||
// Field collections reference field groups, handle similarly to blocks
|
||||
schema.type = 'array';
|
||||
schema.items = {
|
||||
type: 'object'
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown field type, default to string
|
||||
schema.type = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract choice values from field choices
|
||||
* @param choices Array of choice strings or objects
|
||||
* @returns Array of choice values
|
||||
*/
|
||||
private static extractChoiceValues(choices: (string | { id?: string | null; title: string })[]): string[] {
|
||||
return choices.map((choice) => {
|
||||
if (typeof choice === 'string') {
|
||||
return choice;
|
||||
} else {
|
||||
return choice.id || choice.title;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schemas for block field groups
|
||||
* @param field The block field
|
||||
* @returns Array of JSON Schemas for each field group
|
||||
*/
|
||||
private static async getBlockFieldGroupSchemas(field: Field): Promise<JSONSchema[]> {
|
||||
const schemas: JSONSchema[] = [];
|
||||
|
||||
if (!field.fieldGroup) {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
const fieldGroupIds = Array.isArray(field.fieldGroup) ? field.fieldGroup : [field.fieldGroup];
|
||||
const fieldGroups = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS) as { id: string; fields: Field[] }[] | undefined;
|
||||
|
||||
if (!fieldGroups || fieldGroups.length === 0) {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
for (const groupId of fieldGroupIds) {
|
||||
const fieldGroup = fieldGroups.find((fg) => fg.id === groupId);
|
||||
if (fieldGroup && fieldGroup.fields) {
|
||||
const groupSchema: JSONSchema = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
|
||||
for (const groupField of fieldGroup.fields) {
|
||||
const fieldSchema = await this.generateFieldSchema(groupField);
|
||||
if (fieldSchema && groupSchema.properties) {
|
||||
groupSchema.properties[groupField.name] = fieldSchema;
|
||||
|
||||
if (groupField.required && groupSchema.required) {
|
||||
groupSchema.required.push(groupField.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove required array if empty
|
||||
if (groupSchema.required && groupSchema.required.length === 0) {
|
||||
delete groupSchema.required;
|
||||
}
|
||||
|
||||
schemas.push(groupSchema);
|
||||
}
|
||||
}
|
||||
|
||||
return schemas;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { parse, parseISO, parseJSON, format } from 'date-fns';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
export class DateHelper {
|
||||
public static formatUpdate(value: string | null | undefined): string | null {
|
||||
@@ -20,20 +19,6 @@ export class DateHelper {
|
||||
return format(date, DateHelper.formatUpdate(dateFormat) as string);
|
||||
}
|
||||
|
||||
public static formatInTimezone(
|
||||
date?: Date,
|
||||
dateFormat?: string,
|
||||
timezone?: string
|
||||
): string | null {
|
||||
if (!date || !dateFormat) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return timezone
|
||||
? formatInTimeZone(date, timezone, DateHelper.formatUpdate(dateFormat) as string)
|
||||
: format(date, DateHelper.formatUpdate(dateFormat) as string);
|
||||
}
|
||||
|
||||
public static tryParse(date: any, format?: string): Date | null {
|
||||
if (!date) {
|
||||
return null;
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
import Ajv, { ErrorObject } from 'ajv';
|
||||
import { ContentType } from '../models';
|
||||
import { ContentTypeSchemaGenerator, JSONSchema } from './ContentTypeSchemaGenerator';
|
||||
|
||||
/**
|
||||
* Validation error with location information
|
||||
*/
|
||||
export interface ValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
keyword?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates front matter data against content type schemas
|
||||
*
|
||||
* This validator uses JSON Schema validation (via AJV) to ensure that front matter
|
||||
* in markdown files conforms to the structure defined in content types.
|
||||
*
|
||||
* Features:
|
||||
* - Automatic schema generation from content type definitions
|
||||
* - Type validation (string, number, boolean, datetime, arrays, etc.)
|
||||
* - Required field validation
|
||||
* - Enum/choice validation
|
||||
* - Number range validation (min/max)
|
||||
* - Nested object validation
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* const validator = new FrontMatterValidator();
|
||||
* const errors = validator.validate(frontMatterData, contentType);
|
||||
* if (errors.length > 0) {
|
||||
* // Handle validation errors
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class FrontMatterValidator {
|
||||
private ajv: Ajv;
|
||||
private schemaCache: Map<string, JSONSchema>;
|
||||
|
||||
constructor() {
|
||||
this.ajv = new Ajv({
|
||||
allErrors: true,
|
||||
verbose: true,
|
||||
strict: false,
|
||||
allowUnionTypes: true
|
||||
});
|
||||
this.schemaCache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate front matter data against a content type
|
||||
* @param data The front matter data to validate
|
||||
* @param contentType The content type to validate against
|
||||
* @returns Array of validation errors (empty if valid)
|
||||
*/
|
||||
public async validate(data: any, contentType: ContentType): Promise<ValidationError[]> {
|
||||
if (!contentType || !contentType.fields || contentType.fields.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get or generate schema
|
||||
const schema = await this.getSchema(contentType);
|
||||
if (!schema) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Compile and validate
|
||||
const validate = this.ajv.compile(schema);
|
||||
const valid = validate(data);
|
||||
|
||||
if (valid) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert AJV errors to our format
|
||||
return this.convertAjvErrors(validate.errors || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or generate schema for a content type
|
||||
* @param contentType The content type
|
||||
* @returns JSON Schema
|
||||
*/
|
||||
private async getSchema(contentType: ContentType): Promise<JSONSchema | null> {
|
||||
// Check cache first
|
||||
const cacheKey = contentType.name;
|
||||
if (this.schemaCache.has(cacheKey)) {
|
||||
return this.schemaCache.get(cacheKey) || null;
|
||||
}
|
||||
|
||||
// Generate new schema
|
||||
const schema = await ContentTypeSchemaGenerator.generateSchema(contentType);
|
||||
this.schemaCache.set(cacheKey, schema);
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the schema cache
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this.schemaCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert AJV errors to validation errors
|
||||
* @param ajvErrors AJV error objects
|
||||
* @returns Array of validation errors
|
||||
*/
|
||||
private convertAjvErrors(ajvErrors: ErrorObject[]): ValidationError[] {
|
||||
const errors: ValidationError[] = [];
|
||||
|
||||
for (const error of ajvErrors) {
|
||||
const field = this.extractFieldName(error.instancePath);
|
||||
const message = this.formatErrorMessage(error, field);
|
||||
|
||||
errors.push({
|
||||
field,
|
||||
message,
|
||||
keyword: error.keyword,
|
||||
params: error.params
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract field name from instance path
|
||||
* @param instancePath The JSON pointer path
|
||||
* @returns Field name
|
||||
*/
|
||||
private extractFieldName(instancePath: string): string {
|
||||
if (!instancePath || instancePath === '') {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
// Remove leading slash and convert to dot notation
|
||||
return instancePath
|
||||
.replace(/^\//, '')
|
||||
.replace(/\//g, '.')
|
||||
.replace(/~1/g, '/')
|
||||
.replace(/~0/g, '~');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error message for display
|
||||
* @param error AJV error object
|
||||
* @param field Field name
|
||||
* @returns Formatted error message
|
||||
*/
|
||||
private formatErrorMessage(error: ErrorObject, field: string): string {
|
||||
const displayField = field === 'root' ? 'The document' : `Field '${field}'`;
|
||||
|
||||
switch (error.keyword) {
|
||||
case 'required': {
|
||||
const missingProperty = error.params?.missingProperty;
|
||||
return `Missing required field '${missingProperty}'`;
|
||||
}
|
||||
|
||||
case 'type': {
|
||||
const expectedType = error.params?.type;
|
||||
return `${displayField} must be of type ${expectedType}`;
|
||||
}
|
||||
|
||||
case 'enum': {
|
||||
const allowedValues = error.params?.allowedValues;
|
||||
if (allowedValues && Array.isArray(allowedValues)) {
|
||||
return `${displayField} must be one of: ${allowedValues.join(', ')}`;
|
||||
}
|
||||
return `${displayField} has an invalid value`;
|
||||
}
|
||||
|
||||
case 'format': {
|
||||
const format = error.params?.format;
|
||||
return `${displayField} must be in ${format} format`;
|
||||
}
|
||||
|
||||
case 'minimum': {
|
||||
const minimum = error.params?.limit;
|
||||
return `${displayField} must be greater than or equal to ${minimum}`;
|
||||
}
|
||||
|
||||
case 'maximum': {
|
||||
const maximum = error.params?.limit;
|
||||
return `${displayField} must be less than or equal to ${maximum}`;
|
||||
}
|
||||
|
||||
case 'minItems': {
|
||||
const minItems = error.params?.limit;
|
||||
return `${displayField} must have at least ${minItems} items`;
|
||||
}
|
||||
|
||||
case 'maxItems': {
|
||||
const maxItems = error.params?.limit;
|
||||
return `${displayField} must have at most ${maxItems} items`;
|
||||
}
|
||||
|
||||
case 'additionalProperties': {
|
||||
const additionalProperty = error.params?.additionalProperty;
|
||||
return `Unexpected field '${additionalProperty}' is not allowed`;
|
||||
}
|
||||
|
||||
case 'oneOf':
|
||||
return `${displayField} must match exactly one of the allowed schemas`;
|
||||
|
||||
case 'anyOf':
|
||||
return `${displayField} must match at least one of the allowed schemas`;
|
||||
|
||||
default:
|
||||
return error.message || `${displayField} is invalid`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export class Logger {
|
||||
|
||||
private constructor() {
|
||||
const displayName = Extension.getInstance().displayName;
|
||||
Logger.channel = window.createOutputChannel(displayName, 'frontmatter.project.output');
|
||||
Logger.channel = window.createOutputChannel(displayName);
|
||||
commands.registerCommand(COMMAND_NAME.showOutputChannel, () => {
|
||||
Logger.channel?.show();
|
||||
});
|
||||
|
||||
@@ -172,14 +172,7 @@ export class MediaHelpers {
|
||||
}
|
||||
})
|
||||
);
|
||||
files = files
|
||||
.filter((f) => f.mtime !== undefined || f.mtimeMs !== undefined)
|
||||
.map((f) => {
|
||||
if (f.mtime === undefined && f.mtimeMs !== undefined) {
|
||||
return { ...f, mtime: new Date(f.mtimeMs as number) };
|
||||
}
|
||||
return f;
|
||||
});
|
||||
files = files.filter((f) => f.mtime !== undefined);
|
||||
|
||||
// Sort the files
|
||||
if (crntSort?.type === SortType.string) {
|
||||
|
||||
@@ -113,7 +113,7 @@ export class MediaLibrary {
|
||||
|
||||
public async get(id: string): Promise<MediaRecord | undefined> {
|
||||
try {
|
||||
const fileId = MediaLibrary.parsePath(id);
|
||||
const fileId = this.parsePath(id);
|
||||
if (await this.db?.exists(fileId)) {
|
||||
return await this.db?.getData(fileId);
|
||||
}
|
||||
@@ -142,13 +142,13 @@ export class MediaLibrary {
|
||||
}
|
||||
|
||||
public set(id: string, metadata: any): void {
|
||||
const fileId = MediaLibrary.parsePath(id);
|
||||
const fileId = this.parsePath(id);
|
||||
this.db?.push(fileId, metadata, true);
|
||||
}
|
||||
|
||||
public async rename(oldId: string, newId: string): Promise<void> {
|
||||
const fileId = MediaLibrary.parsePath(oldId);
|
||||
const newFileId = MediaLibrary.parsePath(newId);
|
||||
const fileId = this.parsePath(oldId);
|
||||
const newFileId = this.parsePath(newId);
|
||||
const data = await this.get(fileId);
|
||||
if (data) {
|
||||
this.db?.delete(fileId);
|
||||
@@ -157,7 +157,7 @@ export class MediaLibrary {
|
||||
}
|
||||
|
||||
public async remove(path: string): Promise<void> {
|
||||
const fileId = MediaLibrary.parsePath(path);
|
||||
const fileId = this.parsePath(path);
|
||||
await this.db?.delete(fileId);
|
||||
}
|
||||
|
||||
@@ -183,12 +183,9 @@ export class MediaLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
public static parsePath(path: string) {
|
||||
public parsePath(path: string) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
let absPath = parseWinPath(path).replace(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
WORKSPACE_PLACEHOLDER
|
||||
);
|
||||
let absPath = path.replace(parseWinPath(wsFolder?.fsPath || ''), WORKSPACE_PLACEHOLDER);
|
||||
absPath = isWindows() ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath.toLowerCase();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
SETTING_GLOBAL_TIMEZONE,
|
||||
SETTING_PANEL_ACTIONS_DISABLED,
|
||||
SETTING_SPONSORS_AI_ENABLED,
|
||||
SETTING_WEBSITE_URL
|
||||
} from './../constants/settings';
|
||||
import { workspace } from 'vscode';
|
||||
@@ -51,6 +51,7 @@ export class PanelSettings {
|
||||
|
||||
try {
|
||||
return {
|
||||
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
|
||||
copilotEnabled: await Copilot.isInstalled(),
|
||||
git: await GitListener.getSettings(),
|
||||
seo: {
|
||||
@@ -67,8 +68,7 @@ export class PanelSettings {
|
||||
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME)
|
||||
},
|
||||
date: {
|
||||
format: Settings.get<string>(SETTING_DATE_FORMAT) || '',
|
||||
timezone: Settings.get<string>(SETTING_GLOBAL_TIMEZONE) || 'UTC'
|
||||
format: Settings.get<string>(SETTING_DATE_FORMAT) || ''
|
||||
},
|
||||
tags: (await TaxonomyHelper.get(TaxonomyType.Tag)) || [],
|
||||
categories: (await TaxonomyHelper.get(TaxonomyType.Category)) || [],
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { QuickPickItem, QuickPickItemKind, window } from 'vscode';
|
||||
import { authentication, QuickPickItem, QuickPickItemKind, window } from 'vscode';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { SETTING_SPONSORS_AI_ENABLED } from '../constants';
|
||||
import { ContentType } from './ContentType';
|
||||
import { Notifications } from './Notifications';
|
||||
import { Settings } from './SettingsHelper';
|
||||
import { Logger } from './Logger';
|
||||
import { SponsorAi } from '../services/SponsorAI';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { ContentFolder } from '../models';
|
||||
@@ -37,28 +40,56 @@ export class Questions {
|
||||
* @returns
|
||||
*/
|
||||
public static async ContentTitle(showWarning = true): Promise<string | undefined> {
|
||||
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
|
||||
let title: string | undefined = '';
|
||||
const isCopilotInstalled = await Copilot.isInstalled();
|
||||
|
||||
let aiTitles: string[] | undefined;
|
||||
|
||||
if (isCopilotInstalled) {
|
||||
title = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (aiEnabled || isCopilotInstalled) {
|
||||
if (isCopilotInstalled) {
|
||||
title = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (title) {
|
||||
try {
|
||||
aiTitles = await Copilot.suggestTitles(title);
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersQuestionsContentTitleCopilotInputFailed)
|
||||
);
|
||||
title = undefined;
|
||||
if (title) {
|
||||
try {
|
||||
aiTitles = await Copilot.suggestTitles(title);
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersQuestionsContentTitleCopilotInputFailed)
|
||||
);
|
||||
title = undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], {
|
||||
silent: true
|
||||
});
|
||||
|
||||
if (githubAuth && githubAuth.account.label) {
|
||||
title = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (title) {
|
||||
try {
|
||||
aiTitles = await SponsorAi.getTitles(githubAuth.accessToken, title);
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputFailed)
|
||||
);
|
||||
title = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,11 +40,6 @@ export class SlugHelper {
|
||||
const fileName = SlugHelper.slugify(file.name);
|
||||
const regex = new RegExp('{{sluggedFileName}}', 'g');
|
||||
slugTemplate = slugTemplate.replace(regex, fileName);
|
||||
} else if (slugTemplate.includes(`{{slugifiedFileName}}`)) {
|
||||
const file = parse(filePath || '');
|
||||
const fileName = SlugHelper.slugify(file.name);
|
||||
const regex = new RegExp('{{slugifiedFileName}}', 'g');
|
||||
slugTemplate = slugTemplate.replace(regex, fileName);
|
||||
}
|
||||
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
|
||||
@@ -38,5 +38,3 @@ export * from './processFmPlaceholders';
|
||||
export * from './processI18nPlaceholders';
|
||||
export * from './processPathPlaceholders';
|
||||
export * from './processTimePlaceholders';
|
||||
export * from './ContentTypeSchemaGenerator';
|
||||
export * from './FrontMatterValidator';
|
||||
|
||||
@@ -75,30 +75,25 @@ export class MediaListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize(
|
||||
LocalizationKey.listenersDashboardMediaListenersDeleteMediaFolderProgressTitle
|
||||
),
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
const folderPath = parse(msg.folder).dir;
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize(LocalizationKey.listenersDashboardMediaListenersDeleteMediaFolderProgressTitle),
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
const folderPath = parse(msg.folder).dir;
|
||||
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
const parsedPath = MediaLibrary.parsePath(msg.folder);
|
||||
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
const parsedPath = mediaLib.parsePath(msg.folder);
|
||||
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
|
||||
|
||||
for (const fileName of Object.keys(mediaFiles)) {
|
||||
const filePath = join(msg.folder, fileName);
|
||||
await mediaLib.remove(filePath);
|
||||
}
|
||||
|
||||
await workspace.fs.delete(Uri.file(msg.folder), { recursive: true, useTrash: false });
|
||||
await MediaListener.sendMediaFiles(0, folderPath);
|
||||
for (const fileName of Object.keys(mediaFiles)) {
|
||||
const filePath = join(msg.folder, fileName);
|
||||
await mediaLib.remove(filePath);
|
||||
}
|
||||
);
|
||||
|
||||
await workspace.fs.delete(Uri.file(msg.folder), { recursive: true, useTrash: false });
|
||||
await MediaListener.sendMediaFiles(0, folderPath);
|
||||
});
|
||||
}
|
||||
|
||||
public static async updateMediaFolder(msg: {
|
||||
@@ -110,48 +105,41 @@ export class MediaListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize(
|
||||
LocalizationKey.listenersDashboardMediaListenersUpdateMediaFolderProgressTitle
|
||||
),
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
const folderName = parse(msg.folder).base;
|
||||
window.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize(LocalizationKey.listenersDashboardMediaListenersUpdateMediaFolderProgressTitle),
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
const folderName = parse(msg.folder).base;
|
||||
|
||||
const newFolderName = await window.showInputBox({
|
||||
prompt: 'Enter new folder name',
|
||||
value: folderName
|
||||
});
|
||||
|
||||
const newFolderName = await window.showInputBox({
|
||||
prompt: 'Enter new folder name',
|
||||
value: folderName
|
||||
});
|
||||
|
||||
if (!newFolderName || newFolderName === folderName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newFolderPath = join(parse(msg.folder).dir, newFolderName);
|
||||
|
||||
// Get all media files from the folder
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
const parsedPath = MediaLibrary.parsePath(msg.folder);
|
||||
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
|
||||
|
||||
// Update the folder
|
||||
await workspace.fs.rename(Uri.file(msg.folder), Uri.file(newFolderPath), {
|
||||
overwrite: false
|
||||
});
|
||||
|
||||
// Update the media files
|
||||
for (const fileName of Object.keys(mediaFiles)) {
|
||||
const newFilePath = join(newFolderPath, fileName);
|
||||
const oldFilePath = join(msg.folder, fileName);
|
||||
await mediaLib.rename(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
await this.sendMediaFiles(0, parse(msg.folder).dir);
|
||||
if (!newFolderName || newFolderName === folderName) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
const newFolderPath = join(parse(msg.folder).dir, newFolderName);
|
||||
|
||||
// Get all media files from the folder
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
const parsedPath = mediaLib.parsePath(msg.folder);
|
||||
const mediaFiles = await mediaLib.getAllByPath(parsedPath);
|
||||
|
||||
// Update the folder
|
||||
await workspace.fs.rename(Uri.file(msg.folder), Uri.file(newFolderPath), { overwrite: false });
|
||||
|
||||
// Update the media files
|
||||
for (const fileName of Object.keys(mediaFiles)) {
|
||||
const newFilePath = join(newFolderPath, fileName);
|
||||
const oldFilePath = join(msg.folder, fileName);
|
||||
await mediaLib.rename(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
await this.sendMediaFiles(0, parse(msg.folder).dir);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +205,7 @@ export class MediaListener extends BaseListener {
|
||||
for (const file of filesEndingWith) {
|
||||
const absPath = FilesHelper.relToAbsPath(file);
|
||||
if (!(await existsAsync(absPath))) {
|
||||
const parsedPath = MediaLibrary.parsePath(absPath);
|
||||
const parsedPath = mediaLib.parsePath(absPath);
|
||||
const metadata = await mediaLib.get(parsedPath);
|
||||
if (metadata) {
|
||||
unmappedFiles.push({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PostMessageData } from './../../models/PostMessageData';
|
||||
import { basename, join } from 'path';
|
||||
import { basename } from 'path';
|
||||
import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from 'vscode';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
@@ -12,24 +12,13 @@ import {
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Page } from '../../dashboardWebView/models';
|
||||
import { ContentFolder } from '../../models/ContentFolder';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
Logger,
|
||||
parseWinPath,
|
||||
Settings,
|
||||
ContentType,
|
||||
Notifications
|
||||
} from '../../helpers';
|
||||
import { ArticleHelper, Extension, Logger, parseWinPath, Settings } from '../../helpers';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { DataListener } from '../panel';
|
||||
import Fuse from 'fuse.js';
|
||||
import { PagesParser } from '../../services/PagesParser';
|
||||
import { unlinkAsync, rmdirAsync } from '../../utils';
|
||||
import { LoadingType } from '../../models';
|
||||
import { Questions } from '../../helpers/Questions';
|
||||
import { Template } from '../../commands/Template';
|
||||
|
||||
export class PagesListener extends BaseListener {
|
||||
private static watchers: { [path: string]: FileSystemWatcher } = {};
|
||||
@@ -56,9 +45,6 @@ export class PagesListener extends BaseListener {
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
case DashboardMessage.createContentInFolder:
|
||||
await this.createContentInFolder(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.refreshPages:
|
||||
this.getPagesData(true);
|
||||
break;
|
||||
@@ -71,9 +57,6 @@ export class PagesListener extends BaseListener {
|
||||
case DashboardMessage.rename:
|
||||
ArticleHelper.rename(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.moveFile:
|
||||
await this.moveFile(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,246 +306,6 @@ export class PagesListener extends BaseListener {
|
||||
this.sendMsg(DashboardCommand.searchPages, pageResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a file to a different folder
|
||||
* @param payload
|
||||
*/
|
||||
private static async moveFile(payload: { filePath: string; destinationFolder: string }) {
|
||||
if (!payload || !payload.filePath || !payload.destinationFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { filePath, destinationFolder } = payload;
|
||||
|
||||
try {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (!wsFolder) {
|
||||
Logger.error('Workspace folder not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all content folders
|
||||
const folders = await Folders.get();
|
||||
if (!folders || folders.length === 0) {
|
||||
Logger.error('No content folders found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the destination folder
|
||||
let targetFolderPath = '';
|
||||
for (const folder of folders) {
|
||||
const absoluteFolderPath = Folders.getFolderPath(Uri.file(folder.path));
|
||||
const relativeFolderPath = parseWinPath(absoluteFolderPath)
|
||||
.replace(parseWinPath(wsFolder.fsPath), '')
|
||||
.replace(/^\/+|\/+$/g, '');
|
||||
|
||||
if (
|
||||
destinationFolder === relativeFolderPath ||
|
||||
destinationFolder.startsWith(relativeFolderPath + '/')
|
||||
) {
|
||||
targetFolderPath = absoluteFolderPath;
|
||||
// Add subfolder if any
|
||||
if (destinationFolder !== relativeFolderPath) {
|
||||
const subPath = destinationFolder
|
||||
.substring(relativeFolderPath.length)
|
||||
.replace(/^\/+|\/+$/g, '');
|
||||
targetFolderPath = join(targetFolderPath, subPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFolderPath) {
|
||||
Logger.error('Target folder not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the file name
|
||||
const fileName = basename(filePath);
|
||||
const newFilePath = join(targetFolderPath, fileName);
|
||||
|
||||
// Check if target already exists
|
||||
try {
|
||||
await workspace.fs.stat(Uri.file(newFilePath));
|
||||
Logger.error(`File already exists at destination: ${newFilePath}`);
|
||||
return;
|
||||
} catch {
|
||||
// File doesn't exist, which is good
|
||||
}
|
||||
|
||||
// Check if it's a page bundle
|
||||
const article = await ArticleHelper.getFrontMatterByPath(filePath);
|
||||
if (article) {
|
||||
const contentType = await ArticleHelper.getContentType(article);
|
||||
|
||||
if (contentType.pageBundle) {
|
||||
// Move the entire folder
|
||||
const sourceFolder = parseWinPath(filePath).substring(
|
||||
0,
|
||||
parseWinPath(filePath).lastIndexOf('/')
|
||||
);
|
||||
const folderName = basename(sourceFolder);
|
||||
const newFolderPath = join(targetFolderPath, folderName);
|
||||
|
||||
// Move the folder
|
||||
await workspace.fs.rename(Uri.file(sourceFolder), Uri.file(newFolderPath), {
|
||||
overwrite: false
|
||||
});
|
||||
|
||||
Logger.info(`Moved page bundle from ${sourceFolder} to ${newFolderPath}`);
|
||||
} else {
|
||||
// Move just the file
|
||||
await workspace.fs.rename(Uri.file(filePath), Uri.file(newFilePath), {
|
||||
overwrite: false
|
||||
});
|
||||
|
||||
Logger.info(`Moved file from ${filePath} to ${newFilePath}`);
|
||||
}
|
||||
} else {
|
||||
// Move just the file
|
||||
await workspace.fs.rename(Uri.file(filePath), Uri.file(newFilePath), {
|
||||
overwrite: false
|
||||
});
|
||||
|
||||
Logger.info(`Moved file from ${filePath} to ${newFilePath}`);
|
||||
}
|
||||
|
||||
// Refresh the pages data
|
||||
this.getPagesData(true);
|
||||
} catch (error) {
|
||||
Logger.error(`Error moving file: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content in a specific folder
|
||||
* @param payload
|
||||
*/
|
||||
private static async createContentInFolder(payload: { folderPath: string | null }) {
|
||||
if (!payload) {
|
||||
// Fall back to regular content creation
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
return;
|
||||
}
|
||||
|
||||
const { folderPath } = payload;
|
||||
|
||||
// Get all content folders (including those with disableCreation)
|
||||
const allFolders = await Folders.get();
|
||||
|
||||
if (!allFolders || allFolders.length === 0) {
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
return;
|
||||
}
|
||||
|
||||
let targetFolder = null;
|
||||
let subPath = '';
|
||||
|
||||
if (folderPath) {
|
||||
// The folderPath is a relative path like "content/posts" or "blog/en"
|
||||
// We need to find the matching content folder and determine the subfolder
|
||||
Logger.info(`[createContentInFolder] folderPath: ${folderPath}`);
|
||||
|
||||
let bestMatch: { folder: ContentFolder; subPath: string; matchLength: number } | null = null;
|
||||
|
||||
for (const folder of allFolders) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (!wsFolder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const absoluteFolderPath = Folders.getFolderPath(Uri.file(folder.path));
|
||||
const relativeFolderPath = parseWinPath(absoluteFolderPath)
|
||||
.replace(parseWinPath(wsFolder.fsPath), '')
|
||||
.replace(/^\/+|\/+$/g, '');
|
||||
|
||||
Logger.info(
|
||||
`[createContentInFolder] Checking folder: ${folder.title}, relativePath: ${relativeFolderPath}`
|
||||
);
|
||||
|
||||
// Check if the folderPath matches or starts with this content folder
|
||||
if (folderPath === relativeFolderPath || folderPath.startsWith(relativeFolderPath + '/')) {
|
||||
const currentSubPath =
|
||||
folderPath !== relativeFolderPath
|
||||
? folderPath.substring(relativeFolderPath.length).replace(/^\/+|\/+$/g, '')
|
||||
: '';
|
||||
|
||||
// Keep track of the best (longest/most specific) match
|
||||
if (!bestMatch || relativeFolderPath.length > bestMatch.matchLength) {
|
||||
bestMatch = {
|
||||
folder,
|
||||
subPath: currentSubPath,
|
||||
matchLength: relativeFolderPath.length
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch) {
|
||||
targetFolder = bestMatch.folder;
|
||||
subPath = bestMatch.subPath;
|
||||
Logger.info(
|
||||
`[createContentInFolder] Best match: ${targetFolder.title}, subPath: ${subPath}`
|
||||
);
|
||||
|
||||
// Check if content creation is disabled for this folder
|
||||
if (targetFolder.disableCreation) {
|
||||
Notifications.error(`Content creation is disabled for folder: ${targetFolder.title}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFolder) {
|
||||
// If no folder matches, let the user select one (filter out disabled folders)
|
||||
const availableFolders = allFolders.filter((f) => !f.disableCreation);
|
||||
if (availableFolders.length === 0) {
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFolder = await Questions.SelectContentFolder();
|
||||
if (!selectedFolder) {
|
||||
return;
|
||||
}
|
||||
targetFolder = allFolders.find((f) => f.path === selectedFolder.path);
|
||||
}
|
||||
|
||||
if (!targetFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the folder path
|
||||
let absoluteFolderPath = Folders.getFolderPath(Uri.file(targetFolder.path));
|
||||
|
||||
// Add the subfolder if any
|
||||
if (subPath) {
|
||||
absoluteFolderPath = join(absoluteFolderPath, subPath);
|
||||
}
|
||||
|
||||
// Check if templates are enabled
|
||||
const templatesEnabled = Settings.get('dashboardState.contents.templatesEnabled');
|
||||
|
||||
if (templatesEnabled) {
|
||||
// Use the template creation flow
|
||||
await Template.create(absoluteFolderPath);
|
||||
} else {
|
||||
// Use the content type creation flow
|
||||
const selectedContentType = await Questions.SelectContentType(
|
||||
targetFolder.contentTypes || []
|
||||
);
|
||||
if (!selectedContentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentTypes = ContentType.getAll();
|
||||
const contentType = contentTypes?.find((ct) => ct.name === selectedContentType);
|
||||
if (contentType) {
|
||||
ContentType['create'](contentType, absoluteFolderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fresh page data
|
||||
*/
|
||||
|
||||
@@ -114,12 +114,7 @@ export class SsgListener extends BaseListener {
|
||||
}
|
||||
|
||||
// https://github.com/withastro/astro/blob/defab70cb2a0c67d5e9153542490d2749046b151/packages/astro/src/content/utils.ts#L450
|
||||
let contentConfig = await workspace.findFiles(`**/src/content/config.*`);
|
||||
|
||||
// Also search for content.config.* files (newer pattern)
|
||||
if (contentConfig.length === 0) {
|
||||
contentConfig = await workspace.findFiles(`**/content.config.*`);
|
||||
}
|
||||
const contentConfig = await workspace.findFiles(`**/src/content/config.*`);
|
||||
|
||||
if (contentConfig.length === 0) {
|
||||
SsgListener.sendRequest(command as any, requestId, []);
|
||||
|
||||
@@ -727,10 +727,6 @@ export enum LocalizationKey {
|
||||
* Change to list
|
||||
*/
|
||||
dashboardHeaderViewSwitchToList = 'dashboard.header.viewSwitch.toList',
|
||||
/**
|
||||
* Change to structure
|
||||
*/
|
||||
dashboardHeaderViewSwitchToStructure = 'dashboard.header.viewSwitch.toStructure',
|
||||
/**
|
||||
* Support Front Matter
|
||||
*/
|
||||
@@ -1112,7 +1108,7 @@ export enum LocalizationKey {
|
||||
*/
|
||||
dashboardStepsStepsToGetStartedGitName = 'dashboard.steps.stepsToGetStarted.git.name',
|
||||
/**
|
||||
* Enable Git synchronization to easily sync your changes with your repository.
|
||||
* Enable Git synchronization to eaily sync your changes with your repository.
|
||||
*/
|
||||
dashboardStepsStepsToGetStartedGitDescription = 'dashboard.steps.stepsToGetStarted.git.description',
|
||||
/**
|
||||
@@ -1636,10 +1632,6 @@ export enum LocalizationKey {
|
||||
* Content
|
||||
*/
|
||||
panelSeoKeywordInfoValidInfoContent = 'panel.seoKeywordInfo.validInfo.content',
|
||||
/**
|
||||
* First paragraph
|
||||
*/
|
||||
panelSeoKeywordInfoValidInfoFirstParagraph = 'panel.seoKeywordInfo.validInfo.firstParagraph',
|
||||
/**
|
||||
* Recommended frequency: 0.75% - 1.5%
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,6 @@ export interface MediaInfo {
|
||||
dimensions?: ISizeCalculationResult | undefined;
|
||||
mimeType?: string | undefined;
|
||||
mtime?: Date;
|
||||
mtimeMs?: number;
|
||||
ctime?: Date;
|
||||
size?: number;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface PanelSettings {
|
||||
dataTypes: DataType[] | undefined;
|
||||
fieldGroups: FieldGroup[] | undefined;
|
||||
commaSeparatedFields: string[];
|
||||
aiEnabled: boolean;
|
||||
copilotEnabled: boolean;
|
||||
contentFolders: ContentFolder[];
|
||||
websiteUrl: string;
|
||||
@@ -176,7 +177,6 @@ export interface WhenClause {
|
||||
|
||||
export interface DateInfo {
|
||||
format: string;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
export interface SEO {
|
||||
|
||||
@@ -11,7 +11,6 @@ export interface IArticleDetailsProps {
|
||||
internalLinks: number;
|
||||
externalLinks: number;
|
||||
images: number;
|
||||
firstParagraph?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface IDateTimeFieldProps extends BaseFieldProps<Date | null> {
|
||||
format?: string;
|
||||
timezone?: string;
|
||||
onChange: (date: string) => void;
|
||||
}
|
||||
|
||||
@@ -31,7 +30,6 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({
|
||||
value,
|
||||
required,
|
||||
format,
|
||||
timezone,
|
||||
onChange
|
||||
}: React.PropsWithChildren<IDateTimeFieldProps>) => {
|
||||
const DEFAULT_FORMAT = 'MM/dd/yyyy HH:mm';
|
||||
@@ -40,13 +38,11 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({
|
||||
const onDateChange = React.useCallback((date: Date) => {
|
||||
setDateValue(date);
|
||||
if (format) {
|
||||
// Always use DateHelper.formatInTimezone when a format is provided
|
||||
onChange(DateHelper.formatInTimezone(date, format, timezone) || "");
|
||||
onChange(DateHelper.format(date, format) || "");
|
||||
} else {
|
||||
// Only fallback to ISO string if no format is provided
|
||||
onChange(date.toISOString());
|
||||
}
|
||||
}, [format, timezone, onChange]);
|
||||
}, [format, onChange]);
|
||||
|
||||
const showRequiredState = useMemo(() => {
|
||||
return required && !dateValue;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { FieldMessage } from '../Fields/FieldMessage';
|
||||
import { FieldTitle } from '../Fields/FieldTitle';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { PanelSettingsAtom } from '../../state';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { SparklesIcon } from '@heroicons/react/24/outline';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import useDropdownStyle from '../../hooks/useDropdownStyle';
|
||||
import { CopilotIcon } from '../Icons';
|
||||
@@ -311,6 +311,21 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{settings?.aiEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action"
|
||||
title={localize(
|
||||
LocalizationKey.panelTagPickerAiSuggest,
|
||||
label?.toLowerCase() || type.toLowerCase()
|
||||
)}
|
||||
type="button"
|
||||
onClick={() => suggestTaxonomy('ai', type)}
|
||||
disabled={!!loading}
|
||||
>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{settings?.copilotEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action"
|
||||
@@ -327,7 +342,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [settings?.copilotEnabled, label, type]);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, label, type]);
|
||||
|
||||
const sortedSelectedTags = useMemo(() => {
|
||||
const safeSelected = selected || [];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PencilIcon } from '@heroicons/react/24/outline';
|
||||
import { PencilIcon, SparklesIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
@@ -132,6 +132,18 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{settings?.aiEnabled && settings.seo.descriptionField === name && (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
title={localize(LocalizationKey.panelFieldsTextFieldAiMessage, label?.toLowerCase())}
|
||||
type="button"
|
||||
onClick={() => suggestDescription('ai')}
|
||||
disabled={!!loading}
|
||||
>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{settings?.copilotEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
@@ -145,7 +157,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [settings?.copilotEnabled, settings?.seo, name, actions, loading]);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, settings?.seo, name, actions, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showRequiredState) {
|
||||
|
||||
@@ -193,7 +193,6 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
required={!!field.required}
|
||||
format={field.dateFormat || settings?.date?.format}
|
||||
onChange={onFieldChange}
|
||||
timezone={settings?.date?.timezone}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,6 @@ export interface ISeoKeywordInfoProps {
|
||||
content: string;
|
||||
wordCount?: number;
|
||||
headings?: string[];
|
||||
firstParagraph?: string;
|
||||
}
|
||||
|
||||
const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
@@ -27,8 +26,7 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
slug,
|
||||
content,
|
||||
wordCount,
|
||||
headings,
|
||||
firstParagraph
|
||||
headings
|
||||
}: React.PropsWithChildren<ISeoKeywordInfoProps>) => {
|
||||
|
||||
const density = () => {
|
||||
@@ -92,10 +90,9 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
(slug.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
slug.toLowerCase().includes(keyword.replace(/ /g, '-').toLowerCase())),
|
||||
content: !!content && content.toLowerCase().includes(keyword.toLowerCase()),
|
||||
heading: checkHeadings(),
|
||||
firstParagraph: !!firstParagraph && firstParagraph.toLowerCase().includes(keyword.toLowerCase())
|
||||
heading: checkHeadings()
|
||||
};
|
||||
}, [title, description, slug, content, headings, wordCount, firstParagraph]);
|
||||
}, [title, description, slug, content, headings, wordCount]);
|
||||
|
||||
const tooltipContent = React.useMemo(() => {
|
||||
return (
|
||||
@@ -105,8 +102,7 @@ const SeoKeywordInfo: React.FunctionComponent<ISeoKeywordInfoProps> = ({
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.description} /> {localize(LocalizationKey.commonDescription)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.slug} /> {localize(LocalizationKey.commonSlug)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.content} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoContent)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={!!checks.heading} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)}</span><br />
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={checks.firstParagraph} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoFirstParagraph)}</span>
|
||||
<span className='inline-flex items-center gap-1'><ValidInfo isValid={!!checks.heading} /> {localize(LocalizationKey.panelSeoKeywordInfoValidInfoLabel)}</span>
|
||||
</>
|
||||
)
|
||||
}, [checks]);
|
||||
|
||||
@@ -14,7 +14,6 @@ export interface ISeoKeywordsProps {
|
||||
content: string;
|
||||
headings?: string[];
|
||||
wordCount?: number;
|
||||
firstParagraph?: string;
|
||||
}
|
||||
|
||||
const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({
|
||||
|
||||
@@ -96,7 +96,6 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
headings={metadata?.articleDetails?.headingsText}
|
||||
wordCount={metadata?.articleDetails?.wordCount}
|
||||
content={metadata?.articleDetails?.content}
|
||||
firstParagraph={metadata?.articleDetails?.firstParagraph}
|
||||
/>
|
||||
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
|
||||
@@ -33,8 +33,7 @@ export class Copilot {
|
||||
}
|
||||
|
||||
const copilotExt = extensions.getExtension(`GitHub.copilot`);
|
||||
const copilotChatExt = extensions.getExtension(`GitHub.copilot-chat`);
|
||||
return !!copilotExt || !!copilotChatExt;
|
||||
return !!copilotExt;
|
||||
}
|
||||
|
||||
public static async suggestTitles(title: string): Promise<string[] | undefined> {
|
||||
@@ -270,7 +269,7 @@ Example: SEO, website optimization, digital marketing.`
|
||||
// console.log(models);
|
||||
const [model] = await lm.selectChatModels({
|
||||
vendor: 'copilot',
|
||||
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4.1'
|
||||
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4o-mini'
|
||||
});
|
||||
|
||||
if ((!model || !model.sendRequest) && retry <= 5) {
|
||||
|
||||
@@ -201,7 +201,7 @@ export class PagesParser {
|
||||
const modifiedField = await ArticleHelper.getModifiedDateField(article);
|
||||
const modifiedFieldValue =
|
||||
modifiedField?.name && article?.data[modifiedField.name]
|
||||
? DateHelper.tryParse(article?.data[modifiedField.name], modifiedField.dateFormat)?.getTime()
|
||||
? DateHelper.tryParse(article?.data[modifiedField.name])?.getTime()
|
||||
: undefined;
|
||||
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
@@ -253,8 +253,7 @@ export class PagesParser {
|
||||
Article.generateSlug(escapedTitle, article, contentType.slugTemplate)
|
||||
?.slugWithPrefixAndSuffix,
|
||||
date: article?.data[dateField] || '',
|
||||
draft: article?.data.draft,
|
||||
fmPageFolder: undefined
|
||||
draft: article?.data.draft
|
||||
};
|
||||
|
||||
let previewFieldParents = ContentType.findPreviewField(contentType.fields);
|
||||
@@ -335,7 +334,7 @@ export class PagesParser {
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
// Handle both string and object formats for the field value
|
||||
let imageValue: string | undefined;
|
||||
let imageValue: string;
|
||||
if (typeof fieldValue === 'string') {
|
||||
imageValue = fieldValue;
|
||||
} else if (typeof fieldValue === 'object' && fieldValue.src) {
|
||||
@@ -343,7 +342,7 @@ export class PagesParser {
|
||||
imageValue = fieldValue.src;
|
||||
} else {
|
||||
// Skip processing if the value is neither a string nor an object with src
|
||||
imageValue = undefined;
|
||||
imageValue = null;
|
||||
}
|
||||
|
||||
if (imageValue) {
|
||||
@@ -361,26 +360,26 @@ export class PagesParser {
|
||||
|
||||
const contentFolderPath = join(dirname(filePath), imageValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (await existsAsync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (await existsAsync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
let previewPath = '';
|
||||
|
||||
const Webview = Dashboard.getWebview();
|
||||
if (Webview) {
|
||||
previewPath = Webview.asWebviewUri(previewUri).toString();
|
||||
} else {
|
||||
previewPath = PagesParser.getWebviewUri(previewUri).toString();
|
||||
}
|
||||
|
||||
page['fmPreviewImage'] = previewPath || '';
|
||||
}
|
||||
let previewUri = null;
|
||||
if (await existsAsync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (await existsAsync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
let previewPath = '';
|
||||
|
||||
const Webview = Dashboard.getWebview();
|
||||
if (Webview) {
|
||||
previewPath = Webview.asWebviewUri(previewUri).toString();
|
||||
} else {
|
||||
previewPath = PagesParser.getWebviewUri(previewUri).toString();
|
||||
}
|
||||
|
||||
page['fmPreviewImage'] = previewPath || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { format } from 'date-fns';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { SETTING_GLOBAL_TIMEZONE } from '../constants';
|
||||
import { DateHelper, Settings } from '../helpers';
|
||||
|
||||
export const formatInTimezone = (date: Date, dateFormat: string) => {
|
||||
const timezone = Settings.get<string>(SETTING_GLOBAL_TIMEZONE) || 'UTC';
|
||||
return DateHelper.formatInTimezone(date, dateFormat, timezone) || '';
|
||||
const timezone = Settings.get<string>(SETTING_GLOBAL_TIMEZONE);
|
||||
return timezone
|
||||
? formatInTimeZone(date, timezone, DateHelper.formatUpdate(dateFormat) as string)
|
||||
: format(date, DateHelper.formatUpdate(dateFormat) as string);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user