mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-06-19 17:45:44 +02:00
Merge pull request #560 from estruyf/dev
This commit is contained in:
+69
-1
@@ -1,6 +1,74 @@
|
||||
# Change Log
|
||||
|
||||
## [8.3.0] - 2022-02-14 - [Release notes](https://beta.frontmatter.codes/updates/v8.3.0)
|
||||
## [8.4.0] - 2023-04-03 - [Release notes](https://beta.frontmatter.codes/updates/v8.4.0)
|
||||
|
||||
### 🧪 Experimental features
|
||||
|
||||
- External UI script support for dashboards
|
||||
- Visual Studio Code Theming support for the dashboards
|
||||
- Front matter AI 🤖
|
||||
|
||||
> **Info**: To enable the experimental features you need to set the `frontMatter.experimental` setting to `true`.
|
||||
|
||||
### 🙏 Exclusive Features for Sponsors
|
||||
|
||||
We're excited to announce a brand new feature exclusively available to sponsors of Front Matter CMS. With this update, we've added Front Matter AI to the project, which provides helpful suggestions for creating new content such as title suggestions and tag/category suggestions.
|
||||
|
||||
> **Important**: To access the Front Matter AI feature, you will need to sign-in ([backers & supports sign-in instructions](https://frontmatter.codes/docs/getting-started#backers-&-supporters)) and set the `frontMatter.sponsors.ai.enabled` setting to `true` and you're good to go! We put it behind a setting to not automatically enable it and let you decide if you want to use it or not.
|
||||
|
||||
If you're not already a sponsor, now is a great time to consider supporting the project. By becoming a sponsor, you not only gain access to exclusive features like Front Matter AI, but also help to support the ongoing development and maintenance of the project. You can become a sponsor by visiting the [GitHub sponsor page](https://github.com/sponsors/estruyf).
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#363](https://github.com/estruyf/vscode-front-matter/issues/363): Multiline support for the `string` field in data view
|
||||
- [#513](https://github.com/estruyf/vscode-front-matter/issues/513): Added support for external UI scripts to add custom HTML on the dashboard elements
|
||||
- [#530](https://github.com/estruyf/vscode-front-matter/issues/530): Implementation of the Front Matter AI 🤖 powered by [mendable.ai](https://mendable.ai)
|
||||
- [#537](https://github.com/estruyf/vscode-front-matter/issues/537): Allow to use the root path `/` as the public folder
|
||||
- [#541](https://github.com/estruyf/vscode-front-matter/issues/541): Added title AI suggestions for GitHub sponsors
|
||||
- [#548](https://github.com/estruyf/vscode-front-matter/issues/548): Project selection support when working in mono-repos or multi-root workspaces
|
||||
- [#550](https://github.com/estruyf/vscode-front-matter/issues/550): Added taxonomy (tags/categories) AI suggestions for GitHub sponsors
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- Added an `unknown` field for uniforms when it has no type defined
|
||||
- [#512](https://github.com/estruyf/vscode-front-matter/issues/512): Added the `jsonc` file association for the `frontMatter.json` file. That way, you can use comments in the file.
|
||||
- [#522](https://github.com/estruyf/vscode-front-matter/issues/522): Configuration support added for [Astro](https://astro.build)
|
||||
- [#523](https://github.com/estruyf/vscode-front-matter/issues/523): Added support for `floating`/`decimal` numbers with a new number field property called `numberOptions`
|
||||
- [#524](https://github.com/estruyf/vscode-front-matter/issues/524): Removed the **Global settings** view from the panel. You can still get it back by configuring a [custom view mode](https://frontmatter.codes/docs/panel#view-modes).
|
||||
- [#535](https://github.com/estruyf/vscode-front-matter/issues/535): Retain the scroll position after selecting a media file
|
||||
- [#538](https://github.com/estruyf/vscode-front-matter/issues/538): Added support to encode emojis in the string field
|
||||
- [#549](https://github.com/estruyf/vscode-front-matter/issues/549): Git submodule support to sync changes
|
||||
- [#554](https://github.com/estruyf/vscode-front-matter/issues/554): When inserting snippets, only the content snippets will be shown
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- [#534](https://github.com/estruyf/vscode-front-matter/issues/534): Moved the `mediaDb.json` file to a `.frontmatter/database` folder instead of the `.frontmatter/content` folder
|
||||
- [#536](https://github.com/estruyf/vscode-front-matter/issues/536): Set the start location from the script to the root of the workspace
|
||||
- [#555](https://github.com/estruyf/vscode-front-matter/issues/555): When generating a content-type from existing content, Front Matter will better detect the type of field
|
||||
- [#556](https://github.com/estruyf/vscode-front-matter/issues/556): Content values are aligned to the type of field
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#518](https://github.com/estruyf/vscode-front-matter/issues/518): Fix an issue where the `YAML` parser adds line breaks to long strings
|
||||
- [#520](https://github.com/estruyf/vscode-front-matter/issues/520): Add the URL protocol to the host on opening the preview if it's missing
|
||||
- [#521](https://github.com/estruyf/vscode-front-matter/issues/521): Fix empty snippets dashboard placeholder
|
||||
- [#526](https://github.com/estruyf/vscode-front-matter/issues/526): Fix card content menu
|
||||
- [#528](https://github.com/estruyf/vscode-front-matter/issues/528): Fix where the `.astro` code section `---` is seen as front matter
|
||||
- [#529](https://github.com/estruyf/vscode-front-matter/issues/529): Fix YAML parsing in Windows which added an extra carriage return
|
||||
- [#531](https://github.com/estruyf/vscode-front-matter/issues/531): Fix prettier update which caused data views to not render list items
|
||||
- [#539](https://github.com/estruyf/vscode-front-matter/issues/539): Fix the override of the default file prefix on content creation
|
||||
- [#543](https://github.com/estruyf/vscode-front-matter/issues/543): Fix JSON schema for script commands
|
||||
- [#547](https://github.com/estruyf/vscode-front-matter/issues/547): Fix setting default value in a hidden group field (`block`)
|
||||
- [#552](https://github.com/estruyf/vscode-front-matter/issues/552): Fix for content retrieval in multi-root workspaces
|
||||
- [#557](https://github.com/estruyf/vscode-front-matter/issues/557): Fix for dropdown of the tag picker
|
||||
|
||||
## [8.3.0] - 2023-02-14 - [Release notes](https://beta.frontmatter.codes/updates/v8.3.0)
|
||||
|
||||
### 🧪 Experimental features
|
||||
|
||||
- Visual Studio Code Theming support for the dashboards
|
||||
|
||||
> **Info**: To enable the experimental features you need to set the `frontMatter.experimental` setting to `true`.
|
||||
|
||||
### ✨ New features
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<path fill="#C5C5C5" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z"/>
|
||||
<path fill="#C5C5C5" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 540 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<path fill="#424242" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z"/>
|
||||
<path fill="#424242" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 540 B |
+41
-35
@@ -33,8 +33,12 @@
|
||||
position: inherit !important;
|
||||
}
|
||||
|
||||
.z-10 { z-index: 10 !important; }
|
||||
.z-20 { z-index: 10 !important; }
|
||||
.z-10 {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
.z-20 {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100% !important;
|
||||
@@ -44,11 +48,12 @@
|
||||
.ext_settings,
|
||||
.git_actions,
|
||||
.initialize_actions {
|
||||
padding: 1rem 1.25rem;
|
||||
padding: 1rem 1.25rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app, .frontmatter {
|
||||
#app,
|
||||
.frontmatter {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@
|
||||
align-items: center;
|
||||
opacity: 0.8;
|
||||
text-align: center;
|
||||
padding: 1rem 1.25rem;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.spinner,
|
||||
@@ -103,7 +108,7 @@
|
||||
padding-bottom: var(--input-margin-vertical);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.frontmatter h3 {
|
||||
@@ -113,7 +118,7 @@
|
||||
.frontmatter p,
|
||||
.frontmatter h4,
|
||||
.frontmatter ul {
|
||||
margin-bottom: .5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.article__tags h3,
|
||||
@@ -131,7 +136,8 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.seo__status__details, .seo__status__keywords {
|
||||
.seo__status__details,
|
||||
.seo__status__keywords {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -162,11 +168,11 @@
|
||||
}
|
||||
|
||||
.article__tags__dropbox.open {
|
||||
border: 1px solid rgba(0, 0, 0, .9);
|
||||
border: 1px solid rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.article__tags ul {
|
||||
color: var(--vscode-dropdown-foreground);
|
||||
color: var(--vscode-dropdown-foreground);
|
||||
background-color: var(--vscode-dropdown-background);
|
||||
}
|
||||
|
||||
@@ -176,16 +182,16 @@
|
||||
}
|
||||
|
||||
.article__tags li:active {
|
||||
color: var(--vscode-button-foreground);
|
||||
color: var(--vscode-button-foreground);
|
||||
background-color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
.article__tags li[aria-selected="true"] {
|
||||
.article__tags li[aria-selected='true'] {
|
||||
color: var(--vscode-button-foreground);
|
||||
background-color: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
.article__tags li[aria-disabled="true"] {
|
||||
.article__tags li[aria-disabled='true'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -224,7 +230,7 @@
|
||||
}
|
||||
|
||||
.ext_link_block svg {
|
||||
margin-right: .5rem;
|
||||
margin-right: 0.5rem;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -258,16 +264,16 @@
|
||||
}
|
||||
|
||||
.ext_link_block button.active {
|
||||
color: var(--vscode-button-foreground);
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
background: var(--vscode-button-background);
|
||||
}
|
||||
.ext_link_block button.active:hover {
|
||||
cursor: pointer;
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
cursor: pointer;
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
.ext_link_block a:hover,
|
||||
.ext_link_block a:active,
|
||||
.ext_link_block a:hover,
|
||||
.ext_link_block a:active,
|
||||
.ext_link_block a:focus,
|
||||
.ext_link_block a:visited {
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
@@ -301,15 +307,15 @@
|
||||
}
|
||||
|
||||
.table__cell__validation .valid {
|
||||
color: #46EC86;
|
||||
color: #46ec86;
|
||||
}
|
||||
|
||||
.table__cell__validation .warning {
|
||||
color: #E6AF2E;
|
||||
color: #e6af2e;
|
||||
}
|
||||
|
||||
.table__cell__validation div span + span {
|
||||
margin-left: .5rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.seo__status__note {
|
||||
@@ -325,7 +331,7 @@
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.field__toggle input {
|
||||
.field__toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
@@ -339,21 +345,21 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.field__toggle__slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
content: '';
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@@ -405,7 +411,7 @@ input:checked + .field__toggle__slider:before {
|
||||
}
|
||||
|
||||
.file_list__items__item {
|
||||
color: var(--vscode-foreground);
|
||||
color: var(--vscode-foreground);
|
||||
font-size: var(--vscode-font-size);
|
||||
font-weight: var(--vscode-font-weight);
|
||||
cursor: pointer;
|
||||
@@ -419,7 +425,7 @@ input:checked + .field__toggle__slider:before {
|
||||
}
|
||||
|
||||
.file_list__items__item:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
color: var(--vscode-list-hoverForeground);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -429,7 +435,7 @@ input:checked + .field__toggle__slider:before {
|
||||
flex-shrink: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: .25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.file_list__items__item span {
|
||||
@@ -454,7 +460,7 @@ input:checked + .field__toggle__slider:before {
|
||||
.sponsor svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: .25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.sponsor a {
|
||||
@@ -471,7 +477,7 @@ input:checked + .field__toggle__slider:before {
|
||||
}
|
||||
|
||||
.sponsor a > span {
|
||||
margin-right: .25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
/* Timepicker */
|
||||
@@ -494,4 +500,4 @@ input:checked + .field__toggle__slider:before {
|
||||
|
||||
.react-datepicker-time__input input {
|
||||
border: 1px solid #aeaeae !important;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
Generated
+1691
-52
File diff suppressed because it is too large
Load Diff
+335
-220
File diff suppressed because it is too large
Load Diff
Generated
+17
-6
@@ -3,7 +3,7 @@ lockfileVersion: 5.4
|
||||
specifiers:
|
||||
'@actions/core': ^1.8.2
|
||||
'@bendera/vscode-webview-elements': 0.6.2
|
||||
'@estruyf/vscode': 0.0.3
|
||||
'@estruyf/vscode': ^1.1.0
|
||||
'@headlessui/react': 1.5.0
|
||||
'@heroicons/react': 1.0.4
|
||||
'@iarna/toml': 2.2.3
|
||||
@@ -96,13 +96,13 @@ specifiers:
|
||||
webpack-bundle-analyzer: ^4.5.0
|
||||
webpack-cli: ^4.9.1
|
||||
webpack-dev-server: ^4.6.0
|
||||
yaml: ^1.10.2
|
||||
yaml: ^2.2.1
|
||||
yawn-yaml: ^1.5.0
|
||||
|
||||
devDependencies:
|
||||
'@actions/core': 1.10.0
|
||||
'@bendera/vscode-webview-elements': 0.6.2
|
||||
'@estruyf/vscode': 0.0.3
|
||||
'@estruyf/vscode': 1.1.0
|
||||
'@headlessui/react': 1.5.0_w7o5yyljkiidx2s2nzb26ottzu
|
||||
'@heroicons/react': 1.0.4_react@17.0.1
|
||||
'@iarna/toml': 2.2.3
|
||||
@@ -195,7 +195,7 @@ devDependencies:
|
||||
webpack-bundle-analyzer: 4.7.0
|
||||
webpack-cli: 4.10.0_y7ttplitmkohdpgkllksfboxwa
|
||||
webpack-dev-server: 4.11.1_pda42hcaj7d62cr262fr632kue
|
||||
yaml: 1.10.2
|
||||
yaml: 2.2.1
|
||||
yawn-yaml: 1.5.0
|
||||
|
||||
packages:
|
||||
@@ -313,10 +313,11 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@estruyf/vscode/0.0.3:
|
||||
resolution: {integrity: sha512-Mak13ZHj/TcyL0snPHsrqELlhpvV1JszZFScCcm1G2dp6UdP4dSk32MlNH5FusGhTIEOI6APE8krldMcZjS/3w==}
|
||||
/@estruyf/vscode/1.1.0:
|
||||
resolution: {integrity: sha512-JOjok+d870IsBPqk/E+KeW9E5ar+slqW8Sae5PtNb1yB8aMudJy9DHV5wQeki/ZwggFYDv+6EPjyzW/71/5yVw==}
|
||||
dependencies:
|
||||
'@types/vscode-webview': 1.57.0
|
||||
uuid: 9.0.0
|
||||
dev: true
|
||||
|
||||
/@headlessui/react/1.5.0_w7o5yyljkiidx2s2nzb26ottzu:
|
||||
@@ -7137,6 +7138,11 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/uuid/9.0.0:
|
||||
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/uvu/0.5.6:
|
||||
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7542,6 +7548,11 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dev: true
|
||||
|
||||
/yaml/2.2.1:
|
||||
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/yargs-parser/20.2.4:
|
||||
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
@@ -226,7 +226,7 @@ export class Article {
|
||||
|
||||
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
filePrefix = ArticleHelper.getFilePrefix(editor.document.uri.fsPath, contentType);
|
||||
filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType);
|
||||
|
||||
const titleField = 'title';
|
||||
const articleTitle: string = article.data[titleField];
|
||||
|
||||
@@ -20,13 +20,15 @@ export class Cache {
|
||||
await Extension.getInstance().setState(key, data, type);
|
||||
}
|
||||
|
||||
private static async clear() {
|
||||
public static async clear(showNotification: boolean = true) {
|
||||
const ext = Extension.getInstance();
|
||||
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, undefined, 'workspace', true);
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Index, undefined, 'workspace', true);
|
||||
await ext.setState(ExtensionState.Settings.Extends, undefined, 'workspace', true);
|
||||
|
||||
Notifications.info('Cache cleared');
|
||||
if (showNotification) {
|
||||
Notifications.info('Cache cleared');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { TelemetryEvent, PreviewCommands, SETTING_EXPERIMENTAL } from './../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, window } from 'vscode';
|
||||
import { Extension, Settings } from '../helpers';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
|
||||
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 - Ask me anything',
|
||||
{
|
||||
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;
|
||||
|
||||
webView.webview.onDidReceiveMessage((message) => {
|
||||
switch (message.command) {
|
||||
case PreviewCommands.toVSCode.open:
|
||||
if (message.data) {
|
||||
commands.executeCommand('vscode.open', message.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardFile = 'dashboardWebView.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 extensionUri = ext.extensionPath;
|
||||
|
||||
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 scriptUri = '';
|
||||
if (isProd) {
|
||||
scriptUri = webView.webview
|
||||
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
|
||||
.toString();
|
||||
} else {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
Telemetry.send(TelemetryEvent.openChatbot);
|
||||
}
|
||||
}
|
||||
+34
-11
@@ -2,10 +2,11 @@ import {
|
||||
SETTING_DASHBOARD_OPENONSTART,
|
||||
CONTEXT,
|
||||
ExtensionState,
|
||||
SETTING_EXPERIMENTAL
|
||||
SETTING_EXPERIMENTAL,
|
||||
SETTING_EXTENSIBILITY_SCRIPTS
|
||||
} from '../constants';
|
||||
import { join } from 'path';
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode';
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from 'vscode';
|
||||
import { Logger, Settings as SettingsHelper } from '../helpers';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
} from '../listeners/dashboard';
|
||||
import { MediaListener as PanelMediaListener } from '../listeners/panel';
|
||||
import { GitListener, ModeListener } from '../listeners/general';
|
||||
import { Folders } from './Folders';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -77,7 +79,7 @@ export class Dashboard {
|
||||
if (hasData) {
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.viewData,
|
||||
data: Dashboard.viewData
|
||||
payload: Dashboard.viewData
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,7 +121,8 @@ export class Dashboard {
|
||||
ViewColumn.One,
|
||||
{
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true
|
||||
retainContextWhenHidden: true,
|
||||
enableCommandUris: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -142,7 +145,7 @@ export class Dashboard {
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.viewData,
|
||||
data: null
|
||||
payload: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,7 +193,7 @@ export class Dashboard {
|
||||
* Post data to the dashboard
|
||||
* @param msg
|
||||
*/
|
||||
public static postWebviewMessage(msg: { command: DashboardCommand; data?: unknown }) {
|
||||
public static postWebviewMessage(msg: { command: DashboardCommand; payload?: unknown }) {
|
||||
if (Dashboard.isDisposed) {
|
||||
return;
|
||||
}
|
||||
@@ -227,6 +230,20 @@ export class Dashboard {
|
||||
|
||||
// Get experimental setting
|
||||
const experimental = SettingsHelper.get(SETTING_EXPERIMENTAL);
|
||||
const extensibilityScripts = SettingsHelper.get<string[]>(SETTING_EXTENSIBILITY_SCRIPTS) || [];
|
||||
|
||||
const scriptsToLoad: string[] = [];
|
||||
if (experimental) {
|
||||
for (const script of extensibilityScripts) {
|
||||
if (script.startsWith('https://')) {
|
||||
scriptsToLoad.push(script);
|
||||
} else {
|
||||
const absScriptPath = Folders.getAbsFilePath(script);
|
||||
const scriptUri = webView.asWebviewUri(Uri.file(absScriptPath));
|
||||
scriptsToLoad.push(scriptUri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
@@ -238,10 +255,10 @@ export class Dashboard {
|
||||
} 'self' 'unsafe-inline' https://*`,
|
||||
`script-src ${
|
||||
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
} 'unsafe-eval'`,
|
||||
`style-src ${webView.cspSource} 'self' 'unsafe-inline'`,
|
||||
} 'unsafe-eval' https://*`,
|
||||
`style-src ${webView.cspSource} 'self' 'unsafe-inline' https://*`,
|
||||
`font-src ${webView.cspSource}`,
|
||||
`connect-src https://o1022172.ingest.sentry.io ${
|
||||
`connect-src https://o1022172.ingest.sentry.io https://* ${
|
||||
isProd
|
||||
? ``
|
||||
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
@@ -265,9 +282,15 @@ export class Dashboard {
|
||||
version.usedVersion ? '' : `data-showWelcome="true"`
|
||||
} ${experimental ? `data-experimental="${experimental}"` : ''} ></div>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
|
||||
${(scriptsToLoad || [])
|
||||
.map((script) => {
|
||||
return `<script type="module" src="${script}" nonce="${nonce}"></script>`;
|
||||
})
|
||||
.join('')}
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
+52
-8
@@ -174,12 +174,20 @@ export class Folders {
|
||||
public static getStaticFolderRelativePath(): string | undefined {
|
||||
let staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
|
||||
|
||||
if (staticFolder && staticFolder.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
staticFolder = Folders.getAbsFilePath(staticFolder);
|
||||
if (
|
||||
staticFolder &&
|
||||
(staticFolder.includes(WORKSPACE_PLACEHOLDER) ||
|
||||
staticFolder === '/' ||
|
||||
staticFolder === './')
|
||||
) {
|
||||
staticFolder =
|
||||
staticFolder === '/' || staticFolder === './'
|
||||
? Folders.getAbsFilePath('[[workspace]]')
|
||||
: Folders.getAbsFilePath(staticFolder);
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const relativePath = relative(parseWinPath(wsFolder.fsPath), parseWinPath(staticFolder));
|
||||
return relativePath;
|
||||
return relativePath === '' ? '/' : relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +271,7 @@ export class Folders {
|
||||
|
||||
for (const folder of folders) {
|
||||
try {
|
||||
const folderPath = parseWinPath(folder.path);
|
||||
let projectStart = parseWinPath(folder.path).replace(wsFolder, '');
|
||||
|
||||
if (typeof projectStart === 'string') {
|
||||
@@ -282,7 +291,10 @@ export class Folders {
|
||||
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
|
||||
}
|
||||
|
||||
const foundFiles = await workspace.findFiles(filePath, '**/node_modules/**');
|
||||
let foundFiles = await workspace.findFiles(filePath, '**/node_modules/**');
|
||||
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
|
||||
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
|
||||
|
||||
files = [...files, ...foundFiles];
|
||||
}
|
||||
|
||||
@@ -383,10 +395,22 @@ export class Folders {
|
||||
public static async update(folders: ContentFolder[]) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const folderDetails = folders.map((folder) => ({
|
||||
...folder,
|
||||
path: Folders.relWsFolder(folder, wsFolder)
|
||||
}));
|
||||
const folderDetails = folders
|
||||
.map((folder) => {
|
||||
const detail = {
|
||||
...folder,
|
||||
path: Folders.relWsFolder(folder, wsFolder)
|
||||
};
|
||||
|
||||
if (detail['$schema'] || detail.extended) {
|
||||
return null;
|
||||
}
|
||||
|
||||
delete detail.originalPath;
|
||||
|
||||
return detail;
|
||||
})
|
||||
.filter((folder) => folder !== null);
|
||||
|
||||
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
|
||||
|
||||
@@ -407,6 +431,26 @@ export class Folders {
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the absolute folder path
|
||||
* @param filePath
|
||||
* @returns
|
||||
*/
|
||||
public static getAbsFolderPath(folderPath: string): string {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
let absPath = '';
|
||||
if (folderPath.includes(WORKSPACE_PLACEHOLDER)) {
|
||||
absPath = folderPath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
|
||||
} else {
|
||||
absPath = join(parseWinPath(wsFolder?.fsPath || ''), folderPath);
|
||||
}
|
||||
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
return parseWinPath(absPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the absolute URL for the workspace
|
||||
* @param folder
|
||||
|
||||
@@ -161,7 +161,8 @@ export class Preview {
|
||||
light: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-light.svg'))
|
||||
};
|
||||
|
||||
const localhostUrl = await env.asExternalUri(Uri.parse(settings.host));
|
||||
const crntUrl = settings.host.startsWith('http') ? settings.host : `http://${settings.host}`;
|
||||
const localhostUrl = await env.asExternalUri(Uri.parse(crntUrl));
|
||||
|
||||
const cspSource = webView.webview.cspSource;
|
||||
|
||||
|
||||
+28
-2
@@ -1,12 +1,13 @@
|
||||
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { workspace, Uri } from 'vscode';
|
||||
import { workspace, Uri, commands, window } from 'vscode';
|
||||
import { join } from 'path';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Template } from './Template';
|
||||
import { Folders } from './Folders';
|
||||
import { FrameworkDetector, Logger, MediaLibrary, Settings } from '../helpers';
|
||||
import { Extension, FrameworkDetector, Logger, MediaLibrary, Settings } from '../helpers';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
SETTING_CONTENT_DEFAULT_FILETYPE,
|
||||
SETTING_TAXONOMY_CONTENT_TYPES,
|
||||
TelemetryEvent
|
||||
@@ -28,6 +29,13 @@ categories: []
|
||||
---
|
||||
`;
|
||||
|
||||
public static registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(commands.registerCommand(COMMAND_NAME.switchProject, Project.switchProject));
|
||||
}
|
||||
|
||||
public static isInitialized() {
|
||||
const hasProjectFile = Settings.hasProjectFile();
|
||||
// If it has a project file, initialize the media library
|
||||
@@ -74,6 +82,24 @@ categories: []
|
||||
}
|
||||
}
|
||||
|
||||
public static async switchProject() {
|
||||
const projects = Settings.getProjects();
|
||||
const project = await window.showQuickPick(
|
||||
projects.map((p) => p.name),
|
||||
{
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true,
|
||||
title: 'Select a project to switch to'
|
||||
}
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsListener.switchProject(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the templates folder + sample if needed
|
||||
* @param sampleTemplate
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IChatIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ChatIcon: React.FunctionComponent<IChatIconProps> = ({ className }: React.PropsWithChildren<IChatIconProps>) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={className || 'h-4 w-4'} viewBox="0 0 32 32">
|
||||
<path fill="currentcolor" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z" />
|
||||
<path fill="currentcolor" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -5,11 +5,13 @@ export { AutoFieldProps } from 'uniforms';
|
||||
import BoolField from './BoolField';
|
||||
import DateField from './DateField';
|
||||
import ListField from './ListField';
|
||||
import LongTextField from './LongTextField';
|
||||
import NestField from './NestField';
|
||||
import NumField from './NumField';
|
||||
import RadioField from './RadioField';
|
||||
import SelectField from './SelectField';
|
||||
import TextField from './TextField';
|
||||
import UnknownField from './UnknownField';
|
||||
|
||||
const AutoField = createAutoField((props) => {
|
||||
if (props.allowedValues) {
|
||||
@@ -28,7 +30,12 @@ const AutoField = createAutoField((props) => {
|
||||
case Object:
|
||||
return NestField;
|
||||
case String:
|
||||
if (props["multiline"]) {
|
||||
return LongTextField;
|
||||
}
|
||||
return TextField;
|
||||
default:
|
||||
return UnknownField;
|
||||
}
|
||||
|
||||
return invariant(false, 'Unsupported field type: %s', props.fieldType);
|
||||
|
||||
@@ -7,6 +7,7 @@ import ListItemField from './ListItemField';
|
||||
|
||||
import './ListField.css';
|
||||
import { LabelField } from './LabelField';
|
||||
import { AutoField } from 'uniforms-unstyled';
|
||||
|
||||
export type ListFieldProps = HTMLFieldProps<
|
||||
unknown[],
|
||||
@@ -27,14 +28,13 @@ function List({
|
||||
<LabelField label={label} id={props.id} required={props.required} />
|
||||
|
||||
{value?.map((item, itemIndex) =>
|
||||
Children.map(children as React.ReactElement[], (child: React.ReactElement, childIndex) =>
|
||||
Children.map(children as React.ReactElement[], (child: React.ReactElement<any>, childIndex) =>
|
||||
isValidElement(child)
|
||||
? cloneElement(child, {
|
||||
key: `${itemIndex}-${childIndex}`,
|
||||
// name: '',
|
||||
// name: (child.props.name || '').replace('$', '' + itemIndex),
|
||||
...itemProps
|
||||
})
|
||||
? cloneElement(child as React.ReactElement<any>, {
|
||||
key: `${itemIndex}-${childIndex}`,
|
||||
name: ((child?.props as any)?.name || "").replace('$', '' + itemIndex),
|
||||
...itemProps,
|
||||
})
|
||||
: child
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type LongTextFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
@@ -22,7 +23,7 @@ function LongText({
|
||||
}: LongTextFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
{label && <label htmlFor={id}>{label}</label>}
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<textarea
|
||||
disabled={disabled}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Ref } from 'react';
|
||||
import * as React from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type UnknownFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
>;
|
||||
|
||||
function UnknownField({
|
||||
autoComplete,
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
type,
|
||||
value,
|
||||
...props
|
||||
}: UnknownFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<div className={`text-[var(--vscode-errorForeground)]`}>Unknown field</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
UnknownField.defaultProps = { type: 'text' };
|
||||
|
||||
export default connectField<UnknownFieldProps>(UnknownField, { kind: 'leaf' });
|
||||
@@ -28,6 +28,7 @@ export const COMMAND_NAME = {
|
||||
initTemplate: getCommandName('initTemplate'),
|
||||
collapseSections: getCommandName('collapseSections'),
|
||||
preview: getCommandName('preview'),
|
||||
chatbot: getCommandName('chatbot'),
|
||||
dashboard: getCommandName('dashboard'),
|
||||
dashboardMedia: getCommandName('dashboard.media'),
|
||||
dashboardSnippets: getCommandName('dashboard.snippets'),
|
||||
@@ -64,6 +65,9 @@ export const COMMAND_NAME = {
|
||||
addMissingFields: getCommandName('contenttype.addMissingFields'),
|
||||
setContentType: getCommandName('contenttype.setContentType'),
|
||||
|
||||
// Project
|
||||
switchProject: getCommandName('project.switch'),
|
||||
|
||||
// Git
|
||||
gitSync: getCommandName('git.sync'),
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ export const ExtensionState = {
|
||||
SettingPromoted: `frontMatter:Settings:Promoted`,
|
||||
MoveTemplatesFolder: `frontMatter:Templates:Move`,
|
||||
|
||||
Project: {
|
||||
current: `frontMatter:Project:current`
|
||||
},
|
||||
|
||||
Dashboard: {
|
||||
Contents: {
|
||||
Sorting: `frontMatter:Dashboard:Contents:Sorting`
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
export const FrameworkDetectors = [
|
||||
{
|
||||
framework: {
|
||||
name: 'astro',
|
||||
dist: 'dist',
|
||||
static: 'public',
|
||||
build: 'npm run build',
|
||||
server: 'http://localhost:3000'
|
||||
},
|
||||
requiredFiles: ['astro.config.mjs'],
|
||||
requiredDependencies: ['astro'],
|
||||
commands: {
|
||||
start: 'npm run dev'
|
||||
}
|
||||
},
|
||||
{
|
||||
framework: {
|
||||
name: 'gatsby',
|
||||
dist: 'public',
|
||||
static: 'static',
|
||||
build: 'gatsby build'
|
||||
build: 'gatsby build',
|
||||
server: 'http://localhost:8000'
|
||||
},
|
||||
requiredFiles: ['gatsby-config.js'],
|
||||
requiredDependencies: ['gatsby'],
|
||||
@@ -17,7 +32,8 @@ export const FrameworkDetectors = [
|
||||
name: 'hugo',
|
||||
dist: 'public',
|
||||
static: 'static',
|
||||
build: 'hugo'
|
||||
build: 'hugo',
|
||||
server: 'http://localhost:1313'
|
||||
},
|
||||
requiredFiles: ['config.toml', 'config.yaml', 'config.yml'],
|
||||
commands: {
|
||||
@@ -29,7 +45,8 @@ export const FrameworkDetectors = [
|
||||
name: 'next',
|
||||
dist: '.next',
|
||||
static: 'public',
|
||||
build: 'next build'
|
||||
build: 'next build',
|
||||
server: 'http://localhost:3000'
|
||||
},
|
||||
requiredFiles: ['next.config.js'],
|
||||
requiredDependencies: ['next'],
|
||||
@@ -42,7 +59,8 @@ export const FrameworkDetectors = [
|
||||
name: 'nuxt',
|
||||
dist: 'dist',
|
||||
static: 'static',
|
||||
build: 'nuxt'
|
||||
build: 'nuxt',
|
||||
server: 'http://localhost:3000'
|
||||
},
|
||||
requiredFiles: ['nuxt.config.js'],
|
||||
requiredDependencies: ['nuxt'],
|
||||
@@ -55,7 +73,8 @@ export const FrameworkDetectors = [
|
||||
name: 'jekyll',
|
||||
dist: '_site',
|
||||
static: 'assets',
|
||||
build: 'bundle exec jekyll build'
|
||||
build: 'bundle exec jekyll build',
|
||||
server: 'http://localhost:4000'
|
||||
},
|
||||
requiredFiles: ['Gemfile'],
|
||||
requiredDependencies: ['jekyll'],
|
||||
@@ -68,7 +87,8 @@ export const FrameworkDetectors = [
|
||||
name: 'docusaurus',
|
||||
dist: 'build',
|
||||
static: 'static',
|
||||
build: 'npx docusaurus build'
|
||||
build: 'npx docusaurus build',
|
||||
server: 'http://localhost:3000'
|
||||
},
|
||||
requiredFiles: ['docusaurus.config.js'],
|
||||
requiredDependencies: ['@docusaurus/core'],
|
||||
@@ -80,7 +100,8 @@ export const FrameworkDetectors = [
|
||||
framework: {
|
||||
name: '11ty',
|
||||
dist: '_site',
|
||||
build: 'npx @11ty/eleventy'
|
||||
build: 'npx @11ty/eleventy',
|
||||
server: 'http://localhost:8080'
|
||||
},
|
||||
requiredDependencies: ['@11ty/eleventy'],
|
||||
commands: {
|
||||
@@ -91,7 +112,8 @@ export const FrameworkDetectors = [
|
||||
framework: {
|
||||
name: 'hexo',
|
||||
dist: 'public',
|
||||
build: 'npx hexo-cli generate'
|
||||
build: 'npx hexo-cli generate',
|
||||
server: 'http://localhost:4000'
|
||||
},
|
||||
requiredFiles: ['_config.js'],
|
||||
requiredDependencies: ['hexo'],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const LocalStore = {
|
||||
rootFolder: '.frontmatter',
|
||||
contentFolder: 'content',
|
||||
databaseFolder: 'database',
|
||||
templatesFolder: 'templates',
|
||||
mediaDatabaseFile: 'mediaDb.json'
|
||||
};
|
||||
|
||||
@@ -28,6 +28,9 @@ export const TelemetryEvent = {
|
||||
updateMediaMetadata: 'updateMediaMetadata',
|
||||
openExplorerView: 'openExplorerView',
|
||||
|
||||
// Chatbot
|
||||
openChatbot: 'openChatbot',
|
||||
|
||||
// Content types
|
||||
generateContentType: 'generateContentType',
|
||||
addMissingFields: 'addMissingFields',
|
||||
|
||||
@@ -12,5 +12,7 @@ export const CONTEXT = {
|
||||
isSnippetsDashboardEnabled: 'frontMatter:dashboard:snippets:enabled',
|
||||
isDataDashboardEnabled: 'frontMatter:dashboard:data:enabled',
|
||||
|
||||
isGitEnabled: 'frontMatter:git:enabled'
|
||||
isGitEnabled: 'frontMatter:git:enabled',
|
||||
|
||||
projectSwitchEnabled: 'frontMatter:project:switch:enabled',
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export const EXTENSION_NAME = 'Front Matter';
|
||||
export const EXTENSION_NAME = '𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲';
|
||||
|
||||
export const CONFIG_KEY = 'frontMatter';
|
||||
|
||||
export const SETTING_EXPERIMENTAL = 'experimental';
|
||||
|
||||
export const SETTING_EXTENSIBILITY_SCRIPTS = 'extensibility.scripts';
|
||||
|
||||
export const SETTING_EXTENDS = 'extends';
|
||||
|
||||
export const SETTING_GLOBAL_NOTIFICATIONS = 'global.notifications';
|
||||
@@ -87,6 +89,20 @@ export const SETTING_SITE_BASEURL = 'site.baseURL';
|
||||
|
||||
export const SETTING_GIT_ENABLED = 'git.enabled';
|
||||
export const SETTING_GIT_COMMIT_MSG = 'git.commitMessage';
|
||||
export const SETTING_GIT_SUBMODULE_PULL = 'git.submodule.pull';
|
||||
export const SETTING_GIT_SUBMODULE_PUSH = 'git.submodule.push';
|
||||
export const SETTING_GIT_SUBMODULE_BRANCH = 'git.submodule.branch';
|
||||
export const SETTING_GIT_SUBMODULE_FOLDER = 'git.submodule.folder';
|
||||
|
||||
/**
|
||||
* Sponsors only settings
|
||||
*/
|
||||
export const SETTING_SPONSORS_AI_ENABLED = 'sponsors.ai.enabled';
|
||||
|
||||
/**
|
||||
* Project override support
|
||||
*/
|
||||
export const SETTING_PROJECTS = 'projects';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
|
||||
@@ -5,6 +5,9 @@ export enum DashboardMessage {
|
||||
getMode = 'getMode',
|
||||
showWarning = 'showWarning',
|
||||
|
||||
// Project switching
|
||||
switchProject = 'switchProject',
|
||||
|
||||
// Welcome view
|
||||
initializeProject = 'initializeProject',
|
||||
setFramework = 'setFramework',
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { TaxonomyView } from './TaxonomyView';
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '..';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { UnknownView } from './UnknownView';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { ErrorView } from './ErrorView';
|
||||
@@ -30,6 +30,7 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
const { loading, pages, settings } = useMessages();
|
||||
const view = useRecoilValue(DashboardViewSelector);
|
||||
const mode = useRecoilValue(ModeAtom);
|
||||
const [isDevMode, setIsDevMode] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
useDarkMode();
|
||||
|
||||
@@ -60,6 +61,12 @@ export const App: React.FunctionComponent<IAppProps> = ({
|
||||
navigate(routePaths[view]);
|
||||
}, [view]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.fmExternal && window.fmExternal.isDevelopment) {
|
||||
setIsDevMode(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!settings) {
|
||||
return <Spinner />;
|
||||
}
|
||||
@@ -86,6 +93,27 @@ Stack: ${componentStack}`
|
||||
}}
|
||||
>
|
||||
<main className={`h-full w-full`}>
|
||||
{
|
||||
isDevMode && (
|
||||
<div className="relative p-2 flex justify-center items-center bg-[var(--vscode-statusBar-debuggingBackground)] text-[var(--vscode-statusBar-debuggingForeground)]">
|
||||
<span className='absolute left-2'>Development mode</span>
|
||||
|
||||
<a
|
||||
className="ml-2 px-2 hover:text-[var(--vscode-statusBar-debuggingForeground)] hover:bg-[var(--vscode-statusBarItem-hoverBackground)] hover:outline-none focus:outline-none"
|
||||
href={`command:workbench.action.webview.reloadWebviewAction`}
|
||||
title="Reload the dashboard">
|
||||
Reload
|
||||
</a>
|
||||
<a
|
||||
className="ml-2 px-2 hover:text-[var(--vscode-statusBar-debuggingForeground)] hover:bg-[var(--vscode-statusBarItem-hoverBackground)] hover:outline-none focus:outline-none"
|
||||
href={`command:workbench.action.webview.openDeveloperTools`}
|
||||
title="Open DevTools">
|
||||
DevTools
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Routes>
|
||||
<Route path={routePaths.welcome} element={<WelcomeScreen settings={settings} />} />
|
||||
<Route
|
||||
@@ -104,6 +132,6 @@ Stack: ${componentStack}`
|
||||
<Route path={`*`} element={<UnknownView />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</ErrorBoundary>
|
||||
</ErrorBoundary >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Feedback } from './Feedback';
|
||||
|
||||
export interface IAnswerProps {
|
||||
answer: string;
|
||||
answerId: number;
|
||||
sources: string[];
|
||||
}
|
||||
|
||||
export const Answer: React.FunctionComponent<IAnswerProps> = ({
|
||||
answer,
|
||||
answerId,
|
||||
sources,
|
||||
}: React.PropsWithChildren<IAnswerProps>) => {
|
||||
if (!answer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className='answer'>
|
||||
<div className='text-lg flex justify-between'>
|
||||
<p>Answer</p>
|
||||
|
||||
<Feedback answerId={answerId} />
|
||||
</div>
|
||||
|
||||
<ReactMarkdown children={answer} remarkPlugins={[remarkGfm]} />
|
||||
|
||||
{
|
||||
sources && sources.length > 0 && (
|
||||
<div>
|
||||
<p className='text-lg'>Resources</p>
|
||||
<ul className={`space-y-2 list-disc pl-4`}>
|
||||
{sources.map((source, idx) => (
|
||||
<li key={`source-${idx}`} className={`text-sm`}>
|
||||
<a className={`text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]`} href={source} target="_blank" rel="noreferrer">{source}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className={`-mx-4 -mb-4 py-2 px-4 bg-[var(--vscode-sideBar-background)] text-[var(--vscode-sideBarTitle-foreground)] text-xs rounded-b`} style={{
|
||||
fontFamily: "var(--vscode-editor-font-family)",
|
||||
}}>
|
||||
Warning: Anwers might be wrong. In case of doubt, please consult the docs.
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Header } from './Header';
|
||||
import { Chatbox } from './Chatbox';
|
||||
import { Loader } from './Loader';
|
||||
import { QuestionAnswer } from './QuestionAnswer';
|
||||
import { Placeholder } from './Placeholder';
|
||||
import { useSettingsContext } from '../../providers/SettingsProvider';
|
||||
import { AiInitResponse } from './models/AiInitResponse';
|
||||
|
||||
export interface IChatbotProps { }
|
||||
|
||||
export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.PropsWithChildren<IChatbotProps>) => {
|
||||
const { aiUrl } = useSettingsContext();
|
||||
const [company, setCompany] = React.useState<string | undefined>(undefined);
|
||||
const [chatId, setChatId] = React.useState<number | undefined>(undefined);
|
||||
const [questions, setQuestions] = React.useState<string[]>([]);
|
||||
const [answers, setAnswers] = React.useState<string[]>([]);
|
||||
const [answerIds, setAnswerIds] = React.useState<number[]>([]);
|
||||
const [sources, setSources] = React.useState<string[][]>([]);
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
|
||||
const init = async () => {
|
||||
setLoading(true);
|
||||
const initResponse = await fetch(`${aiUrl}/api/ai-init`);
|
||||
|
||||
if (!initResponse.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initJson: AiInitResponse = await initResponse.json();
|
||||
|
||||
if (!initJson.company || !initJson.chatId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCompany(initJson.company);
|
||||
setChatId(initJson.chatId);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const onTrigger = useCallback(async (message: string) => {
|
||||
callChatbot(message, questions.length);
|
||||
}, [questions, company, chatId]);
|
||||
|
||||
const callChatbot = useCallback(async (message, questionLenght) => {
|
||||
const nrOfQuestions = questionLenght + 1;
|
||||
setLoading(true);
|
||||
|
||||
setQuestions(prev => [...prev, message])
|
||||
setAnswers(prev => [...prev, ""])
|
||||
setSources(prev => [...prev, []])
|
||||
setAnswerIds(prev => [...prev, 0])
|
||||
|
||||
if (!company || !chatId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${aiUrl}/api/ai-chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'accept': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company,
|
||||
chatId,
|
||||
question: message,
|
||||
}),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data: {
|
||||
answer: string;
|
||||
answerId: number;
|
||||
sources: string[];
|
||||
} = await response.json();
|
||||
|
||||
if (!data.answer || !data.answerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSources(prev => {
|
||||
const metadata = [...new Set(data.sources || [])] as string[]
|
||||
const crntSources: string[][] = Object.assign([], prev)
|
||||
if (crntSources.length === nrOfQuestions) {
|
||||
crntSources[nrOfQuestions - 1] = metadata;
|
||||
} else {
|
||||
crntSources.push(metadata);
|
||||
}
|
||||
|
||||
return crntSources;
|
||||
});
|
||||
|
||||
setAnswerIds(prev => {
|
||||
const crntAnswerIds: number[] = Object.assign([], prev)
|
||||
if (crntAnswerIds.length === nrOfQuestions) {
|
||||
crntAnswerIds[nrOfQuestions - 1] = data.answerId;
|
||||
} else {
|
||||
crntAnswerIds.push(data.answerId);
|
||||
}
|
||||
|
||||
return crntAnswerIds;
|
||||
});
|
||||
|
||||
setAnswers(prev => {
|
||||
const crntAnswers: string[] = Object.assign([], prev)
|
||||
if (crntAnswers.length === nrOfQuestions) {
|
||||
crntAnswers[nrOfQuestions - 1] = data.answer;
|
||||
} else {
|
||||
crntAnswers.push(data.answer);
|
||||
}
|
||||
|
||||
return crntAnswers;
|
||||
});
|
||||
}, [company, chatId]);
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col overflow-x-hidden h-full w-full items-center overflow-hidden`}>
|
||||
<Header />
|
||||
|
||||
<div className='w-full h-[1px] bg-[var(--frontmatter-border)] -mx-4 mb-4'></div>
|
||||
|
||||
<main className={`qa__bot flex-grow w-full max-w-xl overflow-y-auto overflow-x-hidden`}>
|
||||
{
|
||||
questions && questions.length > 0 ? questions.map((question, idx) => (
|
||||
<QuestionAnswer
|
||||
key={idx}
|
||||
question={question}
|
||||
answer={answers[idx]}
|
||||
answerId={answerIds[idx]}
|
||||
sources={sources[idx]} />
|
||||
)) : (
|
||||
<Placeholder>
|
||||
{loading ? <div className='dots'>Assistent is getting ready</div> : `I'm ready, what do you want to know?`}
|
||||
</Placeholder>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
loading && (questions && questions.length > 0) && <Loader />
|
||||
}
|
||||
</main>
|
||||
|
||||
<Chatbox isLoading={loading} onTrigger={onTrigger} />
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=chatbot" alt="Chatbot metrics" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { PaperAirplaneIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
|
||||
export interface IChatboxProps {
|
||||
isLoading: boolean;
|
||||
onTrigger: (message: string) => void;
|
||||
}
|
||||
|
||||
export const Chatbox: React.FunctionComponent<IChatboxProps> = ({ isLoading, onTrigger }: React.PropsWithChildren<IChatboxProps>) => {
|
||||
const [message, setMessage] = React.useState<string>("");
|
||||
const [isFocussed, setIsFocussed] = React.useState<boolean>(false);
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
const callAi = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
setMessage("")
|
||||
}, 0);
|
||||
|
||||
onTrigger(message);
|
||||
}, [message]);
|
||||
|
||||
return (
|
||||
<footer className={`w-full mt-4 bg-[var(--vscode-input-background)] border-t ${isFocussed ? "border-[var(--vscode-focusBorder)]" : "border-[var(--frontmatter-border)]"}`}>
|
||||
<div className={`w-full max-w-xl relative pb-4 mx-auto`}>
|
||||
<div className='chatbox px-4'>
|
||||
<textarea
|
||||
className={`
|
||||
resize-none w-full outline-none border-0 pr-8
|
||||
${getColors(
|
||||
'focus:outline-none border-gray-300 text-vulcan-500',
|
||||
'border-transparent bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] focus:outline-none focus:border-transparent'
|
||||
)}`}
|
||||
placeholder='How can I configure Front Matter?'
|
||||
autoFocus={true}
|
||||
value={message}
|
||||
cols={30}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
callAi();
|
||||
}
|
||||
}}
|
||||
onFocus={() => setIsFocussed(true)}
|
||||
onBlur={() => setIsFocussed(false)}
|
||||
/>
|
||||
|
||||
<button
|
||||
className={`absolute right-6 top-3 text-[var(--frontmatter-button-background)] hover:text-[var(--frontmatter-button-hoverBackground)] disabled:opacity-50 disabled:text-[var(--vscode-disabledForeground)]`}
|
||||
type='button'
|
||||
disabled={message.trim().length === 0 || isLoading}
|
||||
onClick={callAi}
|
||||
>
|
||||
<PaperAirplaneIcon className='h-6 w-6 rotate-90' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
import { ThumbDownIcon, ThumbUpIcon } from '@heroicons/react/outline';
|
||||
import { ThumbDownIcon as ThumbDownSolidIcon, ThumbUpIcon as ThumbUpSolidIcon } from '@heroicons/react/solid';
|
||||
import { useCallback } from 'react';
|
||||
import { useSettingsContext } from '../../providers/SettingsProvider';
|
||||
|
||||
export interface IFeedbackProps {
|
||||
answerId: number;
|
||||
}
|
||||
|
||||
export const Feedback: React.FunctionComponent<IFeedbackProps> = ({
|
||||
answerId
|
||||
}: React.PropsWithChildren<IFeedbackProps>) => {
|
||||
const { aiUrl } = useSettingsContext();
|
||||
const [isUpVoted, setIsUpVoted] = React.useState<boolean>(false);
|
||||
const [isDownVoted, setIsDownVoted] = React.useState<boolean>(false);
|
||||
|
||||
const onUpVote = useCallback(() => {
|
||||
setIsUpVoted(true)
|
||||
setIsDownVoted(false)
|
||||
callVote(true)
|
||||
}, []);
|
||||
|
||||
const onDownVote = useCallback(() => {
|
||||
setIsDownVoted(true)
|
||||
setIsUpVoted(false)
|
||||
callVote(false)
|
||||
}, []);
|
||||
|
||||
const callVote = useCallback(async (vote: boolean) => {
|
||||
await fetch(`${aiUrl}/api/ai-feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
answerId,
|
||||
vote: vote ? 1 : -1,
|
||||
})
|
||||
})
|
||||
}, [answerId])
|
||||
|
||||
if (!answerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='text-lg flex gap-4'>
|
||||
<button
|
||||
className='hover:text-[var(--vscode-textLink-activeForeground)]'
|
||||
onClick={onUpVote}>
|
||||
{
|
||||
isUpVoted ? (
|
||||
<ThumbUpSolidIcon className='h-4 w-4 text-[var(--vscode-textLink-foreground)]' />
|
||||
) : (
|
||||
<ThumbUpIcon className='h-4 w-4' />
|
||||
)
|
||||
}
|
||||
</button>
|
||||
<button
|
||||
className='hover:text-[var(--vscode-textLink-activeForeground)]'
|
||||
onClick={() => onDownVote()}>
|
||||
{
|
||||
isDownVoted ? (
|
||||
<ThumbDownSolidIcon className='h-4 w-4 text-[var(--vscode-textLink-foreground)]' />
|
||||
) : (
|
||||
<ThumbDownIcon className='h-4 w-4' />
|
||||
)
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { ChatIcon } from '../../../components/icons/ChatIcon';
|
||||
|
||||
export interface IHeaderProps { }
|
||||
|
||||
export const Header: React.FunctionComponent<IHeaderProps> = (props: React.PropsWithChildren<IHeaderProps>) => {
|
||||
return (
|
||||
<header className={`w-full max-w-xl m-4 px-4`}>
|
||||
<h1 className='text-2xl flex items-center space-x-4'>
|
||||
<ChatIcon className='h-6 w-6' />
|
||||
<span>Ask Front Matter AI</span>
|
||||
</h1>
|
||||
<h2
|
||||
className='mt-2 text-sm text-[var(--frontmatter-secondary-text)]'
|
||||
style={{
|
||||
fontFamily: "var(--vscode-editor-font-family)",
|
||||
}}
|
||||
>
|
||||
Our AI, powered by <a className={`text-[var(--vscode-textLink-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]`} href={`https://www.mendable.ai/`} title={`mendable.ai`}>mendable.ai</a>, has processed the documentation and can assist you with any queries regarding Front Matter. Go ahead and ask away!
|
||||
</h2>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ILoaderProps { }
|
||||
|
||||
export const Loader: React.FunctionComponent<ILoaderProps> = (props: React.PropsWithChildren<ILoaderProps>) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-4 flex items-center justify-center space-x-2 animate-pulse">
|
||||
<div className="w-4 h-4 bg-[var(--frontmatter-button-background)] rounded-full"></div>
|
||||
<div className="w-4 h-4 bg-[var(--frontmatter-button-background)] rounded-full"></div>
|
||||
<div className="w-4 h-4 bg-[var(--frontmatter-button-background)] rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { ChatIcon } from '../../../components/icons/ChatIcon';
|
||||
|
||||
export interface IPlaceholderProps { }
|
||||
|
||||
export const Placeholder: React.FunctionComponent<IPlaceholderProps> = ({
|
||||
children,
|
||||
}: React.PropsWithChildren<IPlaceholderProps>) => {
|
||||
return (
|
||||
<div className='w-full h-full flex flex-col gap-2 items-center justify-center text-[var(--frontmatter-secondary-text)] opacity-80'>
|
||||
<ChatIcon className='h-16 w-16 text-[var(--vscode-sideBarTitle-foreground)]' />
|
||||
|
||||
<div className='text-[var(--vscode-sideBarTitle-foreground)] text-2xl mt-4'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import { Answer } from './Answer';
|
||||
|
||||
export interface IQuestionAnswerProps {
|
||||
question: string;
|
||||
answer: string;
|
||||
answerId: number;
|
||||
sources: string[];
|
||||
}
|
||||
|
||||
export const QuestionAnswer: React.FunctionComponent<IQuestionAnswerProps> = ({ question, answer, answerId, sources }: React.PropsWithChildren<IQuestionAnswerProps>) => {
|
||||
return (
|
||||
<ul className={`mt-4 space-y-4 px-4`}>
|
||||
<li className='question'>{question}</li>
|
||||
|
||||
<Answer
|
||||
answer={answer}
|
||||
answerId={answerId}
|
||||
sources={sources} />
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface AiInitResponse {
|
||||
company: string;
|
||||
chatId: number;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
)
|
||||
}`}
|
||||
>
|
||||
<Menu as="div" className={`relative flex text-left ${listView ? '' : 'z-10'}`}>
|
||||
<Menu as="div" className={`relative flex text-left`}>
|
||||
{!listView && (
|
||||
<div className="hidden group-hover/card:flex">
|
||||
<QuickAction title={`View content`} onClick={onView}>
|
||||
|
||||
@@ -43,6 +43,8 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
@@ -8,9 +7,11 @@ import { DateField } from '../Common/DateField';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
import { Status } from './Status';
|
||||
import * as React from 'react';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IItemProps extends Page { }
|
||||
|
||||
@@ -27,6 +28,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
const [imageHtml, setImageHtml] = useState<string | undefined>(undefined);
|
||||
const [footerHtml, setFooterHtml] = useState<string | undefined>(undefined);
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
const escapedTitle = useMemo(() => {
|
||||
@@ -49,7 +52,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
};
|
||||
|
||||
const tags: string[] | undefined = React.useMemo(() => {
|
||||
const tags: string[] | undefined = useMemo(() => {
|
||||
if (!settings?.dashboardState?.contents?.tags) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -74,11 +77,47 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return [];
|
||||
}, [settings, pageData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.fmExternal && window.fmExternal.getCardFooter) {
|
||||
window.fmExternal.getCardFooter(fmFilePath, {
|
||||
fmFilePath,
|
||||
date,
|
||||
title,
|
||||
description,
|
||||
type,
|
||||
...pageData
|
||||
}).then(htmlContent => {
|
||||
if (htmlContent) {
|
||||
setFooterHtml(htmlContent);
|
||||
} else {
|
||||
setFooterHtml(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (window.fmExternal && window.fmExternal.getCardImage) {
|
||||
window.fmExternal.getCardImage(fmFilePath, {
|
||||
fmFilePath,
|
||||
date,
|
||||
title,
|
||||
description,
|
||||
type,
|
||||
...pageData
|
||||
}).then(htmlContent => {
|
||||
if (htmlContent) {
|
||||
setImageHtml(htmlContent);
|
||||
} else {
|
||||
setImageHtml(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (view === DashboardViewType.Grid) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<div
|
||||
className={`group flex flex-wrap items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded ${getColors(
|
||||
className={`group flex flex-col items-start content-start h-full w-full text-left shadow-md dark:shadow-none hover:shadow-xl border rounded ${getColors(
|
||||
'bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 dark:hover:bg-vulcan-100 border-gray-200 dark:border-vulcan-50',
|
||||
'bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]'
|
||||
)
|
||||
@@ -92,31 +131,35 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
)
|
||||
}`}
|
||||
>
|
||||
{pageData[PREVIEW_IMAGE_FIELD] ? (
|
||||
<img
|
||||
src={`${pageData[PREVIEW_IMAGE_FIELD]}`}
|
||||
alt={escapedTitle}
|
||||
className="absolute inset-0 h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`flex items-center justify-center ${getColors(
|
||||
'bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100',
|
||||
'bg-[var(--vscode-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]'
|
||||
{
|
||||
imageHtml ?
|
||||
<div className="h-full w-full" dangerouslySetInnerHTML={{ __html: imageHtml }} /> :
|
||||
pageData[PREVIEW_IMAGE_FIELD] ? (
|
||||
<img
|
||||
src={`${pageData[PREVIEW_IMAGE_FIELD]}`}
|
||||
alt={escapedTitle}
|
||||
className="absolute inset-0 h-full w-full object-cover group-hover:brightness-75"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`flex items-center justify-center ${getColors(
|
||||
'bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100',
|
||||
'bg-[var(--vscode-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]'
|
||||
)
|
||||
}`}
|
||||
>
|
||||
<MarkdownIcon className={`h-32 ${getColors(
|
||||
'text-vulcan-100 dark:text-whisper-100',
|
||||
'text-[var(--vscode-sideBarTitle-foreground)] opacity-80'
|
||||
)
|
||||
}`} />
|
||||
</div>
|
||||
)
|
||||
}`}
|
||||
>
|
||||
<MarkdownIcon className={`h-32 ${getColors(
|
||||
'text-vulcan-100 dark:text-whisper-100',
|
||||
'text-[var(--vscode-sideBarTitle-foreground)] opacity-80'
|
||||
)
|
||||
}`} />
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
</button>
|
||||
|
||||
<div className="relative p-4 w-full">
|
||||
<div className="relative p-4 w-full grow">
|
||||
<div className={`flex justify-between items-center`}>
|
||||
{draftField && draftField.name && <Status draft={pageData[draftField.name]} />}
|
||||
|
||||
@@ -158,6 +201,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{
|
||||
footerHtml && (
|
||||
<div className="placeholder__card__footer p-4 w-full" dangerouslySetInnerHTML={{ __html: footerHtml }} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import Ajv from 'ajv';
|
||||
import Ajv, { FormatDefinition } from 'ajv';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { JSONSchemaBridge } from 'uniforms-bridge-json-schema';
|
||||
import { AutoFields, AutoForm, ErrorsField } from '../../../components/uniforms-frontmatter';
|
||||
// import { AutoFields, AutoForm, ErrorsField } from 'uniforms-antd';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { DataFormControls } from './DataFormControls';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
@@ -22,17 +21,40 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
onClear
|
||||
}: React.PropsWithChildren<IDataFormProps>) => {
|
||||
const [bridge, setBridge] = useState<JSONSchemaBridge | null>(null);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
const ajv = new Ajv({ allErrors: true, useDefaults: true });
|
||||
const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
useDefaults: true,
|
||||
validateSchema: false,
|
||||
strict: false,
|
||||
strictTypes: false
|
||||
});
|
||||
|
||||
// Added the multiline keyword for the textarea field
|
||||
ajv.addKeyword({
|
||||
keyword: 'multiline',
|
||||
type: 'boolean',
|
||||
schemaType: 'boolean'
|
||||
});
|
||||
|
||||
const jsonValidator = (schema: object) => {
|
||||
const validator = ajv.compile(schema);
|
||||
try {
|
||||
setError(undefined);
|
||||
const validator = ajv.compile(schema);
|
||||
|
||||
return (crntModel: object) => {
|
||||
validator(crntModel);
|
||||
return validator.errors?.length ? { details: validator.errors } : null;
|
||||
};
|
||||
return (crntModel: object) => {
|
||||
if (validator(crntModel)) {
|
||||
return null;
|
||||
} else {
|
||||
return { details: validator.errors }
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
setError((error as Error).message);
|
||||
return () => { };
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -45,13 +67,21 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={`mt-4 text-[var(--vscode-errorForeground)]`}>
|
||||
{error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div className="autoform">
|
||||
{model ? (
|
||||
<h2 className={getColors(`text-gray-500 dark:text-whisper-900`, `text-[var(--frontmatter-secondary-text)]`)}>Modify the data</h2>
|
||||
<h2 className={getColors(`text - gray - 500 dark: text - whisper - 900`, `text - [var(--frontmatter - secondary - text)]`)}>Modify the data</h2>
|
||||
) : (
|
||||
<h2 className={getColors(`text-gray-500 dark:text-whisper-900`, `text-[var(--frontmatter-secondary-text)]`)}>Add new data</h2>
|
||||
<h2 className={getColors(`text - gray - 500 dark: text - whisper - 900`, `text - [var(--frontmatter - secondary - text)]`)}>Add new data</h2>
|
||||
)}
|
||||
|
||||
<AutoForm
|
||||
@@ -67,7 +97,6 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
|
||||
<div className={`errors`}>
|
||||
<ErrorsField />
|
||||
</div>
|
||||
|
||||
<DataFormControls model={model} onClear={onClear} />
|
||||
</AutoForm>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<any>>) => {
|
||||
if (message.data.command === DashboardCommand.dataFileEntries) {
|
||||
setDataEntries(message.data.data);
|
||||
setDataEntries(message.data.payload);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -317,6 +317,8 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
|
||||
/>
|
||||
|
||||
<ToastContainer />
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=dataview" alt="DataView metrics" />
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import { PaginationStatus } from './PaginationStatus';
|
||||
import useThemeColors from '../../hooks/useThemeColors';
|
||||
import { Startup } from './Startup';
|
||||
import { Navigation } from './Navigation';
|
||||
import { ProjectSwitcher } from './ProjectSwitcher';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -141,17 +142,19 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
}, [location.search]);
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-40 ${getColors(
|
||||
<div className={`w-full sticky top-0 z-20 ${getColors(
|
||||
`bg-gray-100 dark:bg-vulcan-500`,
|
||||
`bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`
|
||||
)
|
||||
}`}>
|
||||
<div className={`mb-0 border-b ${getColors(
|
||||
<div className={`mb-0 border-b flex justify-between ${getColors(
|
||||
`bg-gray-100 dark:bg-vulcan-500 border-gray-200 dark:border-vulcan-300`,
|
||||
`bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--vscode-editorWidget-border)]`
|
||||
)
|
||||
}`}>
|
||||
<Tabs onNavigate={updateView} />
|
||||
|
||||
<ProjectSwitcher />
|
||||
</div>
|
||||
|
||||
{location.pathname === routePaths.contents && (
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { SwitchHorizontalIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
export interface IProjectSwitcherProps { }
|
||||
|
||||
export const ProjectSwitcher: React.FunctionComponent<IProjectSwitcherProps> = (props: React.PropsWithChildren<IProjectSwitcherProps>) => {
|
||||
const [crntProject, setCrntProject] = React.useState<string | undefined>(undefined);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const project = settings?.project;
|
||||
const projects = settings?.projects || [];
|
||||
|
||||
const setProject = (value: string) => {
|
||||
setCrntProject(value);
|
||||
messageHandler.send(DashboardMessage.switchProject, value)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
setCrntProject(project?.name);
|
||||
}, [project]);
|
||||
|
||||
if (projects.length <= 1 || !crntProject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center mr-4 z-[51]">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
<MenuButton
|
||||
label={(
|
||||
<div className="inline-flex items-center">
|
||||
<SwitchHorizontalIcon className="h-4 w-4 mr-2" />
|
||||
<span>project</span>
|
||||
</div>
|
||||
)}
|
||||
title={crntProject} />
|
||||
|
||||
<MenuItems disablePopper>
|
||||
{projects.map((p) => (
|
||||
<MenuItem
|
||||
key={p.name}
|
||||
title={p.name}
|
||||
value={p.name}
|
||||
isCurrent={p.name === crntProject}
|
||||
onClick={(value) => setProject(p.name)}
|
||||
/>
|
||||
))}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -22,7 +22,7 @@ export const SyncButton: React.FunctionComponent<ISyncButtonProps> = (
|
||||
};
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const { command, data } = message.data;
|
||||
const { command, payload } = message.data;
|
||||
|
||||
if (command === GeneralCommands.toWebview.gitSyncingStart) {
|
||||
setIsSyncing(true);
|
||||
|
||||
@@ -109,7 +109,10 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
relPath = mediaParsed.split(wsFolderParsed).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
// If the static folder is the root, we can just return the relative path
|
||||
if (settings.staticFolder === "/") {
|
||||
return relPath;
|
||||
} else if (settings.staticFolder && relPath) {
|
||||
const staticFolderParsed = parseWinPath(settings.staticFolder);
|
||||
relPath = relPath.split(staticFolderParsed).pop();
|
||||
}
|
||||
@@ -444,7 +447,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
{renderMediaIcon}
|
||||
</div>
|
||||
<div
|
||||
className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}
|
||||
className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center group-hover:brightness-75`}
|
||||
>
|
||||
{renderMedia}
|
||||
</div>
|
||||
|
||||
@@ -272,6 +272,8 @@ export const Media: React.FunctionComponent<IMediaProps> = (
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,28 +15,26 @@ export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
disabled
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
const { getColors } = useThemeColors();
|
||||
|
||||
|
||||
return (
|
||||
<div className={`groupinline-flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<span className={`mr-2 font-medium ${getColors('text-gray-500 dark:text-whisper-700', 'text-[var(--vscode-tab-inactiveForeground)]')}`}>{label}:</span>
|
||||
<div className={`group inline-flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center ${getColors('text-gray-500 dark:text-whisper-700', 'text-[var(--vscode-tab-inactiveForeground)]')}`}>{label}:</div>
|
||||
|
||||
<Menu.Button
|
||||
disabled={disabled}
|
||||
className={`group inline-flex justify-center text-sm font-medium ${
|
||||
getColors(
|
||||
'text-vulcan-500 hover:text-vulcan-600 dark:text-whisper-500 dark:hover:text-whisper-600',
|
||||
'text-[var(--vscode-list-activeSelectionForeground)] hover:text-[var(--vscode-list-highlightForeground)]'
|
||||
)
|
||||
}`}
|
||||
className={`group inline-flex justify-center text-sm font-medium ${getColors(
|
||||
'text-vulcan-500 hover:text-vulcan-600 dark:text-whisper-500 dark:hover:text-whisper-600',
|
||||
'text-[var(--vscode-list-activeSelectionForeground)] hover:text-[var(--vscode-list-highlightForeground)]'
|
||||
)
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
<ChevronDownIcon
|
||||
className={`flex-shrink-0 -mr-1 ml-1 h-5 w-5 ${
|
||||
getColors(
|
||||
'text-gray-400 group-hover:text-gray-500 dark:text-whisper-600 dark:group-hover:text-whisper-700',
|
||||
'text-[var(--vscode-list-activeSelectionForeground)] group-hover:text-[var(--vscode-list-highlightForeground)]'
|
||||
)
|
||||
}`}
|
||||
className={`flex-shrink-0 -mr-1 ml-1 h-5 w-5 ${getColors(
|
||||
'text-gray-400 group-hover:text-gray-500 dark:text-whisper-600 dark:group-hover:text-whisper-700',
|
||||
'text-[var(--vscode-list-activeSelectionForeground)] group-hover:text-[var(--vscode-list-highlightForeground)]'
|
||||
)
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
@@ -18,12 +18,11 @@ export const QuickAction: React.FunctionComponent<IQuickActionProps> = ({
|
||||
type="button"
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
className={`px-2 group inline-flex justify-center text-sm font-medium ${
|
||||
getColors(
|
||||
'text-vulcan-400 hover:text-vulcan-600 dark:text-gray-400 dark:hover:text-whisper-600',
|
||||
'text-[var(--vscode-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]'
|
||||
)
|
||||
}`}
|
||||
className={`px-2 group inline-flex justify-center text-sm font-medium ${getColors(
|
||||
'text-vulcan-400 hover:text-vulcan-600 dark:text-gray-400 dark:hover:text-whisper-600',
|
||||
'text-[var(--vscode-foreground)] hover:text-[var(--frontmatter-button-hoverBackground)]'
|
||||
)
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
<span className="sr-only">{title}</span>
|
||||
|
||||
@@ -84,6 +84,8 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
|
||||
marginTop: '30px'
|
||||
}}
|
||||
></iframe>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=preview" alt="Preview metrics" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,14 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
|
||||
const snippets = settings?.snippets || {};
|
||||
const snippetKeys = useMemo(() => {
|
||||
const allSnippetKeys = Object.keys(snippets).sort((a, b) => a.localeCompare(b));
|
||||
let allSnippetKeys = Object.keys(snippets).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
if (viewData?.data?.filePath) {
|
||||
allSnippetKeys = allSnippetKeys.filter((key) => {
|
||||
return !snippets[key].isMediaSnippet;
|
||||
});
|
||||
}
|
||||
|
||||
return allSnippetKeys.filter((key) => {
|
||||
const value = snippetFilter.toLowerCase();
|
||||
const keyValue = key.toLowerCase();
|
||||
@@ -44,7 +51,9 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
// Contains in key or description, values included in key are ranked higher (sort and fuzzy search)
|
||||
return keyValue.includes(value) || descriptionValue.includes(value);
|
||||
});
|
||||
}, [settings?.snippets, snippetFilter]);
|
||||
|
||||
|
||||
}, [settings?.snippets, snippetFilter, viewData?.data?.filePath]);
|
||||
|
||||
const onSnippetAdd = useCallback(() => {
|
||||
if (!snippetTitle || !snippetBody) {
|
||||
@@ -90,7 +99,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
<FilterInput
|
||||
placeholder="Search"
|
||||
isReady={true}
|
||||
autoFocus={true}
|
||||
autoFocus={(snippetKeys && snippetKeys.length > 0)}
|
||||
value={snippetFilter}
|
||||
onChange={(value: string) => setSnippetFilter(value)}
|
||||
onReset={() => setSnippetFilter('')}
|
||||
@@ -121,7 +130,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
</div>
|
||||
)}
|
||||
|
||||
{snippetKeys && snippetKeys.length > 0 ? (
|
||||
{(snippetKeys && snippetKeys.length > 0) ? (
|
||||
<ul
|
||||
role="list"
|
||||
className={`grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}
|
||||
@@ -131,11 +140,20 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className={`flex flex-col items-center ${getColors(`text-gray-500 dark:text-vulcan-500`, `text-[var(--vscode-foreground)]`)
|
||||
<div className="w-full h-full flex items-center justify-center text-white">
|
||||
<div className={`flex flex-col items-center ${getColors('text-gray-500 dark:text-whisper-900', 'text-[var(--frontmatter-text)]')
|
||||
}`}>
|
||||
<CodeIcon className="w-32 h-32" />
|
||||
<p className="text-3xl">No snippets found</p>
|
||||
<p className="text-3xl mt-2">No snippets found</p>
|
||||
<p className="text-xl mt-4">
|
||||
<a
|
||||
className={getColors(`text-teal-700 hover:text-teal-900`, `text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`)}
|
||||
href={`https://frontmatter.codes/docs/snippets`}
|
||||
title={`Read more to get started with snippets`}
|
||||
>
|
||||
Read more to get started with snippets
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -169,6 +187,8 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=snippets" alt="Snippets metrics" />
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -97,6 +97,8 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
|
||||
version={settings?.versionInfo}
|
||||
isBacker={settings?.isBacker}
|
||||
/>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=taxonomy" alt="Taxonomy metrics" />
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -121,6 +121,8 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({
|
||||
</h2>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=welcome" alt="Welcome metrics" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -51,15 +51,15 @@ export default function useMedia() {
|
||||
message: MessageEvent<EventData<MediaPaths | { key: string; value: any }>>
|
||||
) => {
|
||||
if (message.data.command === DashboardCommand.media) {
|
||||
const data: MediaPaths = message.data.data as MediaPaths;
|
||||
const payload: MediaPaths = message.data.payload as MediaPaths;
|
||||
setLoading(false);
|
||||
setMedia(data.media);
|
||||
setTotal(data.total);
|
||||
setFolders(data.folders);
|
||||
setSelectedFolder(data.selectedFolder);
|
||||
setSearchedMedia(data.media);
|
||||
setAllContentFolders(data.allContentFolders);
|
||||
setAllStaticFolders(data.allStaticfolders);
|
||||
setMedia(payload.media);
|
||||
setTotal(payload.total);
|
||||
setFolders(payload.folders);
|
||||
setSelectedFolder(payload.selectedFolder);
|
||||
setSearchedMedia(payload.media);
|
||||
setAllContentFolders(payload.allContentFolders);
|
||||
setAllStaticFolders(payload.allStaticfolders);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,34 +30,34 @@ export default function useMessages() {
|
||||
|
||||
switch (message.command) {
|
||||
case DashboardCommand.loading:
|
||||
setLoading(message.data);
|
||||
setLoading(message.payload);
|
||||
break;
|
||||
case DashboardCommand.viewData:
|
||||
setViewData(message.data);
|
||||
if (message.data?.type === NavigationType.Media) {
|
||||
setViewData(message.payload);
|
||||
if (message.payload?.type === NavigationType.Media) {
|
||||
setView(NavigationType.Media);
|
||||
} else if (message.data?.type === NavigationType.Contents) {
|
||||
} else if (message.payload?.type === NavigationType.Contents) {
|
||||
setView(NavigationType.Contents);
|
||||
} else if (message.data?.type === NavigationType.Data) {
|
||||
} else if (message.payload?.type === NavigationType.Data) {
|
||||
setView(NavigationType.Data);
|
||||
} else if (message.data?.type === NavigationType.Taxonomy) {
|
||||
} else if (message.payload?.type === NavigationType.Taxonomy) {
|
||||
setView(NavigationType.Taxonomy);
|
||||
} else if (message.data?.type === NavigationType.Snippets) {
|
||||
} else if (message.payload?.type === NavigationType.Snippets) {
|
||||
setView(NavigationType.Snippets);
|
||||
}
|
||||
break;
|
||||
case DashboardCommand.settings:
|
||||
setSettings(message.data);
|
||||
setSettings(message.payload);
|
||||
break;
|
||||
case DashboardCommand.pages:
|
||||
setPages(message.data);
|
||||
setPages(message.payload);
|
||||
setLoading(false);
|
||||
break;
|
||||
case DashboardCommand.searchReady:
|
||||
setSearchReady(true);
|
||||
break;
|
||||
case GeneralCommands.toWebview.setMode:
|
||||
setMode(message.data);
|
||||
setMode(message.payload);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,7 +182,7 @@ export default function usePages(pages: Page[]) {
|
||||
const searchListener = (message: MessageEvent<EventData<any>>) => {
|
||||
switch (message.data.command) {
|
||||
case DashboardMessage.searchPages:
|
||||
processPages(message.data.data);
|
||||
processPages(message.data.payload);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import './styles.css';
|
||||
import { Preview } from './components/Preview';
|
||||
import { SettingsProvider } from './providers/SettingsProvider';
|
||||
import { CustomPanelViewResult } from '../models';
|
||||
import { Chatbot } from './components/Chatbot/Chatbot';
|
||||
|
||||
declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
@@ -16,6 +18,21 @@ declare const acquireVsCodeApi: <T = unknown>() => {
|
||||
postMessage: (msg: unknown) => void;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
fmExternal: {
|
||||
isDevelopment: boolean;
|
||||
getCustomFields: {
|
||||
name: string,
|
||||
html: (data: any, change: (value: any) => void) => Promise<CustomPanelViewResult | undefined>
|
||||
}[];
|
||||
getPanelView: (data: any) => Promise<CustomPanelViewResult | undefined>;
|
||||
getCardImage: (filePath: string, data: any) => Promise<string | undefined>;
|
||||
getCardFooter: (filePath: string, data: any) => Promise<string | undefined>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const routePaths: { [name: string]: string } = {
|
||||
welcome: '/welcome',
|
||||
contents: '/contents',
|
||||
@@ -113,6 +130,14 @@ if (elm) {
|
||||
<SettingsProvider experimental={experimental === 'true'} version={version || ""}>
|
||||
<Preview url={url} />
|
||||
</SettingsProvider>, elm);
|
||||
} else if (type === 'chatbot') {
|
||||
render(
|
||||
<SettingsProvider
|
||||
aiUrl='https://frontmatter.codes'
|
||||
experimental={experimental === 'true'}
|
||||
version={version || ""}>
|
||||
<Chatbot />
|
||||
</SettingsProvider>, elm);
|
||||
} else {
|
||||
render(
|
||||
<RecoilRoot>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DraftField,
|
||||
Framework,
|
||||
GitSettings,
|
||||
Project,
|
||||
Snippets,
|
||||
SortingSetting
|
||||
} from '../../models';
|
||||
@@ -16,6 +17,8 @@ import { DashboardViewType } from '.';
|
||||
import { DataFile } from '../../models/DataFile';
|
||||
|
||||
export interface Settings {
|
||||
projects: Project[];
|
||||
project: Project;
|
||||
git: GitSettings;
|
||||
beta: boolean;
|
||||
initialized: boolean;
|
||||
|
||||
@@ -3,27 +3,18 @@ import * as React from 'react'
|
||||
interface ISettingsProviderProps {
|
||||
version?: string;
|
||||
experimental?: boolean;
|
||||
aiUrl?: string;
|
||||
}
|
||||
|
||||
const SettingsContext = React.createContext<ISettingsProviderProps | undefined>(undefined);
|
||||
|
||||
const SettingsProvider: React.FunctionComponent<ISettingsProviderProps> = ({ version, experimental, children }: React.PropsWithChildren<ISettingsProviderProps>) => {
|
||||
const [ crntVersion, setCrntVersion ] = React.useState<string | undefined>(undefined);
|
||||
const [ crntExprimental, setCrntExprimental ] = React.useState<boolean>(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCrntVersion(version);
|
||||
}, [version]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCrntExprimental(typeof experimental === 'boolean' ? experimental : false);
|
||||
}, [experimental]);
|
||||
|
||||
const SettingsProvider: React.FunctionComponent<ISettingsProviderProps> = ({ version, experimental, aiUrl, children }: React.PropsWithChildren<ISettingsProviderProps>) => {
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
version: crntVersion,
|
||||
experimental: crntExprimental
|
||||
version,
|
||||
experimental,
|
||||
aiUrl
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
@apply my-2;
|
||||
}
|
||||
|
||||
input {
|
||||
input,
|
||||
textarea {
|
||||
@apply w-full px-2 py-1 text-vulcan-500;
|
||||
|
||||
&::placeholder {
|
||||
@@ -332,7 +333,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.experimental {
|
||||
.experimental form {
|
||||
[type='text'],
|
||||
[type='email'],
|
||||
[type='url'],
|
||||
@@ -401,3 +402,85 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qa__bot {
|
||||
> div {
|
||||
@apply space-y-4;
|
||||
}
|
||||
|
||||
li {
|
||||
@apply space-y-2;
|
||||
}
|
||||
|
||||
pre code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.question {
|
||||
@apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 py-2 px-4 text-whisper-500;
|
||||
|
||||
&:after {
|
||||
--size: 1rem;
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: var(--size);
|
||||
width: var(--size);
|
||||
z-index: 2;
|
||||
right: calc(var(--size) * -1);
|
||||
border-bottom-right-radius: 8rem;
|
||||
background: radial-gradient(
|
||||
circle at top right,
|
||||
rgba(0, 0, 0, 0) 0,
|
||||
rgba(0, 0, 0, 0) var(--size),
|
||||
#009aa3 var(--size)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.answer {
|
||||
@apply break-all rounded border border-teal-100 border-opacity-20 p-4 pb-0;
|
||||
}
|
||||
}
|
||||
|
||||
.chatbox {
|
||||
textarea {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dots {
|
||||
&:after {
|
||||
display: inline-block;
|
||||
animation: ellipsis 1.25s infinite;
|
||||
content: '.';
|
||||
width: 1em;
|
||||
text-align: left;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
0% {
|
||||
content: '';
|
||||
}
|
||||
25% {
|
||||
content: '.';
|
||||
}
|
||||
50% {
|
||||
content: '..';
|
||||
}
|
||||
75% {
|
||||
content: '...';
|
||||
}
|
||||
100% {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
DataListener,
|
||||
SettingsListener
|
||||
} from './../listeners/panel';
|
||||
import { TelemetryEvent } from '../constants';
|
||||
import { SETTING_EXPERIMENTAL, SETTING_EXTENSIBILITY_SCRIPTS, TelemetryEvent } from '../constants';
|
||||
import {
|
||||
CancellationToken,
|
||||
Disposable,
|
||||
@@ -25,6 +25,7 @@ import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { GitListener, ModeListener } from '../listeners/general';
|
||||
import { Folders } from '../commands';
|
||||
|
||||
export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
public static readonly viewType = 'frontMatter.explorer';
|
||||
@@ -115,7 +116,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
});
|
||||
|
||||
window.onDidChangeActiveTextEditor(() => {
|
||||
this.sendMessage({ command: Command.loading, data: true });
|
||||
this.sendMessage({ command: Command.loading, payload: true });
|
||||
|
||||
if (this.visible) {
|
||||
DataListener.getFileData();
|
||||
@@ -131,7 +132,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
* Post data to the panel
|
||||
* @param msg
|
||||
*/
|
||||
public sendMessage(msg: { command: Command; data?: any }) {
|
||||
public sendMessage(msg: { command: Command; payload?: any }) {
|
||||
this.panel?.webview?.postMessage(msg);
|
||||
}
|
||||
|
||||
@@ -201,17 +202,34 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
|
||||
}
|
||||
|
||||
// Get experimental setting
|
||||
const experimental = Settings.get(SETTING_EXPERIMENTAL);
|
||||
const extensibilityScripts = Settings.get<string[]>(SETTING_EXTENSIBILITY_SCRIPTS) || [];
|
||||
|
||||
const scriptsToLoad: string[] = [];
|
||||
if (experimental) {
|
||||
for (const script of extensibilityScripts) {
|
||||
if (script.startsWith('https://')) {
|
||||
scriptsToLoad.push(script);
|
||||
} else {
|
||||
const absScriptPath = Folders.getAbsFilePath(script);
|
||||
const scriptUri = webView.asWebviewUri(Uri.file(absScriptPath));
|
||||
scriptsToLoad.push(scriptUri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const csp = [
|
||||
`default-src 'none';`,
|
||||
`img-src ${`vscode-file://vscode-app`} ${
|
||||
webView.cspSource
|
||||
} https://api.visitorbadge.io 'self' 'unsafe-inline' https://*`,
|
||||
`script-src 'unsafe-eval' ${
|
||||
`script-src 'unsafe-eval' https://* ${
|
||||
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
}`,
|
||||
`style-src ${webView.cspSource} 'self' 'unsafe-inline'`,
|
||||
`style-src ${webView.cspSource} 'self' 'unsafe-inline' https://*`,
|
||||
`font-src ${webView.cspSource}`,
|
||||
`connect-src https://o1022172.ingest.sentry.io ${
|
||||
`connect-src https://o1022172.ingest.sentry.io https://* ${
|
||||
isProd
|
||||
? ``
|
||||
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
|
||||
@@ -220,7 +238,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="${csp.join('; ')}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -233,11 +251,17 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
<body>
|
||||
<div id="app" data-isProd="${isProd}" data-environment="${
|
||||
isBeta ? 'BETA' : 'main'
|
||||
}" data-version="${version.usedVersion}" ></div>
|
||||
}" data-version="${version.usedVersion}"></div>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`panel-${version.installedVersion}`}" alt="Daily usage" />
|
||||
${(scriptsToLoad || [])
|
||||
.map((script) => {
|
||||
return `<script type="module" src="${script}" nonce="${nonce}"></script>`;
|
||||
})
|
||||
.join('')}
|
||||
|
||||
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
|
||||
|
||||
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`panel-${version.installedVersion}`}" alt="Daily usage" />
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
+12
-5
@@ -26,7 +26,8 @@ import {
|
||||
Dashboard,
|
||||
Article,
|
||||
Settings,
|
||||
StatusListener
|
||||
StatusListener,
|
||||
Chatbot
|
||||
} from './commands';
|
||||
|
||||
let frontMatterStatusBar: vscode.StatusBarItem;
|
||||
@@ -309,6 +310,11 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand(COMMAND_NAME.preview, () => Preview.open(extensionPath))
|
||||
);
|
||||
|
||||
// Chat to the bot
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.chatbot, () => Chatbot.open(extensionPath))
|
||||
);
|
||||
|
||||
// Inserting an image in Markdown
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia)
|
||||
@@ -346,6 +352,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Cache commands
|
||||
Cache.registerCommands();
|
||||
|
||||
// Project switching
|
||||
Project.registerCommands();
|
||||
|
||||
// Subscribe all commands
|
||||
subscriptions.push(
|
||||
insertTags,
|
||||
@@ -369,12 +378,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
createFolder
|
||||
);
|
||||
|
||||
console.log(`FRONT MATTER CMS activated!`)
|
||||
console.log(`𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲 𝖺𝖼𝗍𝗂𝗏𝖺𝗍𝖾𝖽! 𝖱𝖾𝖺𝖽𝗒 𝗍𝗈 𝗌𝗍𝖺𝗋𝗍 𝗐𝗋𝗂𝗍𝗂𝗇𝗀... 👩💻🧑💻👨💻`);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
Telemetry.dispose();
|
||||
}
|
||||
export function deactivate() {}
|
||||
|
||||
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
|
||||
Article.autoUpdate(e);
|
||||
|
||||
@@ -203,7 +203,7 @@ export class ArticleHelper {
|
||||
noArrayIndent: !indentArray,
|
||||
skipInvalid: true,
|
||||
noCompatMode: true,
|
||||
lineWidth: 500,
|
||||
lineWidth: 50000,
|
||||
indent: spaces || 2
|
||||
} as DumpOptions as any);
|
||||
}
|
||||
@@ -383,7 +383,7 @@ export class ArticleHelper {
|
||||
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
|
||||
|
||||
let prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
|
||||
prefix = ArticleHelper.getFilePrefix(folderPath, contentType);
|
||||
prefix = ArticleHelper.getFilePrefix(prefix, folderPath, contentType);
|
||||
|
||||
// Name of the file or folder to create
|
||||
let sanitizedName = ArticleHelper.sanitize(titleValue);
|
||||
@@ -436,8 +436,14 @@ export class ArticleHelper {
|
||||
* @param contentType
|
||||
* @returns
|
||||
*/
|
||||
public static getFilePrefix(filePath?: string, contentType?: ContentType): string | undefined {
|
||||
let prefix = undefined;
|
||||
public static getFilePrefix(
|
||||
prefix: string | null | undefined,
|
||||
filePath?: string,
|
||||
contentType?: ContentType
|
||||
): string | undefined {
|
||||
if (!prefix) {
|
||||
prefix = undefined;
|
||||
}
|
||||
|
||||
// Retrieve the file prefix from the folder
|
||||
if (filePath) {
|
||||
|
||||
+72
-25
@@ -28,7 +28,7 @@ import { Telemetry } from './Telemetry';
|
||||
import { processKnownPlaceholders } from './PlaceholderHelper';
|
||||
import { basename } from 'path';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { existsAsync, writeFileAsync } from '../utils';
|
||||
import { encodeEmoji, existsAsync, writeFileAsync } from '../utils';
|
||||
|
||||
export class ContentType {
|
||||
/**
|
||||
@@ -520,32 +520,30 @@ export class ContentType {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
if (field.toLowerCase() === 'tag' || field.toLowerCase() === 'tags') {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'tags'
|
||||
} as Field);
|
||||
} else if (field.toLowerCase() === 'category' || field.toLowerCase() === 'categories') {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'categories'
|
||||
} as Field);
|
||||
} else if (
|
||||
fieldData &&
|
||||
fieldData instanceof Array &&
|
||||
fieldData.length > 0 &&
|
||||
typeof fieldData[0] === 'string'
|
||||
) {
|
||||
if (field.toLowerCase() === 'tag' || field.toLowerCase() === 'tags') {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'tags'
|
||||
} as Field);
|
||||
} else if (field.toLowerCase() === 'category' || field.toLowerCase() === 'categories') {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'categories'
|
||||
} as Field);
|
||||
} else {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'choice',
|
||||
choices: fieldData
|
||||
} as Field);
|
||||
}
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'choice',
|
||||
choices: fieldData
|
||||
} as Field);
|
||||
} else if (
|
||||
fieldData &&
|
||||
fieldData instanceof Array &&
|
||||
@@ -568,7 +566,19 @@ export class ContentType {
|
||||
fields: newFields
|
||||
} as Field);
|
||||
} else {
|
||||
if (!isNaN(new Date(fieldData).getDate())) {
|
||||
if (field.toLowerCase().includes('image')) {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'image'
|
||||
} as Field);
|
||||
} else if (typeof fieldData === 'number') {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: 'number'
|
||||
} as Field);
|
||||
} else if (!isNaN(new Date(fieldData).getDate())) {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
@@ -609,11 +619,18 @@ export class ContentType {
|
||||
cancellable: false
|
||||
},
|
||||
async () => {
|
||||
const titleValue = await Questions.ContentTitle();
|
||||
let titleValue = await Questions.ContentTitle();
|
||||
if (!titleValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
titleValue = titleValue.trim();
|
||||
// Check if the title needs to encode the emoji's used in it
|
||||
const titleField = contentType.fields.find((f) => f.name === 'title');
|
||||
if (titleField && titleField.encodeEmoji) {
|
||||
titleValue = encodeEmoji(titleValue);
|
||||
}
|
||||
|
||||
let templatePath = contentType.template;
|
||||
let templateData: ParsedFrontMatter | null = null;
|
||||
if (templatePath) {
|
||||
@@ -735,7 +752,37 @@ export class ContentType {
|
||||
) {
|
||||
data[field.name] = true;
|
||||
} else {
|
||||
data[field.name] = '';
|
||||
// Check the field types
|
||||
switch (field.type) {
|
||||
case 'choice':
|
||||
if (field.multiple) {
|
||||
data[field.name] = [];
|
||||
} else {
|
||||
data[field.name] = '';
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
data[field.name] = false;
|
||||
break;
|
||||
case 'number':
|
||||
data[field.name] = 0;
|
||||
break;
|
||||
case 'datetime':
|
||||
data[field.name] = null;
|
||||
break;
|
||||
case 'list':
|
||||
case 'tags':
|
||||
case 'categories':
|
||||
case 'taxonomy':
|
||||
data[field.name] = [];
|
||||
break;
|
||||
case 'string':
|
||||
case 'image':
|
||||
case 'file':
|
||||
default:
|
||||
data[field.name] = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ export class CustomScript {
|
||||
const fullScript = `${command} ${scriptPath} ${args}`;
|
||||
Logger.info(`Executing: ${fullScript}`);
|
||||
|
||||
exec(fullScript, (error, stdout) => {
|
||||
exec(fullScript, { cwd: wsPath }, (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error.message);
|
||||
return;
|
||||
|
||||
@@ -59,6 +59,8 @@ export class DashboardSettings {
|
||||
const pagination = Settings.get<boolean | number>(SETTING_DASHBOARD_CONTENT_PAGINATION);
|
||||
|
||||
const settings = {
|
||||
projects: Settings.getProjects(),
|
||||
project: Settings.getProject(),
|
||||
git: {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Notifications } from './Notifications';
|
||||
import { parseWinPath } from './parseWinPath';
|
||||
import { LocalStore } from '../constants';
|
||||
import { existsAsync, renameAsync } from '../utils';
|
||||
import { existsSync, mkdirSync, renameSync } from 'fs';
|
||||
|
||||
interface MediaRecord {
|
||||
description: string;
|
||||
@@ -23,11 +24,38 @@ export class MediaLibrary {
|
||||
return;
|
||||
}
|
||||
|
||||
// In version 8.4.0 we moved to a new database location
|
||||
// This is to ensure that the database is moved to the new location
|
||||
const oldDbPath = join(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
LocalStore.rootFolder,
|
||||
LocalStore.contentFolder,
|
||||
LocalStore.mediaDatabaseFile
|
||||
);
|
||||
|
||||
const dbFolder = join(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
LocalStore.rootFolder,
|
||||
LocalStore.databaseFolder
|
||||
);
|
||||
const dbPath = join(dbFolder, LocalStore.mediaDatabaseFile);
|
||||
|
||||
if (existsSync(oldDbPath)) {
|
||||
// Check if the database folder exists
|
||||
if (!existsSync(dbFolder)) {
|
||||
mkdirSync(dbFolder, { recursive: true });
|
||||
}
|
||||
// Move the database file
|
||||
if (existsSync(oldDbPath)) {
|
||||
renameSync(oldDbPath, dbPath);
|
||||
}
|
||||
}
|
||||
|
||||
this.db = new JsonDB(
|
||||
join(
|
||||
parseWinPath(wsFolder?.fsPath || ''),
|
||||
LocalStore.rootFolder,
|
||||
LocalStore.contentFolder,
|
||||
LocalStore.databaseFolder,
|
||||
LocalStore.mediaDatabaseFile
|
||||
),
|
||||
true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SETTING_SPONSORS_AI_ENABLED } from './../constants/settings';
|
||||
import { workspace } from 'vscode';
|
||||
import { Extension, Settings } from '.';
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
@@ -46,7 +47,10 @@ export class PanelSettings {
|
||||
public static async get(): Promise<IPanelSettings> {
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
|
||||
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
|
||||
|
||||
return {
|
||||
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
|
||||
git: {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { 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';
|
||||
|
||||
export class Questions {
|
||||
/**
|
||||
@@ -24,12 +28,77 @@ export class Questions {
|
||||
* @returns
|
||||
*/
|
||||
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
|
||||
const title = await window.showInputBox({
|
||||
title: 'Title',
|
||||
prompt: `What would you like to use as a title for the content to create?`,
|
||||
placeHolder: `Content title`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
|
||||
let title: string | undefined = '';
|
||||
|
||||
if (aiEnabled) {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], { silent: true });
|
||||
|
||||
if (githubAuth && githubAuth.account.label) {
|
||||
title = await window.showInputBox({
|
||||
title: 'Title or description',
|
||||
prompt: `What would you like to write about?`,
|
||||
placeHolder: `Content title or description`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (title) {
|
||||
try {
|
||||
const aiTitles = await SponsorAi.getTitles(githubAuth.accessToken, title);
|
||||
|
||||
if (aiTitles && aiTitles.length > 0) {
|
||||
const options: QuickPickItem[] = [
|
||||
{
|
||||
label: `✏️ your title/description`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
{
|
||||
label: title
|
||||
},
|
||||
{
|
||||
label: `🤖 AI generated title`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
...aiTitles.map((d: string) => ({
|
||||
label: d
|
||||
}))
|
||||
];
|
||||
|
||||
const selectedTitle = await window.showQuickPick(options, {
|
||||
title: 'Select a title',
|
||||
placeHolder: `Select a title for your content`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedTitle) {
|
||||
title = selectedTitle.label;
|
||||
} else if (!selectedTitle) {
|
||||
// Reset the title, so the user can enter their own title
|
||||
title = undefined;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(
|
||||
`Failed fetching the AI title. Please try to use your own title or try again later.`
|
||||
);
|
||||
title = undefined;
|
||||
}
|
||||
} else if (!title && showWarning) {
|
||||
Notifications.warning(`You did not specify a title for your content.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
title = await window.showInputBox({
|
||||
title: 'Title',
|
||||
prompt: `What would you like to use as a title for the content to create?`,
|
||||
placeHolder: `Content title`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!title && showWarning) {
|
||||
Notifications.warning(`You did not specify a title for your content.`);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { SETTING_EXTENSIBILITY_SCRIPTS, SETTING_PROJECTS } from './../constants/settings';
|
||||
import { parseWinPath } from './parseWinPath';
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { Notifications } from './Notifications';
|
||||
import { commands, Uri, workspace, window } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import { ContentType, CustomTaxonomy, TaxonomyType } from '../models';
|
||||
import { ContentType, CustomTaxonomy, Project, TaxonomyType } from '../models';
|
||||
import {
|
||||
SETTING_TAXONOMY_TAGS,
|
||||
SETTING_TAXONOMY_CATEGORIES,
|
||||
@@ -52,10 +53,32 @@ export class Settings {
|
||||
private static listeners: any[] = [];
|
||||
private static fileCreationWatcher: vscode.FileSystemWatcher | undefined;
|
||||
private static readConfigPromise: Promise<void> | undefined = undefined;
|
||||
private static project: Project | undefined = undefined;
|
||||
|
||||
public static async init() {
|
||||
await Settings.readConfig();
|
||||
|
||||
const projects = Settings.getProjects();
|
||||
const crntProject = await Extension.getInstance().getState<string | undefined>(
|
||||
ExtensionState.Project.current,
|
||||
'workspace'
|
||||
);
|
||||
|
||||
if (projects.length > 0) {
|
||||
// Get the default project
|
||||
const defaultProject = projects.find((p) => {
|
||||
if (crntProject) {
|
||||
return p.name === crntProject;
|
||||
}
|
||||
return p.default;
|
||||
});
|
||||
if (defaultProject) {
|
||||
Settings.project = defaultProject;
|
||||
} else {
|
||||
Settings.project = projects[0];
|
||||
}
|
||||
}
|
||||
|
||||
Settings.listeners = [];
|
||||
|
||||
if (!Settings.isInitialized) {
|
||||
@@ -71,6 +94,44 @@ export class Settings {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current project
|
||||
* @returns
|
||||
*/
|
||||
public static getProject() {
|
||||
return Settings.project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the project
|
||||
* @param value
|
||||
*/
|
||||
public static setProject(value: string) {
|
||||
Extension.getInstance().setState(ExtensionState.Project.current, value, 'workspace');
|
||||
Settings.project = Settings.getProjects().find((p) => p.name === value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the projects
|
||||
* @returns
|
||||
*/
|
||||
public static getProjects(): Project[] {
|
||||
const settingKey = `${CONFIG_KEY}.${SETTING_PROJECTS}`;
|
||||
|
||||
let projects = [];
|
||||
if (Settings.globalConfig && typeof Settings.globalConfig[settingKey] !== 'undefined') {
|
||||
projects = Settings.globalConfig[settingKey];
|
||||
}
|
||||
|
||||
if (projects.length > 0) {
|
||||
commands.executeCommand('setContext', CONTEXT.projectSwitchEnabled, true);
|
||||
} else {
|
||||
commands.executeCommand('setContext', CONTEXT.projectSwitchEnabled, false);
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the setting is present in the workspace and ask to promote them to the global settings
|
||||
*/
|
||||
@@ -194,6 +255,16 @@ export class Settings {
|
||||
let setting = undefined;
|
||||
const settingKey = `${CONFIG_KEY}.${name}`;
|
||||
|
||||
if (Settings.project) {
|
||||
if (
|
||||
typeof Settings.project.configuration !== 'undefined' &&
|
||||
typeof Settings.project.configuration[settingKey] !== 'undefined'
|
||||
) {
|
||||
setting = Settings.project.configuration[settingKey];
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.globalConfig && typeof Settings.globalConfig[settingKey] !== 'undefined') {
|
||||
setting = Settings.globalConfig[settingKey];
|
||||
}
|
||||
@@ -673,7 +744,10 @@ export class Settings {
|
||||
}
|
||||
// Page folders
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PAGE_FOLDERS)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PAGE_FOLDERS, 'path', configJson);
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PAGE_FOLDERS, 'path', {
|
||||
...configJson,
|
||||
extended: true
|
||||
});
|
||||
}
|
||||
// Placeholders
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PLACEHOLDERS)) {
|
||||
@@ -695,6 +769,10 @@ export class Settings {
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CUSTOM)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CUSTOM, 'id', configJson);
|
||||
}
|
||||
// Projects
|
||||
else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_PROJECTS)) {
|
||||
Settings.updateGlobalConfigArraySetting(SETTING_PROJECTS, 'name', configJson);
|
||||
}
|
||||
// Snippets
|
||||
else if (
|
||||
Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SNIPPETS) &&
|
||||
@@ -707,9 +785,24 @@ export class Settings {
|
||||
configJson,
|
||||
filePath
|
||||
);
|
||||
} else if (typeof configJson === 'string') {
|
||||
Settings.mergeStringArray(`${CONFIG_KEY}.${relSettingName}`, configJson);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the array setting in the global config
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
private static mergeStringArray(key: string, value: string) {
|
||||
// Merge the arrays
|
||||
Settings.globalConfig[key] = [...(Settings.globalConfig[key] || []), value];
|
||||
Settings.globalConfig[key] = Settings.globalConfig[key].filter((item: any, index: number) => {
|
||||
return Settings.globalConfig[key].indexOf(item) === index;
|
||||
}, Settings.globalConfig[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the setting name is equal or starts with the reference setting name
|
||||
* @param value
|
||||
|
||||
+48
-22
@@ -1,21 +1,20 @@
|
||||
import TelemetryReporter, {
|
||||
TelemetryEventMeasurements,
|
||||
TelemetryEventProperties
|
||||
} from '@vscode/extension-telemetry';
|
||||
import { Extension, Settings } from '.';
|
||||
import { EXTENSION_BETA_ID, EXTENSION_ID, SETTING_TELEMETRY_DISABLE } from '../constants';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const METRICS_URL = 'https://frontmatter.codes/api/metrics';
|
||||
|
||||
export class Telemetry {
|
||||
private static instance: Telemetry;
|
||||
private static reporter: TelemetryReporter | null = null;
|
||||
private extTitle: string;
|
||||
private extVersion: string;
|
||||
private events: any[] = [];
|
||||
private timeout: NodeJS.Timeout | undefined;
|
||||
|
||||
private constructor() {
|
||||
const extension = Extension.getInstance();
|
||||
const extTitle = extension.isBetaVersion() ? EXTENSION_BETA_ID : EXTENSION_ID;
|
||||
const extVersion = extension.version;
|
||||
const appKey = `525037e5-70ff-4620-8e52-30e1aef8deee`;
|
||||
|
||||
Telemetry.reporter = new TelemetryReporter(extTitle, extVersion, appKey);
|
||||
this.extTitle = extension.isBetaVersion() ? EXTENSION_BETA_ID : EXTENSION_ID;
|
||||
this.extVersion = extension.version;
|
||||
}
|
||||
|
||||
public static getInstance(): Telemetry {
|
||||
@@ -25,24 +24,51 @@ export class Telemetry {
|
||||
return Telemetry.instance;
|
||||
}
|
||||
|
||||
public static send(
|
||||
eventName: string,
|
||||
properties?: TelemetryEventProperties,
|
||||
measurements?: TelemetryEventMeasurements
|
||||
) {
|
||||
if (!Telemetry.reporter) {
|
||||
Telemetry.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send metrics to our own database
|
||||
* @param eventName
|
||||
* @param properties
|
||||
* @returns
|
||||
*/
|
||||
public static send(eventName: string, properties?: any) {
|
||||
const isDisabled = Settings.get<boolean>(SETTING_TELEMETRY_DISABLE);
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.reporter?.sendTelemetryEvent(eventName, properties, measurements);
|
||||
const instance = Telemetry.getInstance();
|
||||
instance.events.push({
|
||||
name: eventName,
|
||||
extName: instance.extTitle,
|
||||
version: instance.extVersion,
|
||||
properties
|
||||
});
|
||||
|
||||
instance.debounceMetrics();
|
||||
}
|
||||
|
||||
public static dispose() {
|
||||
Telemetry.reporter?.dispose();
|
||||
/**
|
||||
* Debounce the metrics by 1 second
|
||||
*/
|
||||
private async debounceMetrics() {
|
||||
const instance = Telemetry.getInstance();
|
||||
|
||||
// Check if timeout was defined
|
||||
if (instance.timeout) {
|
||||
clearTimeout(instance.timeout);
|
||||
}
|
||||
|
||||
// Set a new timeout
|
||||
instance.timeout = setTimeout(async () => {
|
||||
await fetch(METRICS_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(this.events)
|
||||
});
|
||||
// Reset the events
|
||||
this.events = [];
|
||||
}, 1000) as any as NodeJS.Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,22 @@ import { Dashboard } from '../../commands/Dashboard';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Logger } from '../../helpers/Logger';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export abstract class BaseListener {
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {}
|
||||
public static process(msg: PostMessageData) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: DashboardCommand, data: any) {
|
||||
public static sendMsg(command: DashboardCommand, payload: any) {
|
||||
Logger.info(`Sending message to webview: ${command}`);
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command,
|
||||
data
|
||||
payload
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ExtensionState } from '../../constants';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Extension, Notifications } from '../../helpers';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
export class DashboardListener extends BaseListener {
|
||||
@@ -10,7 +11,7 @@ export class DashboardListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -23,10 +24,10 @@ export class DashboardListener extends BaseListener {
|
||||
Dashboard.reload();
|
||||
break;
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, 'workspace');
|
||||
Extension.getInstance().setState(ExtensionState.PagesView, msg.payload, 'workspace');
|
||||
break;
|
||||
case DashboardMessage.showWarning:
|
||||
Notifications.warning(msg.data);
|
||||
Notifications.warning(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,22 @@ import * as yaml from 'js-yaml';
|
||||
import { DataFileHelper } from '../../helpers';
|
||||
import { existsAsync, readFileAsync, writeFileAsync } from '../../utils';
|
||||
import { mkdirAsync } from '../../utils/mkdirAsync';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.getDataEntries:
|
||||
if (!(msg?.data as DataFile).file) {
|
||||
if (!(msg?.payload as DataFile).file) {
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, []);
|
||||
}
|
||||
|
||||
this.processDataFile(msg?.data);
|
||||
this.processDataFile(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.putDataEntries:
|
||||
this.processDataUpdate(msg?.data);
|
||||
this.processDataUpdate(msg?.payload);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
||||
@@ -4,27 +4,28 @@ import { COMMAND_NAME } from '../../constants';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { CustomScript, Extension } from '../../helpers';
|
||||
import { openFileInEditor } from '../../helpers/openFileInEditor';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
export class ExtensionListener extends BaseListener {
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.openFile:
|
||||
openFileInEditor(msg.data);
|
||||
openFileInEditor(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.initializeProject:
|
||||
commands.executeCommand(COMMAND_NAME.init, SettingsListener.getSettings);
|
||||
break;
|
||||
case DashboardMessage.copyToClipboard:
|
||||
env.clipboard.writeText(msg.data);
|
||||
env.clipboard.writeText(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.runCustomScript:
|
||||
CustomScript.run(msg?.data?.script, msg?.data?.path);
|
||||
CustomScript.run(msg?.payload?.script, msg?.payload?.path);
|
||||
break;
|
||||
case DashboardMessage.setState:
|
||||
this.setState(msg?.data);
|
||||
this.setState(msg?.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Logger } from '../../helpers';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
export class LogListener extends BaseListener {
|
||||
@@ -7,12 +8,12 @@ export class LogListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.logError:
|
||||
Logger.error(msg.data);
|
||||
Logger.error(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,52 +8,53 @@ import { commands, env, Uri } from 'vscode';
|
||||
import { COMMAND_NAME, TelemetryEvent } from '../../constants';
|
||||
import * as os from 'os';
|
||||
import { Folders } from '../../commands';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export class MediaListener extends BaseListener {
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
|
||||
public static async process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static async process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.getMedia:
|
||||
const { page, folder, sorting } = msg?.data;
|
||||
const { page, folder, sorting } = msg?.payload;
|
||||
this.sendMediaFiles(page, folder, sorting);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
Telemetry.send(TelemetryEvent.refreshMedia);
|
||||
MediaHelpers.resetMedia();
|
||||
this.sendMediaFiles(0, msg?.data?.folder);
|
||||
this.sendMediaFiles(0, msg?.payload?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
Telemetry.send(TelemetryEvent.uploadMedia);
|
||||
this.store(msg?.data);
|
||||
this.store(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.deleteMedia:
|
||||
Telemetry.send(TelemetryEvent.deleteMedia);
|
||||
this.delete(msg?.data);
|
||||
this.delete(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.revealMedia:
|
||||
this.openFileInFinder(msg?.data?.file);
|
||||
this.openFileInFinder(msg?.payload?.file);
|
||||
break;
|
||||
case DashboardMessage.insertMedia:
|
||||
Telemetry.send(TelemetryEvent.insertMediaToContent);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.insertFile:
|
||||
Telemetry.send(TelemetryEvent.insertFileToContent);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
Telemetry.send(TelemetryEvent.updateMediaMetadata);
|
||||
this.update(msg.data);
|
||||
this.update(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.createMediaFolder:
|
||||
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.data);
|
||||
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.createHexoAssetFolder:
|
||||
if (msg?.data.hexoAssetFolderPath) {
|
||||
Folders.createFolder(msg?.data.hexoAssetFolderPath);
|
||||
if (msg?.payload.hexoAssetFolderPath) {
|
||||
Folders.createFolder(msg?.payload.hexoAssetFolderPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PostMessageData } from './../../models/PostMessageData';
|
||||
import { basename } from 'path';
|
||||
import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from 'vscode';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
@@ -21,7 +22,7 @@ export class PagesListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static async process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static async process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -41,10 +42,10 @@ export class PagesListener extends BaseListener {
|
||||
this.getPagesData(true);
|
||||
break;
|
||||
case DashboardMessage.searchPages:
|
||||
this.searchPages(msg.data);
|
||||
this.searchPages(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.deleteFile:
|
||||
this.deletePage(msg.data);
|
||||
this.deletePage(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -175,6 +176,7 @@ export class PagesListener extends BaseListener {
|
||||
this.sendMsg(DashboardCommand.searchReady, true);
|
||||
|
||||
await this.createSearchIndex(pages);
|
||||
this.sendMsg(DashboardCommand.loading, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
import { join } from 'path';
|
||||
import { commands, Uri } from 'vscode';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { COMMAND_NAME, SETTING_CONTENT_STATIC_FOLDER, SETTING_FRAMEWORK_ID } from '../../constants';
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
ExtensionState,
|
||||
SETTING_CONTENT_STATIC_FOLDER,
|
||||
SETTING_FRAMEWORK_ID,
|
||||
SETTING_PREVIEW_HOST
|
||||
} from '../../constants';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { DashboardSettings, Settings } from '../../helpers';
|
||||
import { DashboardSettings, Extension, Settings } from '../../helpers';
|
||||
import { FrameworkDetector } from '../../helpers/FrameworkDetector';
|
||||
import { Framework } from '../../models';
|
||||
import { Framework, PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { Cache } from '../../commands/Cache';
|
||||
import { Preview } from '../../commands';
|
||||
import { GitListener } from '../general';
|
||||
import { DataListener } from '../panel';
|
||||
import { MarkdownFoldingProvider } from '../../providers/MarkdownFoldingProvider';
|
||||
import { ModeSwitch } from '../../services/ModeSwitch';
|
||||
import { PagesListener } from './PagesListener';
|
||||
|
||||
export class SettingsListener extends BaseListener {
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -22,14 +35,44 @@ export class SettingsListener extends BaseListener {
|
||||
this.getSettings();
|
||||
break;
|
||||
case DashboardMessage.updateSetting:
|
||||
this.update(msg.data);
|
||||
this.update(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.setFramework:
|
||||
this.setFramework(msg?.data);
|
||||
this.setFramework(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.addFolder:
|
||||
this.addFolder(msg?.data);
|
||||
this.addFolder(msg?.payload);
|
||||
break;
|
||||
case DashboardMessage.switchProject:
|
||||
this.switchProject(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static async switchProject(project: string) {
|
||||
if (project) {
|
||||
this.sendMsg(DashboardCommand.loading, true);
|
||||
Settings.setProject(project);
|
||||
await Cache.clear(false);
|
||||
|
||||
// Clear out the media folder
|
||||
await Extension.getInstance().setState<string | undefined>(
|
||||
ExtensionState.SelectedFolder,
|
||||
undefined,
|
||||
'workspace'
|
||||
);
|
||||
|
||||
Preview.init();
|
||||
GitListener.init();
|
||||
|
||||
SettingsListener.getSettings(true);
|
||||
DataListener.getFoldersAndFiles();
|
||||
MarkdownFoldingProvider.triggerHighlighting(true);
|
||||
ModeSwitch.register();
|
||||
|
||||
// Update pages
|
||||
PagesListener.startWatchers();
|
||||
PagesListener.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +105,18 @@ export class SettingsListener extends BaseListener {
|
||||
|
||||
if (frameworkId) {
|
||||
const allFrameworks = FrameworkDetector.getAll();
|
||||
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
|
||||
const framework: Framework | undefined = allFrameworks.find(
|
||||
(f: Framework) => f.name === frameworkId
|
||||
);
|
||||
if (framework) {
|
||||
if (framework.static) {
|
||||
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
|
||||
}
|
||||
|
||||
if (framework.server) {
|
||||
await Settings.update(SETTING_PREVIEW_HOST, framework.server, true);
|
||||
}
|
||||
|
||||
await FrameworkDetector.checkDefaultSettings(framework);
|
||||
} else {
|
||||
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, '', true);
|
||||
|
||||
@@ -4,24 +4,24 @@ import { Dashboard } from '../../commands/Dashboard';
|
||||
import { SETTING_CONTENT_SNIPPETS, TelemetryEvent } from '../../constants';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Notifications, Settings, Telemetry } from '../../helpers';
|
||||
import { Snippets } from '../../models';
|
||||
import { PostMessageData, Snippets } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { SettingsListener } from './SettingsListener';
|
||||
|
||||
export class SnippetListener extends BaseListener {
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.addSnippet:
|
||||
this.addSnippet(msg.data);
|
||||
this.addSnippet(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.updateSnippet:
|
||||
this.updateSnippet(msg.data);
|
||||
this.updateSnippet(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.insertSnippet:
|
||||
Telemetry.send(TelemetryEvent.insertContentSnippet);
|
||||
this.insertSnippet(msg.data);
|
||||
this.insertSnippet(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Settings, TaxonomyHelper } from '../../helpers';
|
||||
import { CustomTaxonomy } from '../../models';
|
||||
import { CustomTaxonomy, PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
export class TaxonomyListener extends BaseListener {
|
||||
@@ -16,7 +16,7 @@ export class TaxonomyListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -24,22 +24,22 @@ export class TaxonomyListener extends BaseListener {
|
||||
this.getData();
|
||||
break;
|
||||
case DashboardMessage.editTaxonomy:
|
||||
TaxonomyHelper.rename(msg.data);
|
||||
TaxonomyHelper.rename(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.mergeTaxonomy:
|
||||
TaxonomyHelper.merge(msg.data);
|
||||
TaxonomyHelper.merge(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.deleteTaxonomy:
|
||||
TaxonomyHelper.delete(msg.data);
|
||||
TaxonomyHelper.delete(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.moveTaxonomy:
|
||||
TaxonomyHelper.move(msg.data);
|
||||
TaxonomyHelper.move(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.addToTaxonomy:
|
||||
TaxonomyHelper.addTaxonomy(msg.data);
|
||||
TaxonomyHelper.addTaxonomy(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.createTaxonomy:
|
||||
TaxonomyHelper.createNew(msg.data);
|
||||
TaxonomyHelper.createNew(msg.payload);
|
||||
break;
|
||||
case DashboardMessage.importTaxonomy:
|
||||
commands.executeCommand(COMMAND_NAME.exportTaxonomy);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Telemetry } from '../../helpers/Telemetry';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
export class TelemetryListener extends BaseListener {
|
||||
@@ -7,12 +8,12 @@ export class TelemetryListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case DashboardMessage.sendTelemetry:
|
||||
Telemetry.send(msg.data.event, msg.data.properties, msg.data.metrics);
|
||||
Telemetry.send(msg.payload.event, msg.payload.properties);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { GeneralCommands } from './../../constants/GeneralCommands';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { ExplorerView } from '../../explorerView/ExplorerView';
|
||||
import { Extension } from '../../helpers';
|
||||
import { Logger } from '../../helpers/Logger';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { commands, Uri } from 'vscode';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export abstract class BaseListener {
|
||||
public static process(msg: { command: DashboardMessage | CommandToCode | string; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
switch (msg.command) {
|
||||
case GeneralCommands.toVSCode.openLink:
|
||||
if (msg.data) {
|
||||
commands.executeCommand('vscode.open', Uri.parse(msg.data));
|
||||
if (msg.payload) {
|
||||
commands.executeCommand('vscode.open', Uri.parse(msg.payload));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -23,14 +22,14 @@ export abstract class BaseListener {
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: string, data: any) {
|
||||
public static sendMsg(command: string, payload: any) {
|
||||
Logger.info(`Sending message to webview (panel&dashboard): ${command}`);
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extPath);
|
||||
|
||||
panel.sendMessage({ command: command as any, data });
|
||||
panel.sendMessage({ command: command as any, payload });
|
||||
|
||||
Dashboard.postWebviewMessage({ command: command as any, data });
|
||||
Dashboard.postWebviewMessage({ command: command as any, payload });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
SETTING_GIT_SUBMODULE_BRANCH,
|
||||
SETTING_GIT_SUBMODULE_FOLDER,
|
||||
SETTING_GIT_SUBMODULE_PULL,
|
||||
SETTING_GIT_SUBMODULE_PUSH
|
||||
} from './../../constants/settings';
|
||||
import { Settings } from './../../helpers/SettingsHelper';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { ExplorerView } from '../../explorerView/ExplorerView';
|
||||
@@ -5,6 +11,7 @@ import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
Logger,
|
||||
Notifications,
|
||||
processKnownPlaceholders,
|
||||
Telemetry
|
||||
} from '../../helpers';
|
||||
@@ -20,11 +27,16 @@ import {
|
||||
} from '../../constants';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { commands } from 'vscode';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export class GitListener {
|
||||
private static isRegistered: boolean = false;
|
||||
private static client: SimpleGit | null = null;
|
||||
private static subClient: SimpleGit | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the listener
|
||||
*/
|
||||
public static async init() {
|
||||
let isEnabled = false;
|
||||
const gitEnabled = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
@@ -49,7 +61,7 @@ export class GitListener {
|
||||
* Process the messages
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: string; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
switch (msg.command) {
|
||||
case GeneralCommands.toVSCode.gitSync:
|
||||
this.sync();
|
||||
@@ -57,6 +69,9 @@ export class GitListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the sync
|
||||
*/
|
||||
public static async sync() {
|
||||
try {
|
||||
this.sendMsg(GeneralCommands.toWebview.gitSyncingStart, {});
|
||||
@@ -73,6 +88,10 @@ export class GitListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current workspace is a git repository
|
||||
* @returns
|
||||
*/
|
||||
public static async isGitRepository() {
|
||||
const git = this.getClient();
|
||||
if (!git) {
|
||||
@@ -88,16 +107,46 @@ export class GitListener {
|
||||
return isRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the changes from the remote
|
||||
* @returns
|
||||
*/
|
||||
private static async pull() {
|
||||
const git = this.getClient();
|
||||
if (!git) {
|
||||
return;
|
||||
}
|
||||
|
||||
const submoduleFolder = Settings.get<string>(SETTING_GIT_SUBMODULE_FOLDER);
|
||||
const submoduleBranch = Settings.get<string>(SETTING_GIT_SUBMODULE_BRANCH);
|
||||
const submodulePull = Settings.get<boolean>(SETTING_GIT_SUBMODULE_PULL);
|
||||
|
||||
if (submoduleFolder) {
|
||||
const absFolderPath = Folders.getAbsFolderPath(submoduleFolder);
|
||||
const subGit = this.getClient(absFolderPath);
|
||||
if (subGit && submoduleBranch) {
|
||||
await subGit.checkout(submoduleBranch);
|
||||
}
|
||||
} else {
|
||||
if (submoduleBranch) {
|
||||
Logger.info(`Checking out the branch ${submoduleBranch} for submodules`);
|
||||
await git.subModule(['foreach', 'git', 'checkout', submoduleBranch]);
|
||||
}
|
||||
}
|
||||
|
||||
if (submodulePull) {
|
||||
Logger.info(`Pulling from remote with submodules`);
|
||||
await git.subModule(['update', '--remote', '--merge']);
|
||||
}
|
||||
|
||||
Logger.info(`Pulling from remote`);
|
||||
await git.pull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the changes to the remote
|
||||
* @returns
|
||||
*/
|
||||
private static async push() {
|
||||
let commitMsg = Settings.get<string>(SETTING_GIT_COMMIT_MSG);
|
||||
|
||||
@@ -112,48 +161,107 @@ export class GitListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const submoduleFolder = Settings.get<string>(SETTING_GIT_SUBMODULE_FOLDER);
|
||||
const submodulePush = Settings.get<boolean>(SETTING_GIT_SUBMODULE_PUSH);
|
||||
|
||||
if (submoduleFolder) {
|
||||
const absFolderPath = Folders.getAbsFolderPath(submoduleFolder);
|
||||
const subGit = this.getClient(absFolderPath);
|
||||
if (subGit && submodulePush) {
|
||||
try {
|
||||
const status = await subGit.status();
|
||||
// Check if anything changed
|
||||
if (status.files.length > 0) {
|
||||
await subGit.raw(['add', '.', '-A']);
|
||||
await subGit.commit(commitMsg || 'Synced by Front Matter');
|
||||
}
|
||||
await subGit.push();
|
||||
} catch (e) {
|
||||
Notifications.error(`Failed to push submodules. Please check the logs for more details.`);
|
||||
Logger.error((e as Error).message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (submodulePush) {
|
||||
Logger.info(`Pushing to remote with submodules`);
|
||||
|
||||
try {
|
||||
const status = await git.subModule(['foreach', 'git', 'status', '--porcelain', '-u']);
|
||||
const lines = status.split('\n').filter((line) => line.trim() !== '');
|
||||
|
||||
// First line is the submodule folder name
|
||||
if (lines.length > 1) {
|
||||
await git.subModule(['foreach', 'git', 'add', '.', '-A']);
|
||||
await git.subModule([
|
||||
'foreach',
|
||||
'git',
|
||||
'commit',
|
||||
'-m',
|
||||
commitMsg || 'Synced by Front Matter'
|
||||
]);
|
||||
await git.subModule(['foreach', 'git', 'push']);
|
||||
}
|
||||
} catch (e) {
|
||||
Notifications.error(`Failed to push submodules. Please check the logs for more details.`);
|
||||
Logger.error((e as Error).message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info(`Pushing to remote`);
|
||||
|
||||
const status = await git.status();
|
||||
|
||||
for (const file of status.not_added) {
|
||||
await git.add(file);
|
||||
if (status.files.length > 0) {
|
||||
await git.raw(['add', '.', '-A']);
|
||||
await git.commit(commitMsg || 'Synced by Front Matter');
|
||||
}
|
||||
for (const file of status.modified) {
|
||||
await git.add(file);
|
||||
}
|
||||
for (const file of status.deleted) {
|
||||
await git.add(file);
|
||||
}
|
||||
|
||||
await git.commit(commitMsg || 'Synced by Front Matter');
|
||||
|
||||
await git.push();
|
||||
}
|
||||
|
||||
private static getClient() {
|
||||
if (this.client) {
|
||||
/**
|
||||
* Get the git client
|
||||
* @param submoduleFolder
|
||||
* @returns
|
||||
*/
|
||||
private static getClient(submoduleFolder: string = ''): SimpleGit | null {
|
||||
if (!submoduleFolder && this.client) {
|
||||
return this.client;
|
||||
} else if (submoduleFolder && this.subClient) {
|
||||
return this.subClient;
|
||||
}
|
||||
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const options = {
|
||||
baseDir: wsFolder?.fsPath || '',
|
||||
baseDir: submoduleFolder || wsFolder?.fsPath || '',
|
||||
binary: 'git',
|
||||
maxConcurrentProcesses: 6
|
||||
};
|
||||
|
||||
this.client = simpleGit(options);
|
||||
return this.client;
|
||||
if (submoduleFolder) {
|
||||
this.subClient = simpleGit(options);
|
||||
return this.subClient;
|
||||
} else {
|
||||
this.client = simpleGit(options);
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
|
||||
private static sendMsg(command: string, data: any) {
|
||||
/**
|
||||
* Send the message to the webview
|
||||
* @param command
|
||||
* @param payload
|
||||
*/
|
||||
private static sendMsg(command: string, payload: any) {
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extPath);
|
||||
|
||||
panel.sendMessage({ command: command as any, data });
|
||||
panel.sendMessage({ command: command as any, payload });
|
||||
|
||||
Dashboard.postWebviewMessage({ command: command as any, data });
|
||||
Dashboard.postWebviewMessage({ command: command as any, payload });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ModeSwitch } from './../../services/ModeSwitch';
|
||||
import { CONTEXT, FEATURE_FLAG, GeneralCommands, SETTING_GLOBAL_MODES } from '../../constants';
|
||||
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
|
||||
import { Mode } from '../../models';
|
||||
import { Mode, PostMessageData } from '../../models';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { Settings } from '../../helpers';
|
||||
@@ -12,7 +12,7 @@ export class ModeListener extends BaseListener {
|
||||
* Process the messages
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage | CommandToCode; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg as any);
|
||||
|
||||
switch (msg.command) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Article } from '../../commands';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
@@ -8,7 +9,7 @@ export class ArticleListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -16,7 +17,7 @@ export class ArticleListener extends BaseListener {
|
||||
Article.updateSlug();
|
||||
break;
|
||||
case CommandToCode.generateSlug:
|
||||
this.generateSlug(msg.data);
|
||||
this.generateSlug(msg.payload);
|
||||
break;
|
||||
case CommandToCode.updateLastMod:
|
||||
Article.setLastModifiedDate();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Extension } from './../../helpers/Extension';
|
||||
import { ExplorerView } from './../../explorerView/ExplorerView';
|
||||
import { Logger } from '../../helpers';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export abstract class BaseListener {
|
||||
public static process(msg: { command: CommandToCode; data: any }) {}
|
||||
public static process(msg: PostMessageData) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: Command, data: any) {
|
||||
public static sendMsg(command: Command, payload: any) {
|
||||
Logger.info(`Sending message to panel: ${command}`);
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
@@ -20,7 +20,7 @@ export abstract class BaseListener {
|
||||
|
||||
panel.sendMessage({
|
||||
command,
|
||||
data
|
||||
payload
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
import { Article } from '../../commands';
|
||||
import { ParsedFrontMatter } from '../../parsers';
|
||||
import { processKnownPlaceholders } from '../../helpers/PlaceholderHelper';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { encodeEmoji } from '../../utils';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
@@ -28,7 +30,7 @@ export class DataListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: CommandToCode; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -43,16 +45,16 @@ export class DataListener extends BaseListener {
|
||||
commands.executeCommand(COMMAND_NAME.createTemplate);
|
||||
break;
|
||||
case CommandToCode.updateMetadata:
|
||||
this.updateMetadata(msg.data);
|
||||
this.updateMetadata(msg.payload);
|
||||
break;
|
||||
case CommandToCode.frameworkCommand:
|
||||
this.openTerminalWithCommand(msg.data.command);
|
||||
this.openTerminalWithCommand(msg.payload.command);
|
||||
break;
|
||||
case CommandToCode.stopServer:
|
||||
this.stopServer();
|
||||
break;
|
||||
case CommandToCode.updatePlaceholder:
|
||||
this.updatePlaceholder(msg?.data?.field, msg?.data?.value, msg?.data?.title);
|
||||
this.updatePlaceholder(msg?.payload?.field, msg?.payload?.value, msg?.payload?.title);
|
||||
break;
|
||||
case CommandToCode.generateContentType:
|
||||
commands.executeCommand(COMMAND_NAME.generateContentType);
|
||||
@@ -64,7 +66,7 @@ export class DataListener extends BaseListener {
|
||||
commands.executeCommand(COMMAND_NAME.setContentType);
|
||||
break;
|
||||
case CommandToCode.getDataEntries:
|
||||
this.getDataFileEntries(msg.data);
|
||||
this.getDataFileEntries(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -134,9 +136,7 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
// if (JSON.stringify(DataListener.lastMetadataUpdate) !== JSON.stringify(updatedMetadata)) {
|
||||
this.sendMsg(Command.metadata, updatedMetadata);
|
||||
// }
|
||||
|
||||
DataListener.lastMetadataUpdate = updatedMetadata;
|
||||
}
|
||||
@@ -174,6 +174,7 @@ export class DataListener extends BaseListener {
|
||||
const dateFields = contentType.fields.filter((f) => f.type === 'datetime');
|
||||
const imageFields = contentType.fields.filter((f) => f.type === 'image' && f.multiple);
|
||||
const fileFields = contentType.fields.filter((f) => f.type === 'file' && f.multiple);
|
||||
const fieldsWithEmojiEncoding = contentType.fields.filter((f) => f.encodeEmoji);
|
||||
|
||||
// Support multi-level fields
|
||||
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
|
||||
@@ -209,6 +210,9 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (fieldsWithEmojiEncoding.some((f) => f.name === field)) {
|
||||
value = encodeEmoji(value);
|
||||
}
|
||||
parentObj[field] = value;
|
||||
}
|
||||
|
||||
@@ -298,6 +302,11 @@ export class DataListener extends BaseListener {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check if the file is a valid article
|
||||
if (!ArticleHelper.isSupportedFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
this.pushMetadata(article!.data);
|
||||
|
||||
@@ -5,15 +5,15 @@ import * as os from 'os';
|
||||
import { exec } from 'child_process';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { COMMAND_NAME } from '../../constants';
|
||||
import { SettingsListener } from '.';
|
||||
import { openFileInEditor } from '../../helpers';
|
||||
import { PostMessageData } from '../../models';
|
||||
|
||||
export class ExtensionListener extends BaseListener {
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -24,7 +24,7 @@ export class ExtensionListener extends BaseListener {
|
||||
this.openFolder();
|
||||
break;
|
||||
case CommandToCode.openInEditor:
|
||||
openFileInEditor(msg.data);
|
||||
openFileInEditor(msg.payload);
|
||||
break;
|
||||
case CommandToCode.initProject:
|
||||
this.initialize();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { commands, window } from 'vscode';
|
||||
import { Dashboard } from '../../commands/Dashboard';
|
||||
import { COMMAND_NAME } from '../../constants';
|
||||
import { ImageHelper } from '../../helpers';
|
||||
import { DashboardData } from '../../models';
|
||||
import { DashboardData, PostMessageData } from '../../models';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
@@ -13,7 +13,7 @@ export class MediaListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -24,7 +24,7 @@ export class MediaListener extends BaseListener {
|
||||
this.selectMedia(msg);
|
||||
break;
|
||||
case CommandToCode.getImageUrl:
|
||||
this.generateUrl(msg.data);
|
||||
this.generateUrl(msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,10 @@ export class MediaListener extends BaseListener {
|
||||
/**
|
||||
* Select a media file
|
||||
*/
|
||||
private static async selectMedia(msg: { data: any }) {
|
||||
private static async selectMedia(msg: PostMessageData) {
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: 'media',
|
||||
data: msg.data
|
||||
data: msg.payload
|
||||
} as DashboardData);
|
||||
this.getMediaSelection();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SETTING_CUSTOM_SCRIPTS } from '../../constants';
|
||||
import { CustomScript, Settings } from '../../helpers';
|
||||
import { CustomScript as ICustomScript } from '../../models';
|
||||
import { CustomScript as ICustomScript, PostMessageData } from '../../models';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
|
||||
@@ -9,7 +9,7 @@ export class ScriptListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -23,11 +23,11 @@ export class ScriptListener extends BaseListener {
|
||||
* Run a custom script
|
||||
* @param msg
|
||||
*/
|
||||
private static runCustomScript(msg: { command: string; data: any }) {
|
||||
private static runCustomScript(msg: PostMessageData) {
|
||||
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
|
||||
|
||||
if (msg?.data?.title && msg?.data?.script && scripts) {
|
||||
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
|
||||
if (msg?.payload?.title && msg?.payload?.script && scripts) {
|
||||
const customScript = scripts.find((s: ICustomScript) => s.title === msg.payload.title);
|
||||
if (customScript?.script && customScript?.title) {
|
||||
CustomScript.run(customScript);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../constants';
|
||||
import { Extension, Settings } from '../../helpers';
|
||||
import { PanelSettings } from '../../helpers/PanelSettings';
|
||||
import { PostMessageData } from '../../models';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
@@ -18,7 +19,7 @@ export class SettingsListener extends BaseListener {
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: CommandToCode; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
@@ -32,19 +33,19 @@ export class SettingsListener extends BaseListener {
|
||||
this.toggleWritingSettings();
|
||||
break;
|
||||
case CommandToCode.updateModifiedUpdating:
|
||||
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false);
|
||||
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.payload || false);
|
||||
break;
|
||||
case CommandToCode.updateFmHighlight:
|
||||
this.updateSetting(
|
||||
SETTING_CONTENT_FRONTMATTER_HIGHLIGHT,
|
||||
msg.data !== null && msg.data !== undefined ? msg.data : false
|
||||
msg.payload !== null && msg.payload !== undefined ? msg.payload : false
|
||||
);
|
||||
break;
|
||||
case CommandToCode.updatePreviewUrl:
|
||||
this.updateSetting(SETTING_PREVIEW_HOST, msg.data || '');
|
||||
this.updateSetting(SETTING_PREVIEW_HOST, msg.payload || '');
|
||||
break;
|
||||
case CommandToCode.updateStartCommand:
|
||||
this.updateSetting(SETTING_FRAMEWORK_START, msg.data || '');
|
||||
this.updateSetting(SETTING_FRAMEWORK_START, msg.payload || '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,131 @@
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { TagType } from '../../panelWebView/TagType';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { window } from 'vscode';
|
||||
import { ArticleHelper, Settings } from '../../helpers';
|
||||
import { BlockFieldData, CustomTaxonomyData, TaxonomyType } from '../../models';
|
||||
import { authentication, window } from 'vscode';
|
||||
import { ArticleHelper, Extension, Settings } from '../../helpers';
|
||||
import { BlockFieldData, CustomTaxonomyData, PostMessageData, TaxonomyType } from '../../models';
|
||||
import { DataListener } from '.';
|
||||
import { SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from '../../constants';
|
||||
import {
|
||||
DefaultFields,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_TITLE_FIELD,
|
||||
SETTING_TAXONOMY_CATEGORIES,
|
||||
SETTING_TAXONOMY_TAGS
|
||||
} from '../../constants';
|
||||
import { SponsorAi } from '../../services/SponsorAI';
|
||||
import { ExplorerView } from '../../explorerView/ExplorerView';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
|
||||
export class TaxonomyListener extends BaseListener {
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any; data: any }) {
|
||||
public static process(msg: PostMessageData) {
|
||||
super.process(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case CommandToCode.updateTags:
|
||||
this.updateTags(
|
||||
msg.data?.fieldName,
|
||||
msg.data?.values || [],
|
||||
msg.data?.parents || [],
|
||||
msg.data?.blockData
|
||||
msg.payload?.fieldName,
|
||||
msg.payload?.values || [],
|
||||
msg.payload?.parents || [],
|
||||
msg.payload?.blockData
|
||||
);
|
||||
break;
|
||||
case CommandToCode.updateCategories:
|
||||
this.updateTags(
|
||||
msg.data?.fieldName,
|
||||
msg.data?.values || [],
|
||||
msg.data?.parents || [],
|
||||
msg.data?.blockData
|
||||
msg.payload?.fieldName,
|
||||
msg.payload?.values || [],
|
||||
msg.payload?.parents || [],
|
||||
msg.payload?.blockData
|
||||
);
|
||||
break;
|
||||
case CommandToCode.updateKeywords:
|
||||
this.updateTags(
|
||||
TagType.keywords.toLowerCase(),
|
||||
msg.data?.values || [],
|
||||
msg.data?.parents || [],
|
||||
msg.data?.blockData
|
||||
msg.payload?.values || [],
|
||||
msg.payload?.parents || [],
|
||||
msg.payload?.blockData
|
||||
);
|
||||
break;
|
||||
case CommandToCode.updateCustomTaxonomy:
|
||||
this.updateCustomTaxonomy(msg.data);
|
||||
this.updateCustomTaxonomy(msg.payload);
|
||||
break;
|
||||
case CommandToCode.addTagToSettings:
|
||||
this.addTags(TagType.tags, msg.data);
|
||||
this.addTags(TagType.tags, msg.payload);
|
||||
break;
|
||||
case CommandToCode.addCategoryToSettings:
|
||||
this.addTags(TagType.categories, msg.data);
|
||||
this.addTags(TagType.categories, msg.payload);
|
||||
break;
|
||||
case CommandToCode.addToCustomTaxonomy:
|
||||
this.addCustomTaxonomy(msg.data);
|
||||
this.addCustomTaxonomy(msg.payload);
|
||||
break;
|
||||
case CommandToCode.aiSuggestTaxonomy:
|
||||
this.aiSuggestTaxonomy(msg.command, msg.requestId, msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static async aiSuggestTaxonomy(command: string, requestId?: string, type?: TagType) {
|
||||
if (!command || !requestId || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extPath);
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
error: 'No active editor'
|
||||
} as MessageHandlerData<string>);
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!article || !article.data) {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
error: 'No article data'
|
||||
} as MessageHandlerData<string>);
|
||||
return;
|
||||
}
|
||||
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], { silent: true });
|
||||
if (!githubAuth || !githubAuth.accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField =
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
|
||||
const suggestions = await SponsorAi.getTaxonomySuggestions(
|
||||
githubAuth.accessToken,
|
||||
article.data[titleField] || '',
|
||||
article.data[descriptionField] || '',
|
||||
type
|
||||
);
|
||||
|
||||
if (!suggestions) {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
error: 'No article data'
|
||||
} as MessageHandlerData<string>);
|
||||
}
|
||||
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
payload: suggestions || []
|
||||
} as MessageHandlerData<string[]>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tags in the current document
|
||||
* @param tagType
|
||||
|
||||
@@ -7,4 +7,6 @@ export interface ContentFolder {
|
||||
filePrefix?: string;
|
||||
contentTypes?: string[];
|
||||
originalPath?: string;
|
||||
$schema?: string;
|
||||
extended?: boolean;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user