Merge pull request #871 from estruyf/beta

Prep v10.5.0 release
This commit is contained in:
Elio Struyf
2024-10-21 16:34:04 +02:00
committed by GitHub
141 changed files with 7012 additions and 1022 deletions
+3 -1
View File
@@ -13,6 +13,8 @@
"no-unused-expressions": "error",
"curly": "error",
"class-methods-use-this": "warn",
"no-console": "warn"
"no-console": "warn",
"@typescript-eslint/no-empty-interface": "off",
"no-extra-boolean-cast": "off"
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
cache: 'npm'
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
cache: 'npm'
@@ -69,7 +69,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
cache: 'npm'
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
cache: 'npm'
@@ -69,7 +69,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
cache: 'npm'
+15
View File
@@ -1,5 +1,20 @@
# Change Log
## [10.5.0] - 2024-10-21 - [Release notes](https://beta.frontmatter.codes/updates/v10.5.0)
### 🎨 Enhancements
- [#840](https://github.com/estruyf/vscode-front-matter/issues/840): Added the `excludePaths` option for the content folder settings
- [#850](https://github.com/estruyf/vscode-front-matter/issues/850): Extended the i18n/language button to open or create new language files (thanks to [Dennis Zoma](https://github.com/wottpal))
- [#851](https://github.com/estruyf/vscode-front-matter/issues/851): Added `sameContentLocale` option to `contentRelationship` field (thanks to [Dennis Zoma](https://github.com/wottpal))
- [#866](https://github.com/estruyf/vscode-front-matter/issues/866): Support Markdown in the WYSIWYG `string` field
### 🐞 Fixes
- [#858](https://github.com/estruyf/vscode-front-matter/issues/858): Fix button styling on the data screen
- [#860](https://github.com/estruyf/vscode-front-matter/issues/860): Fix typo on the data screen
- [#870](https://github.com/estruyf/vscode-front-matter/issues/870): Fix data number field styling
## [10.4.1] - 2024-09-27
- [#855](https://github.com/estruyf/vscode-front-matter/issues/855): Fix in panel sections
+7 -2
View File
@@ -144,7 +144,7 @@
"dashboard.dataView.dataView.selectDataFolder": "Select data folder",
"dashboard.dataView.dataView.closeSelectedDataFile": "Close data file",
"dashboard.dataView.emptyView.heading": "Select your date type first",
"dashboard.dataView.emptyView.heading": "Select your data type first",
"dashboard.dataView.emptyView.heading.create": "Start by creating a new data file",
"dashboard.dataView.sortableItem.editButton.title": "Edit \"{0}\"",
@@ -583,8 +583,13 @@
"commands.i18n.create.success.created": "Created \"{0}\" i18n content file.",
"commands.i18n.create.quickPick.title": "Create content for locale",
"commands.i18n.create.quickPick.placeHolder": "To which locale do you want to create a new content?",
"commands.i18n.createOrOpen.quickPick.title": "Open or create translation",
"commands.i18n.createOrOpen.quickPick.category.existing": "Existing translations",
"commands.i18n.createOrOpen.quickPick.action.open": "Open \"{0}\"",
"commands.i18n.createOrOpen.quickPick.category.new": "New translations",
"commands.i18n.createOrOpen.quickPick.action.create": "Create \"{0}\"",
"commands.i18n.translate.progress.title": "Translating content...",
"commands.preview.panel.title": "Preview: {0}",
"commands.preview.askUserToPickFolder.title": "Select the folder of the article to preview",
+5694 -171
View File
File diff suppressed because it is too large Load Diff
+189 -190
View File
@@ -3,15 +3,14 @@
"displayName": "Front Matter CMS",
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "10.4.1",
"version": "10.5.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
"color": "#0e131f",
"theme": "dark"
},
"badges": [
{
"badges": [{
"description": "version",
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
"href": "https://github.com/estruyf/vscode-front-matter"
@@ -71,8 +70,7 @@
"**/.frontmatter/config/*.json": "jsonc"
}
},
"keybindings": [
{
"keybindings": [{
"command": "frontMatter.dashboard",
"key": "alt+d"
},
@@ -96,23 +94,19 @@
}
],
"viewsContainers": {
"activitybar": [
{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}
]
"activitybar": [{
"id": "frontmatter-explorer",
"title": "FM",
"icon": "$(fm-logo)"
}]
},
"views": {
"frontmatter-explorer": [
{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}
]
"frontmatter-explorer": [{
"id": "frontMatter.explorer",
"name": "Front Matter",
"icon": "$(fm-logo)",
"type": "webview"
}]
},
"configuration": {
"title": "%settings.configuration.title%",
@@ -180,8 +174,7 @@
"frontMatter.content.defaultFileType": {
"type": "string",
"default": "md",
"oneOf": [
{
"oneOf": [{
"enum": [
"md",
"mdx"
@@ -197,8 +190,7 @@
"frontMatter.content.defaultSorting": {
"type": "string",
"default": "",
"oneOf": [
{
"oneOf": [{
"enum": [
"LastModifiedAsc",
"LastModifiedDesc",
@@ -291,6 +283,14 @@
"default": false,
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description%"
},
"excludePaths": {
"type": "array",
"default": false,
"description": "%setting.frontMatter.content.pageFolders.items.properties.excludePaths.description%",
"items": {
"type": "string"
}
},
"previewPath": {
"type": [
"null",
@@ -550,8 +550,7 @@
"categories"
],
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
"items": [
{
"items": [{
"type": "string",
"enum": [
"contentFolders",
@@ -625,8 +624,7 @@
"command": {
"$id": "#scriptCommand",
"type": "string",
"anyOf": [
{
"anyOf": [{
"enum": [
"node",
"bash",
@@ -822,8 +820,7 @@
"title",
"file"
],
"anyOf": [
{
"anyOf": [{
"required": [
"schema"
]
@@ -891,8 +888,7 @@
"id",
"path"
],
"anyOf": [
{
"anyOf": [{
"required": [
"schema"
]
@@ -1133,29 +1129,26 @@
}
}
},
"default": [
{
"name": "default",
"fileTypes": null,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}
],
"default": [{
"name": "default",
"fileTypes": null,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Caption",
"name": "caption",
"type": "string"
},
{
"title": "Alt text",
"name": "alt",
"type": "string"
}
]
}],
"scope": "Media"
},
"frontMatter.media.supportedMimeTypes": {
@@ -1258,8 +1251,7 @@
"fileType": {
"type": "string",
"default": "",
"oneOf": [
{
"oneOf": [{
"enum": [
"md",
"mdx"
@@ -1374,7 +1366,14 @@
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description%"
},
"wysiwyg": {
"type": "boolean",
"type": [
"boolean",
"string"
],
"enum": [
"html",
"markdown"
],
"default": false,
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description%"
},
@@ -1398,8 +1397,7 @@
"default": "",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
"not": {
"anyOf": [
{
"anyOf": [{
"const": ""
},
{
@@ -1543,6 +1541,11 @@
"default": "path",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description%"
},
"sameContentLocale": {
"type": "boolean",
"default": true,
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description%"
},
"when": {
"type": "object",
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description%",
@@ -1600,8 +1603,7 @@
"type",
"name"
],
"allOf": [
{
"allOf": [{
"if": {
"properties": {
"type": {
@@ -1813,51 +1815,48 @@
"fields"
]
},
"default": [
{
"name": "default",
"pageBundle": false,
"fields": [
{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}
],
"default": [{
"name": "default",
"pageBundle": false,
"fields": [{
"title": "Title",
"name": "title",
"type": "string"
},
{
"title": "Description",
"name": "description",
"type": "string"
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
},
{
"title": "Is in draft",
"name": "draft",
"type": "boolean"
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
}
]
}],
"scope": "Taxonomy"
},
"frontMatter.taxonomy.customTaxonomy": {
@@ -1870,8 +1869,7 @@
"type": "string",
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
"not": {
"anyOf": [
{
"anyOf": [{
"const": ""
},
{
@@ -2070,8 +2068,7 @@
}
}
},
"commands": [
{
"commands": [{
"command": "frontMatter.project.switch",
"title": "%command.frontMatter.project.switch%",
"category": "Front Matter",
@@ -2393,6 +2390,15 @@
"title": "%command.frontMatter.cache.clear%",
"category": "Front Matter"
},
{
"command": "frontMatter.i18n.createOrOpen",
"title": "%command.frontMatter.i18n.createOrOpen%",
"category": "Front Matter",
"icon": {
"light": "assets/icons/i18n-light.svg",
"dark": "assets/icons/i18n-dark.svg"
}
},
{
"command": "frontMatter.i18n.create",
"title": "%command.frontMatter.i18n.create%",
@@ -2403,21 +2409,16 @@
}
}
],
"submenus": [
{
"id": "frontmatter.submenu",
"label": "Front Matter"
}
],
"submenus": [{
"id": "frontmatter.submenu",
"label": "Front Matter"
}],
"menus": {
"webview/context": [
{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}
],
"editor/title": [
{
"webview/context": [{
"command": "workbench.action.webview.openDeveloperTools",
"when": "frontMatter:isDevelopment"
}],
"editor/title": [{
"command": "frontMatter.markup.heading",
"group": "navigation@-133",
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
@@ -2448,7 +2449,7 @@
"when": "frontMatter:file:isValid == true"
},
{
"command": "frontMatter.i18n.create",
"command": "frontMatter.i18n.createOrOpen",
"group": "navigation@-127",
"when": "frontMatter:file:isValid && frontMatter:i18n:enabled"
},
@@ -2503,14 +2504,11 @@
"when": "resourceFilename == 'frontmatter.json'"
}
],
"explorer/context": [
{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}
],
"frontmatter.submenu": [
{
"explorer/context": [{
"submenu": "frontmatter.submenu",
"group": "frontmatter@1"
}],
"frontmatter.submenu": [{
"command": "frontMatter.createFromTemplate",
"when": "explorerResourceIsFolder",
"group": "frontmatter@1"
@@ -2526,8 +2524,7 @@
"group": "frontmatter@3"
}
],
"commandPalette": [
{
"commandPalette": [{
"command": "frontMatter.init",
"when": "frontMatterCanInit"
},
@@ -2704,8 +2701,7 @@
"when": "frontMatter:file:isValid == true"
}
],
"view/title": [
{
"view/title": [{
"command": "frontMatter.docs",
"group": "navigation@-1",
"when": "view == frontMatter.explorer"
@@ -2742,16 +2738,13 @@
}
]
},
"languages": [
{
"id": "frontmatter.project.output",
"mimetypes": [
"text/x-code-output"
]
}
],
"grammars": [
{
"languages": [{
"id": "frontmatter.project.output",
"mimetypes": [
"text/x-code-output"
]
}],
"grammars": [{
"path": "./syntaxes/hugo.tmLanguage.json",
"scopeName": "frontmatter.markdown.hugo",
"injectTo": [
@@ -2764,48 +2757,45 @@
"path": "./syntaxes/frontmatter-output.tmLanguage.json"
}
],
"walkthroughs": [
{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [
{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
"completionEvents": [
"onContext:frontMatterInitialized"
]
"walkthroughs": [{
"id": "frontmatter.welcome",
"title": "Get started with Front Matter",
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
"steps": [{
"id": "frontmatter.welcome.init",
"title": "Get started",
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
"media": {
"markdown": "assets/walkthrough/get-started.md"
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
"completionEvents": [
"onContext:frontMatterInitialized"
]
},
{
"id": "frontmatter.welcome.documentation",
"title": "Documentation",
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
"media": {
"markdown": "assets/walkthrough/documentation.md"
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}
]
"completionEvents": [
"onLink:https://frontmatter.codes/docs"
]
},
{
"id": "frontmatter.welcome.supporter",
"title": "Support the project",
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
"media": {
"markdown": "assets/walkthrough/support-the-project.md"
},
"completionEvents": [
"onLink:https://github.com/sponsors/estruyf"
]
}
]
}]
},
"scripts": {
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
@@ -2869,6 +2859,7 @@
"dotenv": "^16.3.1",
"downshift": "6.0.6",
"eslint": "^8.33.0",
"eslint-webpack-plugin": "^4.2.0",
"fuse.js": "6.5.3",
"github-directory-downloader": "^1.3.6",
"glob": "^10.3.12",
@@ -2903,7 +2894,14 @@
"react-router-dom": "^6.8.0",
"react-sortable-hoc": "^2.0.0",
"recoil": "^0.7.7",
"remark-gfm": "^3.0.1",
"rehype-parse": "^9.0.1",
"rehype-remark": "^10.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"remark-stringify": "^11.0.0",
"rimraf": "^3.0.2",
"semver": "^7.3.8",
"simple-git": "^3.16.0",
@@ -2913,6 +2911,7 @@
"tailwindcss-animate": "^1.0.7",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"unified": "^11.0.5",
"uniforms": "^3.10.2",
"uniforms-antd": "^3.10.2",
"uniforms-bridge-json-schema": "^3.10.2",
@@ -2932,4 +2931,4 @@
"vsce": {
"dependencies": false
}
}
}
+4 -1
View File
@@ -50,6 +50,7 @@
"command.frontMatter.git.sync": "Sync",
"command.frontMatter.cache.clear": "Clear cache",
"command.frontMatter.i18n.create": "Create new translation",
"command.frontMatter.i18n.createOrOpen": "Create or open translation",
"settings.configuration.title": "Front Matter: use frontmatter.json for shared team settings",
"setting.frontMatter.projects.markdownDescription": "Specify the list of projects to load in the Front Matter CMS. [Local](https://file%2B.vscode-resource.vscode-cdn.net/Users/eliostruyf/nodejs/frontmatter-test-projects/astro-blog/test.html) - [Docs](https://frontmatter.codes/docs/settings/overview#frontmatter.projects) - [View in VS Code](vscode://simpleBrowser.show?%5B%22https://frontmatter.codes/docs/settings/overview%23frontmatter.projects%22%5D)",
"setting.frontMatter.projects.items.properties.name.markdownDescription": "Specify the name of the project.",
@@ -73,6 +74,7 @@
"setting.frontMatter.content.pageFolders.items.properties.title.description": "Name of the folder",
"setting.frontMatter.content.pageFolders.items.properties.path.description": "Path of the folder",
"setting.frontMatter.content.pageFolders.items.properties.excludeSubdir.description": "Exclude sub-directories",
"setting.frontMatter.content.pageFolders.items.properties.excludePaths.description": "Exclude paths (e.g. api, _*.*)",
"setting.frontMatter.content.pageFolders.items.properties.previewPath.description": "Defines a custom preview path for the folder.",
"setting.frontMatter.content.pageFolders.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
"setting.frontMatter.content.pageFolders.items.properties.filePrefix.description": "Defines a prefix for the file name.",
@@ -202,7 +204,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "The choice ID",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "The choice title",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "Is a single line field",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field (HTML output)",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field. You can set it to markdown or HTML.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "Do you allow to select multiple values?",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "Specify if the image field can be used as preview. Be aware, you can only have one preview image per content type.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "Do you want to hide the field from the metadata section?",
@@ -228,6 +230,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.required.description": "Specify if the field is required",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeName.description": "Specify the content type name to filter content for the contentRelationship field",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.contentTypeValue.description": "Specify the value to insert for the contentRelationship field",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.sameContentLocale.description": "Specify if you only want to show the content with the same locale",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.description": "Specify the conditions to show the field",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.fieldRef.description": "The field ID to use",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.operator.description": "The operator to use",
+1 -1
View File
@@ -375,7 +375,7 @@ export class Article {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
// Is article located in one of the content folders
const folders = Folders.getCached();
const folders = await Folders.getCachedOrFresh();
const documentPath = parseWinPath(document.fileName);
const folder = folders.find((f) => documentPath.startsWith(f.path));
if (!folder) {
+21 -16
View File
@@ -33,26 +33,31 @@ export class Chatbot {
const cspSource = webView.webview.cspSource;
const fetchLocalization = async (requestId: string) => {
if (!requestId) {
return;
}
const fileContents = await getLocalizationFile();
webView.webview.postMessage({
command: GeneralCommands.toVSCode.getLocalization,
requestId,
payload: fileContents
});
};
webView.webview.onDidReceiveMessage(async (message) => {
switch (message.command) {
const { command, requestId, payload, data } = message;
switch (command) {
case PreviewCommands.toVSCode.open:
if (message.data) {
commands.executeCommand('vscode.open', message.data);
if (payload || data) {
commands.executeCommand('vscode.open', payload || data);
}
return;
break;
case GeneralCommands.toVSCode.getLocalization:
const { requestId } = message;
if (!requestId) {
return;
}
const fileContents = await getLocalizationFile();
webView.webview.postMessage({
command: GeneralCommands.toVSCode.getLocalization,
requestId,
payload: fileContents
});
fetchLocalization(requestId);
return;
}
});
+43 -11
View File
@@ -39,7 +39,7 @@ import { Preview } from './Preview';
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
export class Folders {
private static _folders: ContentFolder[] = [];
private static _folders: ContentFolder[] | undefined = undefined;
public static async registerCommands() {
const ext = Extension.getInstance();
@@ -50,7 +50,7 @@ export class Folders {
public static clearCached() {
Logger.verbose(`Folders:clearCached`);
Folders._folders = [];
Folders._folders = undefined;
}
/**
@@ -327,7 +327,7 @@ export class Folders {
public static async get(): Promise<ContentFolder[]> {
Logger.verbose('Folders:get:start');
if (Folders._folders.length > 0) {
if (Folders._folders && Folders._folders.length > 0) {
Logger.verbose('Folders:get:end - cached folders');
return Folders._folders;
}
@@ -401,8 +401,8 @@ export class Folders {
folder.locales && folder.locales.length > 0 ? folder.locales : i18nSettings;
let defaultLocale;
let sourcePath = folderPath;
let localeFolders: ContentFolder[] = [];
const sourcePath = folderPath;
const localeFolders: ContentFolder[] = [];
if (i18nConfig && i18nConfig.length > 0) {
for (const i18n of i18nConfig) {
@@ -452,10 +452,23 @@ export class Folders {
* Get the cached folder settings
* @returns {ContentFolder[]} - The cached folder settings
*/
public static getCached(): ContentFolder[] {
public static getCached(): ContentFolder[] | undefined {
return Folders._folders;
}
/**
* Retrieves the cached content folders if available, otherwise fetches fresh content folders.
*
* @returns {Promise<ContentFolder[]>} A promise that resolves to an array of content folders.
*/
public static async getCachedOrFresh(): Promise<ContentFolder[]> {
if (Folders._folders && Folders._folders.length > 0) {
return Folders._folders;
}
return await Folders.get();
}
/**
* Update the folder settings
* @param folders
@@ -688,7 +701,7 @@ export class Folders {
public static async getPageFolderByFilePath(
filePath: string
): Promise<ContentFolder | undefined> {
const folders = Folders.getCached();
const folders = await Folders.getCachedOrFresh();
const parsedPath = parseWinPath(filePath);
const pageFolderMatches = folders
.filter((folder) => parsedPath && folder.path && parsedPath.includes(folder.path))
@@ -719,7 +732,7 @@ export class Folders {
return;
}
const folders = Folders.getCached();
const folders = await Folders.getCachedOrFresh();
let selectedFolder: ContentFolder | undefined;
// Try to find the folder by content type
@@ -788,7 +801,11 @@ export class Folders {
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
}
let foundFiles = await Folders.findFiles(filePath);
let foundFiles = await Folders.findFiles(
filePath,
join(folderPath, folder.excludeSubdir ? '/' : '**'),
folder.excludePaths
);
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
foundFiles = foundFiles.filter((f) =>
@@ -876,12 +893,27 @@ export class Folders {
* @param pattern
* @returns
*/
private static async findFiles(pattern: string): Promise<Uri[]> {
private static async findFiles(
pattern: string,
folderPath: string,
excludePaths: string[] = []
): Promise<Uri[]> {
Logger.verbose(`Folders:findFiles:start - ${pattern}`);
try {
pattern = isWindows() ? parseWinPath(pattern) : pattern;
const files = await glob(pattern, { ignore: 'node_modules/**', dot: true });
const files = await glob(pattern, {
ignore: [
'node_modules/**',
...excludePaths.map((path) => {
// path can be a folder name or a wildcard.
// If its a folder name, we need to add a wildcard to the end
path = path.includes('*') ? path : join(path, '**');
return parseWinPath(join(folderPath, path));
})
],
dot: true
});
const allFiles = (files || []).map((file) => Uri.file(file));
Logger.verbose(`Folders:findFiles:end - ${allFiles.length}`);
return allFiles;
+22 -17
View File
@@ -110,27 +110,32 @@ export class Preview {
webView.dispose();
});
const fetchLocalization = async (requestId: string) => {
if (!requestId) {
return;
}
const fileContents = await getLocalizationFile();
webView.webview.postMessage({
command: GeneralCommands.toVSCode.getLocalization,
requestId,
payload: fileContents
});
};
webView.webview.onDidReceiveMessage(async (message) => {
switch (message.command) {
const { command, payload, requestId } = message;
switch (command) {
case PreviewCommands.toVSCode.open:
if (message.payload) {
commands.executeCommand('vscode.open', message.payload);
if (payload) {
commands.executeCommand('vscode.open', payload);
}
return;
break;
case GeneralCommands.toVSCode.getLocalization:
const { requestId } = message;
if (!requestId) {
return;
}
const fileContents = await getLocalizationFile();
webView.webview.postMessage({
command: GeneralCommands.toVSCode.getLocalization,
requestId,
payload: fileContents
});
return;
fetchLocalization(requestId);
break;
}
});
+1 -1
View File
@@ -42,7 +42,7 @@ categories: []
// Initialize command
subscriptions.push(
commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
commands.registerCommand(COMMAND_NAME.init, async (cb: () => void) => {
await Project.init();
if (cb) {
+208 -18
View File
@@ -1,4 +1,13 @@
import { ProgressLocation, Uri, commands, window, workspace } from 'vscode';
import {
ProgressLocation,
QuickPickItem,
QuickPickItemKind,
QuickPickOptions,
Uri,
commands,
window,
workspace
} from 'vscode';
import {
ArticleHelper,
ContentType,
@@ -16,8 +25,7 @@ import { existsAsync, getDescriptionField, getTitleField } from '../utils';
import { Folders } from '.';
import { ParsedFrontMatter } from '../parsers';
import { PagesListener } from '../listeners/dashboard';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../localization';
import { LocalizationKey, localize } from '../localization';
import { Translations } from '../services/Translations';
export class i18n {
@@ -32,6 +40,7 @@ export class i18n {
const subscriptions = Extension.getInstance().subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.create, i18n.create));
subscriptions.push(commands.registerCommand(COMMAND_NAME.i18n.createOrOpen, i18n.createOrOpen));
i18n.clearFiles();
}
@@ -264,7 +273,7 @@ export class i18n {
}
if (!fileUri) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
return;
}
@@ -274,19 +283,19 @@ export class i18n {
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
if (!pageFolder || !pageFolder.localeSourcePath) {
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
return;
}
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
if (!i18nSettings) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
return;
}
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
if (!sourceLocale || !sourceLocale.locale) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
return;
}
@@ -300,15 +309,15 @@ export class i18n {
});
if (targetLocales.length === 0) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateErrorNoLocales));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
return;
}
const locale = await window.showQuickPick(
targetLocales.map((i18n) => i18n.title || i18n.locale),
{
title: l10n.t(LocalizationKey.commandsI18nCreateQuickPickTitle),
placeHolder: l10n.t(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
title: localize(LocalizationKey.commandsI18nCreateQuickPickTitle),
placeHolder: localize(LocalizationKey.commandsI18nCreateQuickPickPlaceHolder),
ignoreFocusOut: true
}
);
@@ -321,19 +330,19 @@ export class i18n {
(i18n) => i18n.title === locale || i18n.locale === locale
);
if (!targetLocale || !targetLocale.path) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoConfig));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
return;
}
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
if (!article) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoFile));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
return;
}
const contentType = await ArticleHelper.getContentType(article);
if (!contentType) {
Notifications.warning(l10n.t(LocalizationKey.commandsI18nCreateWarningNoContentType));
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
return;
}
@@ -365,7 +374,7 @@ export class i18n {
const newFilePath = join(i18nDir, fileInfo.base);
if (await existsAsync(newFilePath)) {
Notifications.error(l10n.t(LocalizationKey.commandsI18nCreateErrorFileExists));
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorFileExists));
return;
}
@@ -384,7 +393,188 @@ export class i18n {
PagesListener.refresh();
Notifications.info(
l10n.t(
localize(
LocalizationKey.commandsI18nCreateSuccessCreated,
sourceLocale.title || sourceLocale.locale
)
);
}
/**
* This method handles the process of creating a new translation file if it doesn't exist,
* or opening an existing translation file if it's already present.
* @param filePath The path of the file where the new content file should be created or being switched to. Behaves like `create` if not provided.
*/
private static async createOrOpen(fileUri?: Uri | string) {
if (!fileUri) {
const filePath = ArticleHelper.getActiveFile();
fileUri = filePath ? Uri.file(filePath) : undefined;
}
if (!fileUri) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFileSelected));
return;
}
if (typeof fileUri === 'string') {
fileUri = Uri.file(fileUri);
}
const pageFolder = await Folders.getPageFolderByFilePath(fileUri.fsPath);
if (!pageFolder || !pageFolder.localeSourcePath) {
Notifications.error(localize(LocalizationKey.commandsI18nCreateErrorNoContentFolder));
return;
}
let article = await ArticleHelper.getFrontMatterByPath(fileUri.fsPath);
if (!article) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoFile));
return;
}
const contentType = await ArticleHelper.getContentType(article);
if (!contentType) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoContentType));
return;
}
const i18nSettings = await i18n.getSettings(fileUri.fsPath);
if (!i18nSettings) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
return;
}
const sourceLocale = await i18n.getLocale(fileUri.fsPath);
if (!sourceLocale || !sourceLocale.locale) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocaleDefinition));
return;
}
// Determine translation file paths
const fileInfo = parse(fileUri.fsPath);
let pageBundleDir = '';
if (await ArticleHelper.isPageBundle(fileUri.fsPath)) {
const dir = ArticleHelper.getPageFolderFromBundlePath(fileUri.fsPath);
pageBundleDir = fileUri.fsPath.replace(dir, '');
pageBundleDir = join(parse(pageBundleDir).dir);
}
// Gather target locales & metadata
const translations = (await i18n.getTranslations(fileUri.fsPath)) || {};
const targetLocales = i18nSettings
.filter((i18n) => {
return i18n.path && i18n.locale !== sourceLocale.locale;
})
.map((i18n) => {
return {
...i18n,
dir: join(pageFolder.localeSourcePath!, i18n.path!, pageBundleDir),
absolutePath: join(
pageFolder.localeSourcePath!,
i18n.path!,
pageBundleDir,
fileInfo.base
),
relativePath: join(i18n.path!, pageBundleDir, fileInfo.base)
};
})
.sort((a, b) => (a.title || a.locale).localeCompare(b.title || b.locale));
if (targetLocales.length === 0) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateErrorNoLocales));
return;
}
// Configure quick pick items & options
const existingTargetLocales = targetLocales.filter((i18n) => translations[i18n.locale]);
const newTargetLocales = targetLocales.filter((i18n) => !translations[i18n.locale]);
const quickPickItems: QuickPickItem[] = [
...(existingTargetLocales.length
? [
{
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryExisting),
kind: QuickPickItemKind.Separator
},
...existingTargetLocales.map((i18n) => ({
label: i18n.title || i18n.locale,
detail: localize(
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionOpen,
i18n.relativePath
)
}))
]
: []),
...(newTargetLocales.length
? [
{
label: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickCategoryNew),
kind: QuickPickItemKind.Separator
},
...newTargetLocales.map((i18n) => ({
label: i18n.title || i18n.locale,
detail: `$(file-add) ${localize(
LocalizationKey.commandsI18nCreateOrOpenQuickPickActionCreate,
i18n.relativePath
)}`
}))
]
: [])
];
const quickPickOptions: QuickPickOptions = {
title: localize(LocalizationKey.commandsI18nCreateOrOpenQuickPickTitle),
ignoreFocusOut: true,
matchOnDetail: true
};
const localeItem = await window.showQuickPick<QuickPickItem>(quickPickItems, quickPickOptions);
const locale = localeItem?.label;
if (!locale) {
return;
}
const targetLocale = targetLocales.find(
(i18n) => i18n.title === locale || i18n.locale === locale
);
if (!targetLocale || !targetLocale.path) {
Notifications.warning(localize(LocalizationKey.commandsI18nCreateWarningNoConfig));
return;
}
// If it exists, open the translation file
if (await existsAsync(targetLocale.absolutePath)) {
await openFileInEditor(targetLocale.absolutePath);
return;
}
// If it doesn't exist, create the new translation file & update front matter
if (!(await existsAsync(targetLocale.dir))) {
await workspace.fs.createDirectory(Uri.file(targetLocale.dir));
}
article = await i18n.updateFrontMatter(
article,
fileUri.fsPath,
contentType,
sourceLocale,
targetLocale,
targetLocale.dir
);
if (sourceLocale?.locale) {
article = await i18n.translate(article, sourceLocale, targetLocale);
}
const newFileUri = Uri.file(targetLocale.absolutePath);
await workspace.fs.writeFile(
newFileUri,
Buffer.from(ArticleHelper.stringifyFrontMatter(article.content, article.data))
);
await openFileInEditor(targetLocale.absolutePath);
PagesListener.refresh();
Notifications.info(
localize(
LocalizationKey.commandsI18nCreateSuccessCreated,
sourceLocale.title || sourceLocale.locale
)
@@ -403,11 +593,11 @@ export class i18n {
sourceLocale: I18nConfig,
targetLocale: I18nConfig
) {
return new Promise<ParsedFrontMatter>(async (resolve) => {
await window.withProgress(
return new Promise<ParsedFrontMatter>((resolve) => {
window.withProgress(
{
location: ProgressLocation.Notification,
title: l10n.t(LocalizationKey.commandsI18nTranslateProgressTitle),
title: localize(LocalizationKey.commandsI18nTranslateProgressTitle),
cancellable: false
},
async () => {
@@ -30,6 +30,7 @@ function Num({
<LabelField label={label} id={id} required={props.required} />
<input
className='block w-full py-2 pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0'
disabled={disabled}
id={id}
max={max}
+2 -1
View File
@@ -70,7 +70,8 @@ export const COMMAND_NAME = {
// i18n
i18n: {
create: getCommandName('i18n.create')
create: getCommandName('i18n.create'),
createOrOpen: getCommandName('i18n.createOrOpen')
},
// Project
+1 -1
View File
@@ -54,7 +54,7 @@ export const App: React.FunctionComponent<IAppProps> = ({
return isAllowed(mode?.features || [], FEATURE_FLAG.dashboard.taxonomy.view);
}, [mode?.features]);
const checkDevMode = (retry: number = 0) => {
const checkDevMode = (retry = 0) => {
if (!window.fmExternal) {
if (retry < 5) {
setTimeout(() => checkDevMode(retry + 1), 150);
@@ -11,10 +11,11 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { GeneralCommands, WEBSITE_LINKS } from '../../../constants';
import { l10nJsonFormat } from '@vscode/l10n';
export interface IChatbotProps { }
export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.PropsWithChildren<IChatbotProps>) => {
export const Chatbot: React.FunctionComponent<IChatbotProps> = () => {
const { aiUrl } = useSettingsContext();
const [company, setCompany] = React.useState<string | undefined>(undefined);
const [chatId, setChatId] = React.useState<number | undefined>(undefined);
@@ -27,7 +28,7 @@ export const Chatbot: React.FunctionComponent<IChatbotProps> = ({ }: React.Props
const init = async () => {
setLoading(true);
messageHandler.request<any>(GeneralCommands.toVSCode.getLocalization).then((data) => {
messageHandler.request<l10nJsonFormat>(GeneralCommands.toVSCode.getLocalization).then((data) => {
if (data) {
l10n.config({
contents: data
@@ -1,33 +0,0 @@
import * as React from 'react';
export interface IButtonProps {
secondary?: boolean;
disabled?: boolean;
className?: string;
onClick: () => void;
}
export const Button: React.FunctionComponent<IButtonProps> = ({
onClick,
className,
disabled,
secondary,
children
}: React.PropsWithChildren<IButtonProps>) => {
return (
<button
type="button"
className={`${className || ''
} inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium focus:outline-none rounded disabled:opacity-50 ${secondary ?
`bg-[var(--vscode-button-secondaryBackground)] text-[--vscode-button-secondaryForeground] hover:bg-[var(--vscode-button-secondaryHoverBackground)]` :
`bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)] hover:bg-[var(--frontmatter-button-hoverBackground)]`
}
`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
};
@@ -53,7 +53,7 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({
};
} catch (error) {
setError((error as Error).message);
return () => { };
return () => void 0;
}
};
@@ -1,9 +1,9 @@
import * as React from 'react';
import { useForm } from 'uniforms';
import { Button } from '../Common/Button';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { SubmitField } from '../../../components/uniforms-frontmatter';
import { Button } from 'vscrui';
export interface IDataFormControlsProps {
model: any | null;
@@ -21,8 +21,8 @@ export const DataFormControls: React.FunctionComponent<IDataFormControlsProps> =
<SubmitField value={model ? `Update` : `Add`} />
<Button
className="ml-4"
secondary
className="ml-4 !py-2"
appearance="secondary"
onClick={() => {
if (onClear) {
onClear();
@@ -10,7 +10,6 @@ import { DashboardMessage } from '../../DashboardMessage';
import { SponsorMsg } from '../Layout/SponsorMsg';
import { EventData } from '@estruyf/vscode';
import { DashboardCommand } from '../../DashboardCommand';
import { Button } from '../Common/Button';
import { arrayMoveImmutable } from 'array-move';
import { EmptyView } from './EmptyView';
import { Container } from './SortableContainer';
@@ -27,12 +26,11 @@ import { DataFolder } from '../../../models';
import { ActionsBarItem } from '../Header/ActionsBarItem';
import { Spinner } from '../Common/Spinner';
import { openFile } from '../../utils/MessageHandlers';
import { Button } from 'vscrui';
export interface IDataViewProps { }
export const DataView: React.FunctionComponent<IDataViewProps> = (
_: React.PropsWithChildren<IDataViewProps>
) => {
export const DataView: React.FunctionComponent<IDataViewProps> = () => {
const [selectedData, setSelectedData] = useState<DataFile | null>(null);
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [dataEntries, setDataEntries] = useState<any | any[] | null>(null);
@@ -68,15 +66,14 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
);
const onSubmit = useCallback(
(data: any) => {
(data: unknown) => {
if (selectedData?.singleEntry) {
// Needs to add a single entry
updateData(data);
return;
}
debugger
const dataClone: any[] = Object.assign([], dataEntries);
const dataClone: unknown[] = Object.assign([], dataEntries);
if (selectedIndex !== null && selectedIndex !== undefined) {
dataClone[selectedIndex] = data;
} else {
@@ -140,7 +137,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
return dataEntries && selectedIndex !== null && selectedIndex !== undefined
? dataEntries[selectedIndex]
: null;
}, [selectedData, , dataEntries, selectedIndex]);
}, [selectedData, dataEntries, selectedIndex]);
// Retrieve the data files, check if they have a schema or ID, if not, they shouldn't be shown
const dataFiles = useMemo(() => {
@@ -189,8 +186,6 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
};
}, []);
console.log('DataView render', settings?.dataFolders);
return (
<div className="flex flex-col h-full overflow-auto inset-y-0">
<Header settings={settings} />
@@ -339,7 +334,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
/>
))}
</Container>
<Button className="mt-4" onClick={() => setSelectedIndex(null)}>
<Button className="mt-4 !py-2" onClick={() => setSelectedIndex(null)}>
{localize(LocalizationKey.dashboardDataViewDataViewAdd)}
</Button>
</>
@@ -9,7 +9,7 @@ import { LocalizationKey } from '../../../localization';
export interface ILanguageFilterProps { }
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = ({ }: React.PropsWithChildren<ILanguageFilterProps>) => {
export const LanguageFilter: React.FunctionComponent<ILanguageFilterProps> = () => {
const locales = useRecoilValue(LocalesAtom);
const [crntLocale, setCrntLocale] = useRecoilState(LocaleAtom);
@@ -21,16 +21,16 @@ import { useEffect, useMemo } from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
export const guardRecoilDefaultValue = (candidate: any): candidate is DefaultValue => {
if (candidate instanceof DefaultValue) return true;
export const guardRecoilDefaultValue = (candidate: unknown): candidate is DefaultValue => {
if (candidate instanceof DefaultValue) {
return true;
}
return false;
};
export interface IClearFiltersProps { }
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
_: React.PropsWithChildren<IClearFiltersProps>
) => {
export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = () => {
const [show, setShow] = React.useState(false);
const folder = useRecoilValue(FolderSelector);
@@ -75,7 +75,9 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
}
}, [folder, tag, category, locale, hasCustomFilters]);
if (!show) return null;
if (!show) {
return null;
}
return (
<button
@@ -36,7 +36,7 @@ export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWi
activeItem={crntFilters[filterName]}
items={values}
onClick={(value) => setCrntFilters((prev) => {
let clone = Object.assign({}, prev);
const clone = Object.assign({}, prev);
if (!clone[filterName] && value) {
clone[filterName] = value;
} else {
@@ -10,7 +10,7 @@ export interface IFoldersFilterProps { }
export const FoldersFilter: React.FunctionComponent<
IFoldersFilterProps
> = ({ }: React.PropsWithChildren<IFoldersFilterProps>) => {
> = () => {
const DEFAULT_TYPE = l10n.t(LocalizationKey.dashboardHeaderFoldersDefault);
const [crntFolder, setCrntFolder] = useRecoilState(FolderAtom);
const settings = useRecoilValue(SettingsSelector);
@@ -11,12 +11,12 @@ export interface IGroupingProps { }
export const Grouping: React.FunctionComponent<
IGroupingProps
> = ({ }: React.PropsWithChildren<IGroupingProps>) => {
> = () => {
const [group, setGroup] = useRecoilState(GroupingAtom);
const pages = useRecoilValue(AllPagesAtom);
const GROUP_OPTIONS = React.useMemo(() => {
let options: { name: string, id: GroupOption }[] = [];
const options: { name: string, id: GroupOption }[] = [];
if (pages.length > 0) {
if (pages.some((x) => x.fmYear)) {
@@ -37,9 +37,7 @@ const NavigationItem: React.FunctionComponent<INavigationItemProps> = ({
)
};
export const Navigation: React.FunctionComponent<INavigationProps> = ({
}: React.PropsWithChildren<INavigationProps>) => {
export const Navigation: React.FunctionComponent<INavigationProps> = () => {
const [crntTab, setCrntTab] = useRecoilState(TabAtom);
const tabInfo = useRecoilValue(TabInfoAtom);
const settings = useRecoilValue(SettingsAtom);
@@ -21,9 +21,7 @@ import { ArrowClockwiseIcon } from '../../../components/icons/ArrowClockwiseIcon
export interface IRefreshDashboardDataProps { }
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = (
{ }: React.PropsWithChildren<IRefreshDashboardDataProps>
) => {
export const RefreshDashboardData: React.FunctionComponent<IRefreshDashboardDataProps> = () => {
const view = useRecoilValue(DashboardViewAtom);
const [, setLoading] = useRecoilState(LoadingAtom);
const resetSearch = useResetRecoilState(SearchAtom);
@@ -165,7 +165,7 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({
}
}
let sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
const sort = allOptions.find((x) => x.id === crntSortingOption?.id) || sortOptions[0];
setCrntSort(sort);
};
@@ -67,7 +67,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
return [];
}
let groupedFolders = [];
const groupedFolders = [];
for (const cFolder of settings?.contentFolders || []) {
const foldersPath = parseWinPath(cFolder.path);
@@ -23,7 +23,7 @@ export interface IMediaHeaderTopProps { }
export const MediaHeaderTop: React.FunctionComponent<
IMediaHeaderTopProps
> = ({ }: React.PropsWithChildren<IMediaHeaderTopProps>) => {
> = () => {
const [lastUpdated, setLastUpdated] = React.useState<string | null>(null);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const crntSorting = useRecoilValue(SortingSelector);
@@ -20,7 +20,8 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
const onRefresh = () => {
if (iframeRef.current?.src) {
iframeRef.current.src = iframeRef.current.src;
const url = iframeRef.current.src;
iframeRef.current.src = url;
}
};
@@ -34,10 +35,12 @@ export const Preview: React.FunctionComponent<IPreviewProps> = ({
navUrl = `https://${navUrl}`;
setCrntUrl(navUrl);
}
iframeRef.current!.src = navUrl;
if (iframeRef.current) {
iframeRef.current.src = navUrl;
}
};
const msgListener = (message: MessageEvent<EventData<any>>) => {
const msgListener = (message: MessageEvent<EventData<string>>) => {
if (message.data.command === PreviewCommands.toWebview.updateUrl) {
setCrntUrl(message.data.payload);
}
@@ -8,7 +8,7 @@ import { Button as VSCodeButton } from 'vscrui';
export interface IIntegrationsViewProps { }
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = ({ }: React.PropsWithChildren<IIntegrationsViewProps>) => {
export const IntegrationsView: React.FunctionComponent<IIntegrationsViewProps> = () => {
const [deeplApiKey, setDeeplApiKey] = React.useState<string>('');
const [azureApiKey, setAzureApiKey] = React.useState<string>('');
const [azureRegion, setAzureRegion] = React.useState<string>('');
@@ -24,7 +24,7 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
const settings = useRecoilValue(SettingsSelector);
const tabs: ITab[] = React.useMemo(() => {
let temp = [
const temp = [
{ id: "view-1", label: l10n.t(LocalizationKey.settingsViewCommon) },
{ id: "view-2", label: l10n.t(LocalizationKey.settingsViewContentFolders) }
];
@@ -44,7 +44,7 @@ export const SettingsView: React.FunctionComponent<ISettingsViewProps> = (_: Rea
return [];
}
let temp = [
const temp = [
{
id: "view-1",
content: <CommonSettings />
@@ -75,7 +75,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
return;
}
let snippets: Snippets = Object.assign({}, settings?.snippets || {});
const snippets: Snippets = Object.assign({}, settings?.snippets || {});
const snippetLines = snippetOriginalBody.split('\n');
const crntSnippet = Object.assign({}, snippets[snippetKey]);
@@ -79,11 +79,11 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
);
const snippetBody = useMemo(() => {
let body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
const body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
const obj: any = {};
const obj: { [key: string]: string } = {};
for (const field of fields) {
obj[field.name] = field.value;
obj[field.name] = field.value as string;
}
return SnippetParser.render(body, obj, snippet.openingTags, snippet.closingTags);
@@ -31,7 +31,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
<div className="relative">
<select
name={field.name}
value={field.value || ''}
value={field.value as string || ''}
className={`block w-full sm:text-sm pr-2 appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
@@ -69,7 +69,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
return (
<TextField
name={field.name}
value={field.value || ''}
value={field.value as string || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
rows={4}
@@ -81,7 +81,7 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
return (
<TextField
name={field.name}
value={field.value || ''}
value={field.value as string || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
/>
@@ -41,7 +41,7 @@ export const TaxonomyLookup: React.FunctionComponent<ITaxonomyLookupProps> = ({
return false;
}
let fieldName = getTaxonomyField(taxonomy, contentType);
const fieldName = getTaxonomyField(taxonomy, contentType);
return fieldName && page[fieldName] ? page[fieldName].includes(value) : false;
}).length;
@@ -80,7 +80,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
}, [data, taxonomy, debounceFilterValue]);
const unmappedItems = useMemo(() => {
let unmapped: string[] = [];
const unmapped: string[] = [];
if (!pages || !settings?.contentTypes || !taxonomy) {
return unmapped;
@@ -100,7 +100,7 @@ export const TaxonomyManager: React.FunctionComponent<ITaxonomyManagerProps> = (
return false;
}
let fieldName = getTaxonomyField(taxonomy, contentType);
const fieldName = getTaxonomyField(taxonomy, contentType);
if (fieldName && page[fieldName]) {
values = page[fieldName];
@@ -5,7 +5,7 @@ import { SettingsSelector } from '../../state';
import { getTaxonomyField } from '../../../helpers/getTaxonomyField';
import { Sorting } from '../../../helpers/Sorting';
import { ArrowLeftIcon, EyeIcon } from '@heroicons/react/24/outline';
import { Button } from '../Common/Button';
import { Button } from 'vscrui';
import { FilterInput } from './FilterInput';
import { useDebounce } from '../../../hooks/useDebounce';
import * as l10n from '@vscode/l10n';
@@ -70,7 +70,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
continue;
}
let fieldName = getTaxonomyField(taxonomy, contentType);
const fieldName = getTaxonomyField(taxonomy, contentType);
if (fieldName && (!page[fieldName] || page[fieldName].indexOf(value) === -1)) {
untagged.push(page);
@@ -78,7 +78,7 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
}
}
untagged = untagged.sort(Sorting.number('fmPublished')).reverse();
untagged = untagged.sort(Sorting.numerically('fmPublished')).reverse();
if (debounceFilterValue) {
return untagged.filter((p) => p.title.toLowerCase().includes(debounceFilterValue.toLowerCase()));
@@ -229,8 +229,8 @@ export const TaxonomyTagging: React.FunctionComponent<ITaxonomyTaggingProps> = (
</div>
<div className='flex justify-end space-x-2'>
<Button onClick={onDismiss} secondary>{l10n.t(LocalizationKey.commonCancel)}</Button>
<Button onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
<Button className='!py-2' onClick={onDismiss} appearance='secondary'>{l10n.t(LocalizationKey.commonCancel)}</Button>
<Button className='!py-2' onClick={() => onContentMapping(value, pageMappings)}>{l10n.t(LocalizationKey.commonApply)}</Button>
</div>
</div>
);
+1 -1
View File
@@ -42,7 +42,7 @@ export default function useMediaInfo(media?: MediaInfo) {
}, [media]);
const mediaDetails = useMemo(() => {
let sizeDetails = [];
const sizeDetails = [];
if (mediaDimensions) {
sizeDetails.push(mediaDimensions);
+2 -6
View File
@@ -159,8 +159,6 @@ export default function usePages(pages: Page[]) {
if (tab !== Tab.All) {
crntPages = crntPages.filter((page) => page.fmDraft === tab);
} else {
crntPages = crntPages;
}
} else {
// Draft field is a boolean field
@@ -194,8 +192,6 @@ export default function usePages(pages: Page[]) {
crntPages = drafts;
} else if (tab === Tab.Scheduled) {
crntPages = scheduled;
} else {
crntPages = crntPages;
}
}
}
@@ -240,11 +236,11 @@ export default function usePages(pages: Page[]) {
};
useEffect(() => {
let usedSorting = sorting;
const usedSorting = sorting;
const startPageProcessing = () => {
// Check if search needs to be performed
let searchedPages = pages;
const searchedPages = pages;
if (search) {
Messenger.send(DashboardMessage.searchPages, { query: search });
} else {
+6 -1
View File
@@ -14,6 +14,7 @@ import { I10nProvider } from './providers/I10nProvider';
import { SentryInit } from '../utils/sentryInit';
import { WEBSITE_LINKS } from '../constants';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare const acquireVsCodeApi: <T = unknown>() => {
getState: () => T;
setState: (data: T) => void;
@@ -119,4 +120,8 @@ if (elm) {
}
// Webpack HMR
if ((module as any).hot) (module as any).hot.accept();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((module as any).hot) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(module as any).hot.accept();
}
+4 -2
View File
@@ -115,7 +115,9 @@
}
input[type='submit'] {
@apply mt-4 inline-flex w-auto items-center rounded border border-transparent bg-[var(--frontmatter-button-background)] px-3 py-2 text-sm font-medium leading-4 text-[var(--vscode-button-foreground)];
@apply mt-4 inline-flex w-auto items-center rounded-[2px] border border-transparent bg-[var(--frontmatter-button-background)] px-[11px] py-[4px] text-[var(--vscode-button-foreground)];
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size, 13px);
&:hover {
@apply bg-[var(--frontmatter-button-hoverBackground)];
@@ -310,7 +312,7 @@
}
input[type='submit'] {
@apply rounded text-vulcan-500;
@apply rounded-[2px] text-vulcan-500;
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ export const darkenColor = (color: string | undefined, percentage: number) => {
// Check if the color is in rgba format
if (color.startsWith('rgba')) {
// Extract the alpha value
const alphaMatch = color.match(/[\d\.]+(?=\))/);
const alphaMatch = color.match(/[\d.]+(?=\))/);
const alpha = alphaMatch ? Number(alphaMatch[0]) : 1;
return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`;
@@ -1,6 +1,6 @@
import { darkenColor, opacityColor, preserveColor } from '.';
export const updateCssVariables = (isDarkTheme: boolean = true) => {
export const updateCssVariables = (isDarkTheme = true) => {
const styles = getComputedStyle(document.documentElement);
// Lightbox
+4 -2
View File
@@ -39,6 +39,7 @@ import { i18n } from './commands/i18n';
import { UriHandler } from './providers/UriHandler';
let pageUpdateDebouncer: { (fnc: any, time: number): void };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let editDebounce: { (fnc: any, time: number): void };
let collection: vscode.DiagnosticCollection;
@@ -56,7 +57,7 @@ export async function activate(context: vscode.ExtensionContext) {
}
// Sponsor check
Backers.init(context).then(() => {});
Backers.init(context);
// Make sure the EN language file is loaded
if (!vscode.l10n.uri) {
@@ -143,7 +144,7 @@ export async function activate(context: vscode.ExtensionContext) {
SettingsHelper.startListening();
// Create the status bar
let fmStatusBarItem = vscode.window.createStatusBarItem(
const fmStatusBarItem = vscode.window.createStatusBarItem(
'fm-statusBarItem',
vscode.StatusBarAlignment.Right,
-100
@@ -243,6 +244,7 @@ export async function activate(context: vscode.ExtensionContext) {
console.log(`𝖥𝗋𝗈𝗇𝗍 𝖬𝖺𝗍𝗍𝖾𝗋 𝖢𝖬𝖲 𝖺𝖼𝗍𝗂𝗏𝖺𝗍𝖾𝖽! 𝖱𝖾𝖺𝖽𝗒 𝗍𝗈 𝗌𝗍𝖺𝗋𝗍 𝗐𝗋𝗂𝗍𝗂𝗇𝗀... 👩‍💻🧑‍💻👨‍💻`);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function deactivate() {}
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
+2 -2
View File
@@ -356,7 +356,7 @@ export class ArticleHelper {
* @returns A boolean indicating whether the file is a page bundle or not.
*/
public static async isPageBundle(filePath: string) {
let article = await ArticleHelper.getFrontMatterByPath(filePath);
const article = await ArticleHelper.getFrontMatterByPath(filePath);
if (!article) {
return false;
}
@@ -913,7 +913,7 @@ export class ArticleHelper {
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
if (fileContents) {
let article = FrontMatterParser.fromFile(fileContents);
const article = FrontMatterParser.fromFile(fileContents);
if (article?.data) {
if (commaSeparated) {
+6 -5
View File
@@ -564,7 +564,7 @@ export class ContentType {
const allRequiredFields = ContentType.findRequiredFieldsDeep(contentType.fields);
let emptyFields: Field[][] = [];
const emptyFields: Field[][] = [];
for (const fields of allRequiredFields) {
const fieldValue = this.getFieldValue(
@@ -656,7 +656,7 @@ export class ContentType {
return [];
}
let foundBlocks = [];
const foundBlocks = [];
for (const group of groups) {
const block = blocks.find((block) => block.id === group);
if (!block) {
@@ -960,11 +960,12 @@ export class ContentType {
templateData = await ArticleHelper.getFrontMatterByPath(templatePath);
}
let newFilePath: string | undefined = await ArticleHelper.createContent(
const newFilePath: string | undefined = await ArticleHelper.createContent(
contentType,
folderPath,
titleValue
);
if (!newFilePath) {
return;
}
@@ -1045,7 +1046,7 @@ export class ContentType {
filePath: string,
clearEmpty: boolean,
contentType: IContentType,
isRoot: boolean = true
isRoot = true
): Promise<any> {
if (obj.fields) {
const titleField = getTitleField();
@@ -1106,7 +1107,7 @@ export class ContentType {
filePath
);
} else if (defaultValue && Array.isArray(defaultValue)) {
let defaultValues = [];
const defaultValues = [];
for (let value of defaultValue as string[]) {
if (typeof value === 'string') {
value = await ContentType.processFieldPlaceholders(
+8 -4
View File
@@ -72,7 +72,9 @@ export class CustomScript {
if (!path) {
const editor = window.activeTextEditor;
if (!editor) return;
if (!editor) {
return;
}
articlePath = editor.document.uri.fsPath;
article = ArticleHelper.getFrontMatter(editor);
@@ -120,7 +122,7 @@ export class CustomScript {
return;
}
let output: string[] = [];
const output: string[] = [];
window.withProgress(
{
@@ -283,7 +285,9 @@ export class CustomScript {
const editor = window.activeTextEditor;
if (!articlePath) {
if (!editor) return;
if (!editor) {
return;
}
articlePath = editor.document.uri.fsPath;
article = ArticleHelper.getFrontMatter(editor);
@@ -469,7 +473,7 @@ export class CustomScript {
* @returns
*/
private static async executeScriptAsync(fullScript: string, wsPath: string): Promise<string> {
return new Promise(async (resolve, reject) => {
return new Promise((resolve, reject) => {
exec(fullScript, { cwd: wsPath }, (error, stdout) => {
if (error) {
Logger.error(error.message);
+4 -4
View File
@@ -63,7 +63,7 @@ import { DataListener } from '../listeners/dashboard';
export class DashboardSettings {
private static cachedSettings: ISettings | undefined = undefined;
public static async get(clear: boolean = false) {
public static async get(clear = false) {
if (!this.cachedSettings || clear) {
this.cachedSettings = await this.getSettings();
}
@@ -192,9 +192,9 @@ export class DashboardSettings {
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
const folders = Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS);
let clonedFiles = Object.assign([], files);
const clonedFiles = Object.assign([], files);
if (folders) {
for (let folder of folders) {
for (const folder of folders) {
if (!folder.path) {
continue;
}
@@ -218,7 +218,7 @@ export class DashboardSettings {
);
const dataFiles = [...dataJsonFiles, ...dataYmlFiles, ...dataYamlFiles];
for (let dataFile of dataFiles) {
for (const dataFile of dataFiles) {
clonedFiles.push(DataListener.createDataFileObject(dataFile.fsPath, folder));
}
}
+1 -1
View File
@@ -293,7 +293,7 @@ export class Extension {
propKey: string,
propValue: T,
type: 'workspace' | 'global' = 'global',
setState: boolean = false
setState = false
): Promise<void> {
if (this.isFileStorageNeeded(propKey)) {
let storageUri: Uri | undefined = undefined;
+1 -1
View File
@@ -38,7 +38,7 @@ export class FilesHelper {
*/
public static relToAbsPath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
let absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
const absPath = join(parseWinPath(wsFolder?.fsPath || ''), filePath);
return parseWinPath(absPath);
}
+2 -2
View File
@@ -17,7 +17,7 @@ export class ImageHelper {
* @returns
*/
public static allRelToAbs(field: Field, value: string | string[] | undefined) {
let filePath =
const filePath =
window.activeTextEditor?.document.uri.fsPath ||
Preview.filePath ||
ArticleHelper.getActiveFile();
@@ -54,7 +54,7 @@ export class ImageHelper {
*/
public static relToAbs(filePath: string, value: string) {
const wsFolder = Folders.getWorkspaceFolder();
let staticFolder = Folders.getStaticFolderRelativePath();
const staticFolder = Folders.getStaticFolderRelativePath();
if (staticFolder === STATIC_FOLDER_PLACEHOLDER.hexo.placeholder) {
const editor = window.activeTextEditor;
+3 -2
View File
@@ -45,8 +45,9 @@ export class MediaHelpers {
* @returns
*/
public static async getMedia(
page: number = 0,
requestedFolder: string = '',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
page = 0,
requestedFolder = '',
sort: SortingOption | null = null
) {
const wsFolder = Folders.getWorkspaceFolder();
+1 -1
View File
@@ -162,7 +162,7 @@ export class Notifications {
* @returns
*/
private static shouldShow(level: NotificationType): boolean {
let levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
const levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
if (!levels) {
return true;
+4 -4
View File
@@ -39,7 +39,7 @@ export class Questions {
* @param showWarning
* @returns
*/
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
public static async ContentTitle(showWarning = true): Promise<string | undefined> {
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
let title: string | undefined = '';
const isCopilotInstalled = await Copilot.isInstalled();
@@ -122,7 +122,7 @@ export class Questions {
title: string | undefined,
aiTitles: string[],
isCopilotInstalled: boolean,
showWarning: boolean = true
showWarning = true
): Promise<string | undefined> {
if (title && aiTitles && aiTitles.length > 0) {
const options: QuickPickItem[] = [
@@ -174,7 +174,7 @@ export class Questions {
* @returns
*/
public static async SelectContentFolder(
showWarning: boolean = true
showWarning = true
): Promise<FolderQuickPickItem | undefined> {
let folders = await Folders.get();
folders = folders.filter((f) => !f.disableCreation);
@@ -230,7 +230,7 @@ export class Questions {
*/
public static async SelectContentType(
allowedCts: string[],
showWarning: boolean = true
showWarning = true
): Promise<string | undefined> {
let contentTypes = ContentType.getAll();
if (!contentTypes || contentTypes.length === 0) {
+9 -8
View File
@@ -1,14 +1,15 @@
var illegalRe = /[\/\?<>\\:\*\|"]/g;
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
var reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[\. ]+$/;
const illegalRe = /[/?<>\\:*|"]/g;
// eslint-disable-next-line no-control-regex
const controlRe = /[\x00-\x1F\x80-\x9F]/g;
const reservedRe = /^\.+$/;
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
const windowsTrailingRe = /[. ]+$/;
function sanitize(input: string, replacement: string) {
if (typeof input !== 'string') {
throw new Error('Input must be string');
}
var sanitized = input
const sanitized = input
.replace(illegalRe, replacement)
.replace(controlRe, replacement)
.replace(reservedRe, replacement)
@@ -18,8 +19,8 @@ function sanitize(input: string, replacement: string) {
}
export default function (input: string, options?: any) {
var replacement = (options && options.replacement) || '';
var output = sanitize(input, replacement);
const replacement = (options && options.replacement) || '';
const output = sanitize(input, replacement);
if (replacement === '') {
return output;
}
+9 -17
View File
@@ -67,7 +67,7 @@ export class Settings {
public static globalConfigPath: string | undefined = undefined;
public static globalConfig: any;
private static config: WorkspaceConfiguration;
private static isInitialized: boolean = false;
private static isInitialized = false;
private static listeners: { id: string; callback: (global?: any) => void }[] = [];
private static fileCreationWatcher: FileSystemWatcher | undefined;
private static fileChangeWatcher: FileSystemWatcher | undefined;
@@ -76,7 +76,7 @@ export class Settings {
private static readConfigPromise: Promise<void> | undefined = undefined;
private static project: Project | undefined = undefined;
private static configDebouncer = debounceCallback();
private static hasExtendedConfig: boolean = false;
private static hasExtendedConfig = false;
private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any;
public static async registerCommands() {
@@ -335,7 +335,7 @@ export class Settings {
/**
* Retrieve a setting from global and local config
*/
public static get<T>(name: string, merging: boolean = false): T | undefined {
public static get<T>(name: string, merging = false): T | undefined {
if (!Settings.config) {
return;
}
@@ -383,11 +383,7 @@ export class Settings {
* @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`.
* @returns A promise that resolves when the setting is updated.
*/
public static async safeUpdate<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
public static async safeUpdate<T>(name: string, value: T, updateGlobal = false): Promise<void> {
if (Settings.hasExtendedConfig) {
const configKey = `${CONFIG_KEY}.${name}`;
@@ -418,11 +414,7 @@ ${JSON.stringify(value, null, 2)}`,
* @param name
* @param value
*/
public static async update<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
public static async update<T>(name: string, value: T, updateGlobal = false): Promise<void> {
const fmConfig = await Settings.projectConfigPath();
if (updateGlobal) {
@@ -571,7 +563,7 @@ ${JSON.stringify(value, null, 2)}`,
*/
public static async updateCustomTaxonomyOptions(id: string, options: string[]) {
const customTaxonomies = Settings.get<CustomTaxonomy[]>(SETTING_TAXONOMY_CUSTOM, true) || [];
let taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
const taxIdx = customTaxonomies?.findIndex((o) => o.id === id);
if (taxIdx !== -1) {
customTaxonomies[taxIdx].options = options;
@@ -753,7 +745,7 @@ ${JSON.stringify(value, null, 2)}`,
Logger.info(`Reading dynamic config file: ${absFilePath}`);
if (absFilePath) {
if (await existsAsync(absFilePath)) {
const configFunction = require(absFilePath);
const configFunction = await import(absFilePath);
const dynamicConfig = await configFunction(
Object.assign({}, Settings.globalConfig)
);
@@ -858,7 +850,7 @@ ${JSON.stringify(value, null, 2)}`,
// We need to loop through the config to make sure the objects and arrays are merged
for (const key in config) {
if (config.hasOwnProperty(key)) {
if (Object.prototype.hasOwnProperty.call(config, key)) {
const value = config[key];
const settingName = key.replace(`${CONFIG_KEY}.`, '');
@@ -1209,7 +1201,7 @@ ${JSON.stringify(value, null, 2)}`,
* Reload the config
* @param debounced
*/
private static async reloadConfig(debounced: boolean = true) {
private static async reloadConfig(debounced = true) {
Logger.info(`Reloading config...`);
// Clear the folder cache as we need to see the latest folders
Folders.clearCached();
+1 -1
View File
@@ -69,7 +69,7 @@ export class SlugHelper {
return '';
}
const punctuationless = value?.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, ' ');
const punctuationless = value?.replace(/[.,-/#!$@%^&*;:{}=\-_`'"~()+?<>]/g, ' ');
// Remove double spaces
return punctuationless?.replace(/\s{2,}/g, ' ');
}
+7 -7
View File
@@ -4,8 +4,8 @@ import { SnippetField } from '../models';
export class SnippetParser {
public static getPlaceholders(
value: string[] | string,
openingTags: string = '[[',
closingTags: string = ']]'
openingTags = '[[',
closingTags = ']]'
): string[] {
const template = SnippetParser.template(value);
const parseTree = Mustache.parse(template, [openingTags, closingTags]);
@@ -29,9 +29,9 @@ export class SnippetParser {
public static render(
value: string[] | string,
data: any,
openingTags: string = '[[',
closingTags: string = ']]'
data: unknown,
openingTags = '[[',
closingTags = ']]'
): string {
const template = SnippetParser.template(value);
return Mustache.render(template, data, undefined, [openingTags, closingTags]);
@@ -40,8 +40,8 @@ export class SnippetParser {
public static getFields(
value: string[] | string,
fields: SnippetField[],
openingTags: string = '[[',
closingTags: string = ']]'
openingTags = '[[',
closingTags = ']]'
) {
const placeholders = SnippetParser.getPlaceholders(value, openingTags, closingTags);
+9 -14
View File
@@ -7,6 +7,7 @@ export class Sorting {
* @returns
*/
public static alphabetically = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
if (a[property] < b[property]) {
return -1;
@@ -24,6 +25,7 @@ export class Sorting {
* @returns
*/
public static numerically = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
return a[property] - b[property];
};
@@ -35,6 +37,7 @@ export class Sorting {
* @returns
*/
public static date = (property: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any) => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
@@ -49,13 +52,16 @@ export class Sorting {
* @returns
*/
public static dateWithFallback = (property: string, fallback: string) => {
return (a: any, b: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (a: any, b: any): number => {
const dateA = DateHelper.tryParse(a[property]);
const dateB = DateHelper.tryParse(b[property]);
// Sort by date
var dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
if (dCount) return dCount;
const dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime();
if (dCount) {
return dCount;
}
// If there is a tie, sort by fallback property
if (a[fallback] < b[fallback]) {
@@ -67,15 +73,4 @@ export class Sorting {
return 0;
};
};
/**
* Sort by number
* @param property
* @returns
*/
public static number = (property: string) => {
return (a: any, b: any) => {
return a[property] - b[property];
};
};
}
+5 -5
View File
@@ -273,7 +273,7 @@ export class TaxonomyHelper {
oldValue: string,
newValue?: string,
pages?: Page[],
needsSettingsUpdate: boolean = true
needsSettingsUpdate = true
) {
// Retrieve all the markdown files
const allFiles = pages
@@ -349,7 +349,7 @@ export class TaxonomyHelper {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = await ArticleHelper.getContentType(article);
let fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
const fieldNames: string[] = this.getFieldsHierarchy(taxonomyType, contentType);
if (fieldNames.length > 0 && article && article.data) {
const { data } = article;
@@ -484,8 +484,8 @@ export class TaxonomyHelper {
const article = FrontMatterParser.fromFile(mdFile);
const contentType = await ArticleHelper.getContentType(article);
let oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
let newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
const oldFieldNames: string[] = this.getFieldsHierarchy(oldType, contentType);
const newFieldNames: string[] = this.getFieldsHierarchy(newType, contentType, true);
if (oldFieldNames.length > 0 && newFieldNames.length > 0 && article && article.data) {
const { data } = article;
@@ -559,7 +559,7 @@ export class TaxonomyHelper {
private static getFieldsHierarchy(
taxonomyType: TaxonomyType | string,
contentType: IContentType,
fallback: boolean = false
fallback = false
): string[] {
let fieldNames: string[] = [];
if (taxonomyType === TaxonomyType.Tag) {
+1 -1
View File
@@ -8,7 +8,7 @@ export const decodeBase64 = (dataString: string) => {
const typePart = dataParts[0].split(':').pop() as string;
const dataPart = dataParts.pop() as string;
let response: any = {};
const response: any = {};
response.type = typePart;
response.data = Buffer.from(dataPart, 'base64');
+1 -1
View File
@@ -17,7 +17,7 @@ export const processDateTimePlaceholders = (value: string, articleDate?: Date) =
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let dateFormat = placeholderParts[1].trim().replace('}}', '');
const dateFormat = placeholderParts[1].trim().replace('}}', '');
if (dateFormat) {
if (dateFormat && typeof dateFormat === 'string') {
+2 -2
View File
@@ -25,7 +25,7 @@ export const processFilePrefixPlaceholders = async (value: string, folderPath?:
);
let chars = 3;
let idxValue = files.length + 1;
const idxValue = files.length + 1;
if (value.includes('{{filePrefix.index}}')) {
const regex = new RegExp('{{filePrefix.index}}', 'g');
@@ -40,7 +40,7 @@ export const processFilePrefixPlaceholders = async (value: string, folderPath?:
for (const match of matches) {
const placeholderParts = match.split('|');
if (placeholderParts.length > 1) {
let options = placeholderParts[1].trim().replace('}}', '').split(',');
const options = placeholderParts[1].trim().replace('}}', '').split(',');
for (const option of options) {
if (option.startsWith('zeros:')) {
+1 -1
View File
@@ -11,7 +11,7 @@ export const processPathPlaceholders = (
const relPathToken = '{{pathToken.relPath}}';
if (value.includes(relPathToken) && contentFolder?.path) {
const dirName = dirname(filePath);
let relPath = relative(contentFolder.path, dirName);
const relPath = relative(contentFolder.path, dirName);
value = value.replace(relPathToken, relPath);
}
+1
View File
@@ -5,6 +5,7 @@ import { Logger } from '../../helpers/Logger';
import { PostMessageData } from '../../models';
export abstract class BaseListener {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
public static process(msg: PostMessageData) {}
/**
+15 -10
View File
@@ -17,9 +17,10 @@ export class MediaListener extends BaseListener {
public static async process(msg: PostMessageData) {
super.process(msg);
const { page, folder, sorting } = msg.payload ?? {};
switch (msg.command) {
case DashboardMessage.getMedia:
const { page, folder, sorting } = msg?.payload;
this.sendMediaFiles(page, folder, sorting);
break;
case DashboardMessage.refreshMedia:
@@ -67,11 +68,7 @@ export class MediaListener extends BaseListener {
* @param folder
* @param sorting
*/
public static async sendMediaFiles(
page: number = 0,
folder: string = '',
sorting: SortingOption | null = null
) {
public static async sendMediaFiles(page = 0, folder = '', sorting: SortingOption | null = null) {
MediaLibrary.reset();
const files = await MediaHelpers.getMedia(page, folder, sorting);
this.sendMsg(DashboardCommand.media, files);
@@ -143,7 +140,7 @@ export class MediaListener extends BaseListener {
}
if (unmappedFiles && unmappedFiles.length > 0) {
this.sendRequest(command as any, requestId, unmappedFiles);
this.sendRequest(command as DashboardCommand, requestId, unmappedFiles);
}
}
@@ -151,6 +148,7 @@ export class MediaListener extends BaseListener {
* Store the file and send a message after multiple uploads
* @param data
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static async store(data: any) {
try {
const { folder } = data;
@@ -167,7 +165,9 @@ export class MediaListener extends BaseListener {
this.sendMediaFiles(0, folder || '');
delete this.timers[folderPath];
}, 500);
} catch {}
} catch {
// Do nothing
}
}
/**
@@ -178,13 +178,16 @@ export class MediaListener extends BaseListener {
try {
MediaHelpers.deleteFile(data.file);
this.sendMediaFiles(data.page || 0, data.folder || '');
} catch {}
} catch {
// Do nothing
}
}
/**
* Update media metadata
* @param data
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static async update(data: any) {
try {
const { page, folder } = data;
@@ -192,6 +195,8 @@ export class MediaListener extends BaseListener {
await MediaHelpers.updateMetadata(data);
this.sendMediaFiles(page || 0, folder || '');
} catch {}
} catch {
// Do nothing
}
}
}
+3 -3
View File
@@ -97,7 +97,7 @@ export class PagesListener extends BaseListener {
// Recreate all the watchers
for (const folder of folders) {
const folderUri = Uri.parse(folder.path);
let watcher = workspace.createFileSystemWatcher(
const watcher = workspace.createFileSystemWatcher(
new RelativePattern(folderUri, '**/*'),
false,
false,
@@ -200,7 +200,7 @@ export class PagesListener extends BaseListener {
/**
* Retrieve all the markdown pages
*/
public static async getPagesData(clear: boolean = false, cb?: (pages: Page[]) => void) {
public static async getPagesData(clear = false, cb?: (pages: Page[]) => void) {
const ext = Extension.getInstance();
// Get data from the cache
@@ -257,7 +257,7 @@ export class PagesListener extends BaseListener {
*/
private static async createSearchIndex(pages: Page[]) {
const pagesIndex = Fuse.createIndex(
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType'],
['title', 'slug', 'description', 'fmBody', 'type', 'fmContentType', 'fmLocale.locale'],
pages
);
await Extension.getInstance().setState(
+2 -2
View File
@@ -198,7 +198,7 @@ export class SettingsListener extends BaseListener {
/**
* Retrieve the settings for the dashboard
*/
public static async getSettings(clear: boolean = false) {
public static async getSettings(clear = false) {
Logger.verbose(`SettingsListener:getSettings:start - clear: ${clear}`);
const settings = await DashboardSettings.get(clear);
Logger.verbose(
@@ -315,7 +315,7 @@ export class SettingsListener extends BaseListener {
private static async copyTemplateFiles(
files: [string, FileType][],
templateFileLocation: string,
extRelPath: string = ''
extRelPath = ''
) {
const wsFolder = Folders.getWorkspaceFolder();
if (!wsFolder) {
+11 -4
View File
@@ -30,6 +30,8 @@ import { Event, commands, extensions } from 'vscode';
import { GitAPIState, GitRepository, PostMessageData } from '../../models';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../../dashboardWebView/DashboardMessage';
export class GitListener {
private static gitAPI: {
@@ -39,7 +41,7 @@ export class GitListener {
getAPI: (version: number) => any;
repositories: GitRepository[];
} | null = null;
private static isRegistered: boolean = false;
private static isRegistered = false;
private static client: SimpleGit | null = null;
private static subClient: SimpleGit | null = null;
private static repository: GitRepository | null = null;
@@ -123,6 +125,7 @@ export class GitListener {
break;
case GeneralCommands.toVSCode.git.selectBranch:
this.selectBranch();
break;
case GeneralCommands.toVSCode.git.isRepo:
this.checkIsGitRepo(msg.command, msg.requestId);
break;
@@ -135,7 +138,11 @@ export class GitListener {
}
const isRepo = await GitListener.isGitRepository();
Dashboard.postWebviewMessage({ command: command as any, payload: isRepo, requestId });
Dashboard.postWebviewMessage({
command: command as DashboardCommand | DashboardMessage,
payload: isRepo,
requestId
});
}
/**
@@ -152,7 +159,7 @@ export class GitListener {
* @param commitMsg The commit message for the push operation.
* @param isSync Determines whether to perform a sync operation (default: true) or a fetch operation.
*/
public static async sync(commitMsg?: string, isSync: boolean = true) {
public static async sync(commitMsg?: string, isSync = true) {
try {
this.sendMsg(GeneralCommands.toWebview.git.syncingStart, isSync ? 'syncing' : 'fetching');
@@ -320,7 +327,7 @@ export class GitListener {
* @param submoduleFolder The path to the submodule folder.
* @returns The Git client instance or null if it cannot be retrieved.
*/
private static getClient(submoduleFolder: string = ''): SimpleGit | null {
private static getClient(submoduleFolder = ''): SimpleGit | null {
if (!submoduleFolder && this.client) {
return this.client;
} else if (submoduleFolder && this.subClient) {
+2 -1
View File
@@ -5,7 +5,8 @@ import { Command } from '../../panelWebView/Command';
import { PostMessageData } from '../../models';
export abstract class BaseListener {
public static process(msg: PostMessageData) {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
public static process(_: PostMessageData) {}
/**
* Send a message to the webview
+7 -4
View File
@@ -349,7 +349,7 @@ export class DataListener extends BaseListener {
metadata.articleDetails = articleDetails;
}
let updatedMetadata = Object.assign({}, metadata);
const updatedMetadata = Object.assign({}, metadata);
if (commaSeparated) {
for (const key of commaSeparated) {
if (updatedMetadata[key] && typeof updatedMetadata[key] === 'string') {
@@ -587,13 +587,14 @@ export class DataListener extends BaseListener {
* @returns
*/
public static async getParentObject(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
article: ParsedFrontMatter,
parents: string[] | undefined,
blockData?: BlockFieldData
) {
let parentObj = data;
let allParents = Object.assign([], parents);
const allParents = Object.assign([], parents);
const contentType = await ArticleHelper.getContentType(article);
let selectedIndexes: number[] = [];
if (blockData?.selectedIndex) {
@@ -611,7 +612,7 @@ export class DataListener extends BaseListener {
parentObj = article.data;
// Loop through the parents of the block field
for (const parent of blockData?.parentFields) {
for (const parent of blockData?.parentFields ?? []) {
if (!parentObj) {
continue;
}
@@ -769,6 +770,7 @@ export class DataListener extends BaseListener {
articleData: {
field: string;
value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: { [key: string]: any };
contentType?: IContentType;
},
@@ -778,7 +780,8 @@ export class DataListener extends BaseListener {
return;
}
let { field, value, data, contentType } = articleData;
const { field, data, contentType } = articleData;
let { value } = articleData;
value = value || '';
const valueBefore = value;
+20 -6
View File
@@ -1,3 +1,4 @@
import { i18n } from '../../commands';
import { ExtensionState } from '../../constants';
import { Page } from '../../dashboardWebView/models';
import { Extension } from '../../helpers';
@@ -29,14 +30,28 @@ export class FieldsListener extends BaseListener {
* @param payload
* @returns
*/
private static async searchByType(command: string, requestId?: string, type?: string) {
if (!type || !requestId) {
private static async searchByType(
command: string,
requestId?: string,
data?: { type?: string; sameLocale?: boolean; activePath?: string }
) {
if (!data?.type || !data?.activePath || !requestId) {
return;
}
const activeLocale = await i18n.getLocale(data.activePath);
if (!activeLocale?.locale) {
return;
}
PagesListener.getPagesData(false, async (pages) => {
const fuseOptions: Fuse.IFuseOptions<Page> = {
keys: [{ name: 'fmContentType', weight: 1 }]
keys: [
{ name: 'fmContentType', weight: 1 },
...(data.sameLocale ? [{ name: 'fmLocale.locale', weight: 1 }] : [])
],
findAllMatches: true,
threshold: 0
};
const pagesIndex = await Extension.getInstance().getState<Fuse.FuseIndex<Page>>(
@@ -47,9 +62,8 @@ export class FieldsListener extends BaseListener {
const fuse = new Fuse(pages || [], fuseOptions, fuseIndex);
const results = fuse.search({
$and: [
{
fmContentType: type
}
{ fmContentType: data.type! },
...(data.sameLocale ? [{ 'fmLocale.locale': activeLocale.locale }] : [])
]
});
const pageResults = results.map((page) => page.item);
+21 -1
View File
@@ -492,7 +492,7 @@ export enum LocalizationKey {
*/
dashboardDataViewDataViewCloseSelectedDataFile = 'dashboard.dataView.dataView.closeSelectedDataFile',
/**
* Select your date type first
* Select your data type first
*/
dashboardDataViewEmptyViewHeading = 'dashboard.dataView.emptyView.heading',
/**
@@ -1884,6 +1884,26 @@ export enum LocalizationKey {
* To which locale do you want to create a new content?
*/
commandsI18nCreateQuickPickPlaceHolder = 'commands.i18n.create.quickPick.placeHolder',
/**
* Open or create translation
*/
commandsI18nCreateOrOpenQuickPickTitle = 'commands.i18n.createOrOpen.quickPick.title',
/**
* Existing translations
*/
commandsI18nCreateOrOpenQuickPickCategoryExisting = 'commands.i18n.createOrOpen.quickPick.category.existing',
/**
* Open "{0}"
*/
commandsI18nCreateOrOpenQuickPickActionOpen = 'commands.i18n.createOrOpen.quickPick.action.open',
/**
* New translations
*/
commandsI18nCreateOrOpenQuickPickCategoryNew = 'commands.i18n.createOrOpen.quickPick.category.new',
/**
* Create "{0}"
*/
commandsI18nCreateOrOpenQuickPickActionCreate = 'commands.i18n.createOrOpen.quickPick.action.create',
/**
* Translating content...
*/
+1 -1
View File
@@ -1,5 +1,5 @@
import * as l10n from '@vscode/l10n';
export const localize = (key: string, ...args: any[]): string => {
export const localize = (key: string, ...args: (string | number)[]): string => {
return l10n.t(key, ...args);
};
+1
View File
@@ -6,6 +6,7 @@ export interface ContentFolder {
disableCreation?: boolean;
excludeSubdir?: boolean;
excludePaths?: string[];
previewPath?: string;
trailingSlash?: boolean;
filePrefix?: string;
+1
View File
@@ -4,6 +4,7 @@ export interface DataFile {
file: string;
fileType: 'json' | 'yaml';
labelField: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema?: any;
type?: string;
singleEntry?: boolean;
+1
View File
@@ -2,6 +2,7 @@ export interface DataFolder {
id: string;
path: string;
labelField: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema?: any;
type?: string;
singleEntry?: boolean;
+1
View File
@@ -1,4 +1,5 @@
export interface DataType {
id: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema: any;
}
+3 -1
View File
@@ -103,7 +103,7 @@ export interface Field {
type: FieldType;
choices?: string[] | Choice[];
single?: boolean;
wysiwyg?: boolean;
wysiwyg?: boolean | string;
multiple?: boolean;
isPreviewImage?: boolean;
hidden?: boolean;
@@ -135,6 +135,7 @@ export interface Field {
// Content relationship
contentTypeName?: string;
contentTypeValue?: 'path' | 'slug';
sameContentLocale?: boolean;
// Custom field
customType?: string;
@@ -169,6 +170,7 @@ export enum WhenOperator {
export interface WhenClause {
fieldRef: string;
operator: WhenOperator;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
caseSensitive?: boolean;
}
+1
View File
@@ -1,5 +1,6 @@
export interface PostMessageData {
command: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload: any;
requestId?: string;
}
+1
View File
@@ -1,5 +1,6 @@
export interface Project {
name: string;
default?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
configuration: any;
}
+1 -1
View File
@@ -19,5 +19,5 @@ export type SnippetSpecialPlaceholders = 'FM_SELECTED_TEXT' | string;
export interface SnippetField extends Field {
default?: SnippetSpecialPlaceholders;
value?: any;
value?: unknown;
}
+2 -4
View File
@@ -21,9 +21,7 @@ import { DEFAULT_PANEL_FEATURE_FLAGS } from '../constants/DefaultFeatureFlags';
export interface IViewPanelProps { }
export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
{ }: React.PropsWithChildren<IViewPanelProps>
) => {
export const ViewPanel: React.FunctionComponent<IViewPanelProps> = () => {
const [isDevMode, setIsDevMode] = useState(false);
const {
loading,
@@ -38,7 +36,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
const prevMediaSelection = usePrevious(mediaSelecting);
const [scrollY, setScrollY] = useState(0);
const scollListener = useCallback((e: Event) => {
const scollListener = useCallback(() => {
if (!mediaSelecting) {
setScrollY(window.scrollY);
}
+1 -1
View File
@@ -35,7 +35,7 @@ const Actions: React.FunctionComponent<IActionsProps> = ({
};
const actions = React.useMemo(() => {
let allActions: JSX.Element[] = [];
const allActions: JSX.Element[] = [];
if (!disableActions.includes(`openDashboard`)) {
allActions.push(
+2 -2
View File
@@ -26,7 +26,7 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({
return;
}
const prevState: any = Messenger.getState();
const prevState = Messenger.getState() as { [key: string]: string };
if (
!prevState ||
@@ -51,7 +51,7 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({
}, [collapseKey]);
const updateStorage = (value: boolean) => {
const prevState: any = Messenger.getState();
const prevState = Messenger.getState() as { [key: string]: string };
Messenger.setState({
...prevState,
[collapseKey]: value.toString()
@@ -16,7 +16,7 @@ export interface IDataBlockFieldProps {
settings: PanelSettings;
field: Field;
parentFields: string[];
value: any;
value: unknown[];
blockData: BlockFieldData | undefined;
filePath: string;
fieldsRenderer: (
@@ -24,10 +24,10 @@ export interface IDataBlockFieldProps {
parent: IMetadata,
parentFields: string[],
blockData?: BlockFieldData,
onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void,
onFieldUpdate?: (field: string | undefined, value: unknown, parents: string[]) => void,
parentBlock?: string | null
) => (JSX.Element | null)[] | undefined;
onSubmit: (data: any) => void;
onSubmit: (data: unknown) => void;
parentBlock: string | null | undefined;
required?: boolean;
}
@@ -48,7 +48,7 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
}: React.PropsWithChildren<IDataBlockFieldProps>) => {
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [selectedGroup, setSelectedGroup] = useState<FieldGroup | undefined | null>(null);
const [selectedBlockData, setSelectedBlockData] = useState<any | null>(null);
const [selectedBlockData, setSelectedBlockData] = useState<unknown | null>(null);
const [hideSubBlock, setHideSubBlock] = useState<boolean>(true);
const SELECTION_STATE_KEY = useMemo(
@@ -61,19 +61,20 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
);
const onFieldUpdate = useCallback(
(crntField: string | undefined, crntValue: any, parents: string[]) => {
const dataClone: any[] = Object.assign([], value);
(crntField: string | undefined, crntValue: unknown, parents: string[]) => {
const dataClone: unknown[] = Object.assign([], value);
if (!crntField) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let data: any = {};
if (selectedIndex !== null && selectedIndex !== undefined) {
data = Object.assign({}, dataClone[selectedIndex]);
}
let parentObj: any = data;
let parentObj: { [key: string]: unknown } = data;
if (parents.length > 1) {
// Get last parent
@@ -85,7 +86,7 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
if (!parentObj[lastParent]) {
parentObj[lastParent] = {};
}
parentObj = parentObj[lastParent];
parentObj = parentObj[lastParent] as { [key: string]: unknown };
}
}
@@ -125,7 +126,7 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
const deleteItem = useCallback(
(index: number) => {
const dataClone: any[] = Object.assign([], value);
const dataClone: unknown[] = Object.assign([], value);
if (!value) {
return;
@@ -204,7 +205,7 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
// Show the form
onShowForm();
const fieldData = value[index];
const fieldData = value[index] as { fieldGroup?: string };
if (index !== null && settings?.fieldGroups) {
const fieldGroup = fieldData?.fieldGroup;
const group = settings?.fieldGroups.find((group) => group.id === fieldGroup);
@@ -244,13 +245,13 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
// Storing the state to local storage (does not need to be persistent)
const updateSelectionState = useCallback(
(value: number | undefined) => {
localStorage.setItem(SELECTION_STATE_KEY, value as any);
localStorage.setItem(SELECTION_STATE_KEY, value?.toString() || '');
},
[SELECTION_STATE_KEY]
);
const getSelectedIndex = useCallback(() => {
let crntValue = [];
const crntValue = [];
if (blockData?.selectedIndex !== null && blockData?.selectedIndex !== undefined) {
crntValue.push(blockData.selectedIndex);
@@ -10,7 +10,7 @@ import { VSCodeLabel } from '../VSCode';
export interface IDataBlockRecordsProps {
fieldGroups?: FieldGroup[];
records: any[];
records: unknown[];
selectedIndex: number | null;
onAdd: () => void;
onSort: (obj: SortEnd) => void;
@@ -18,7 +18,7 @@ export interface IDataBlockRecordsProps {
onDelete: (id: number) => void;
}
const Container = SortableContainer(({ children }: React.PropsWithChildren<any>) => {
const Container = SortableContainer(({ children }: React.PropsWithChildren<unknown>) => {
return <ul>{children}</ul>;
});
@@ -27,11 +27,10 @@ export const DataBlockRecords = ({
records,
selectedIndex,
onSort,
onAdd,
onEdit,
onDelete
}: React.PropsWithChildren<IDataBlockRecordsProps>) => {
const getLabel = useCallback((record: any) => {
const getLabel = useCallback((record) => {
if (record.fieldGroup) {
if (fieldGroups) {
const fieldGroup = fieldGroups.find((f) => f.id === record.fieldGroup);
@@ -64,7 +63,7 @@ export const DataBlockRecords = ({
</VSCodeLabel>
<Container onSortEnd={onSort} useDragHandle>
{records.map((v: any, idx: number) => (
{records.map((v: unknown, idx: number) => (
<DataBlockRecord
key={idx}
id={idx}
@@ -12,191 +12,195 @@ import { FieldTitle } from './FieldTitle';
import { FieldMessage } from './FieldMessage';
import { ChoiceButton } from './ChoiceButton';
import useDropdownStyle from '../../hooks/useDropdownStyle';
import useMessages from '../../hooks/useMessages';
export interface IContentTypeRelationshipFieldProps extends BaseFieldProps<string | string[]> {
contentTypeName?: string;
contentTypeValue?: string;
sameContentLocale?: boolean;
multiSelect?: boolean;
onChange: (value: string | string[]) => void;
}
export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeRelationshipFieldProps> = ({
export const ContentTypeRelationshipField: React.FunctionComponent<
IContentTypeRelationshipFieldProps
> = ({
label,
description,
value,
contentTypeName,
contentTypeValue,
sameContentLocale,
multiSelect,
onChange,
required
}: React.PropsWithChildren<IContentTypeRelationshipFieldProps>) => {
const [loading, setLoading] = React.useState<boolean>(false);
const [choices, setChoices] = React.useState<string[]>([]);
const [pages, setPages] = React.useState<Page[]>([]);
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>(value);
const [filter, setFilter] = React.useState<string | undefined>(undefined);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any, '6px');
const [loading, setLoading] = React.useState<boolean>(false);
const [choices, setChoices] = React.useState<string[]>([]);
const [pages, setPages] = React.useState<Page[]>([]);
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>(value);
const [filter, setFilter] = React.useState<string | undefined>(undefined);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any, '6px');
const { metadata } = useMessages();
/**
* Check the required state
*/
const showRequiredState = useMemo(() => {
return (
required && ((crntSelected instanceof Array && crntSelected.length === 0) || !crntSelected)
);
}, [required, crntSelected]);
/**
* Retrieve the value based on the field setting
* @param value
* @param type
* @returns
*/
const getValue = (value: Page, type = 'path') => {
if (type === 'path') {
return value.fmRelFilePath || value.fmFilePath;
}
return `${value[type]}`;
};
/**
* Retrieve choice value to display
*/
const getChoiceValue = useCallback(
(value: string) => {
const choice = pages.find((p: Page) => getValue(p, contentTypeValue) === value);
if (choice) {
return choice.title;
}
return '';
},
[pages, choices, contentTypeValue]
);
/**
* On selecting an option
* @param txtValue
*/
const onSelect = (option: string) => {
setFilter(undefined);
if (multiSelect) {
if (option) {
const crntValue = typeof crntSelected === 'string' ? [crntSelected] : crntSelected;
const newValue = [...((crntValue || []) as string[]), option];
const uniqueValues = [...new Set(newValue)];
setCrntSelected(uniqueValues);
onChange(uniqueValues);
}
} else if (option) {
setCrntSelected(option);
onChange(option);
}
};
/**
* Remove a selected value
* @param txtValue
*/
const removeSelected = useCallback(
(txtValue: string) => {
if (multiSelect) {
const newValue = [...(crntSelected || [])].filter((v) => v !== txtValue);
setCrntSelected(newValue);
onChange(newValue);
} else {
setCrntSelected('');
onChange('');
}
},
[multiSelect, crntSelected, onChange]
);
/**
* Retrieve the available choices
*/
const availableChoices = useMemo(() => {
return pages.filter((page: Page) => {
const value = contentTypeValue === 'slug' ? page.slug : page.fmFilePath;
let toShow = true;
if (typeof crntSelected === 'string') {
toShow = crntSelected !== `${value}` && !value.includes(crntSelected);
} else if (crntSelected instanceof Array) {
const selected = crntSelected.filter((v) => v === `${value}` || value.includes(v));
toShow = selected.length === 0;
}
if (toShow && filter) {
return page.title.toLowerCase().includes(filter);
}
return toShow;
});
}, [choices, crntSelected, multiSelect, contentTypeValue, filter]);
/**
* Retrieve the selected value
*/
useEffect(() => {
if (crntSelected !== value) {
setCrntSelected(value);
}
}, [value]);
/**
* Retrieve the pages based on the content type
*/
useEffect(() => {
if (contentTypeName && metadata?.filePath) {
setLoading(true);
messageHandler
.request<Page[]>(CommandToCode.searchByType, {
type: contentTypeName,
sameLocale: sameContentLocale ?? true,
activePath: metadata?.filePath
})
.then((pages: Page[]) => {
setPages(pages || []);
setChoices((pages || []).map((page) => page.title));
})
.finally(() => {
setLoading(false);
});
}
}, [contentTypeName, sameContentLocale, metadata?.filePath]);
/**
* Check the required state
*/
const showRequiredState = useMemo(() => {
return (
required && ((crntSelected instanceof Array && crntSelected.length === 0) || !crntSelected)
);
}, [required, crntSelected]);
<div className={`metadata_field ${showRequiredState ? 'required' : ''}`}>
<FieldTitle label={label} icon={<DocumentPlusIcon />} required={required} />
/**
* Retrieve the value based on the field setting
* @param value
* @param type
* @returns
*/
const getValue = (value: Page, type: string = "path") => {
if (type === 'path') {
return value.fmRelFilePath || value.fmFilePath;
}
return `${value[type]}`;
};
/**
* Retrieve choice value to display
*/
const getChoiceValue = useCallback((value: string) => {
const choice = pages.find(
(p: Page) => getValue(p, contentTypeValue) === value
);
if (choice) {
return choice.title;
}
return '';
}, [pages, choices, contentTypeValue]);
/**
* On selecting an option
* @param txtValue
*/
const onSelect = (option: string) => {
setFilter(undefined);
if (multiSelect) {
if (option) {
const crntValue = typeof crntSelected === 'string' ? [crntSelected] : crntSelected;
const newValue = [...((crntValue || []) as string[]), option];
const uniqueValues = [...new Set(newValue)];
setCrntSelected(uniqueValues);
onChange(uniqueValues);
}
} else if (option) {
setCrntSelected(option);
onChange(option);
}
};
/**
* Remove a selected value
* @param txtValue
*/
const removeSelected = useCallback((txtValue: string) => {
if (multiSelect) {
const newValue = [...(crntSelected || [])].filter((v) => v !== txtValue);
setCrntSelected(newValue);
onChange(newValue);
} else {
setCrntSelected('');
onChange('');
}
}, [multiSelect, crntSelected, onChange]);
/**
* Retrieve the available choices
*/
const availableChoices = useMemo(() => {
return pages.filter((page: Page) => {
const value = contentTypeValue === "slug" ? page.slug : page.fmFilePath;
let toShow = true;
if (typeof crntSelected === 'string') {
toShow = crntSelected !== `${value}` && !value.includes(crntSelected);
} else if (crntSelected instanceof Array) {
const selected = crntSelected.filter((v) => v === `${value}` || value.includes(v));
toShow = selected.length === 0;
}
if (toShow && filter) {
return page.title.toLowerCase().includes(filter);
}
return toShow;
});
}, [choices, crntSelected, multiSelect, contentTypeValue, filter]);
/**
* Retrieve the selected value
*/
useEffect(() => {
if (crntSelected !== value) {
setCrntSelected(value);
}
}, [value]);
/**
* Retrieve the pages based on the content type
*/
useEffect(() => {
if (contentTypeName) {
setLoading(true);
messageHandler
.request<Page[]>(CommandToCode.searchByType, contentTypeName)
.then((pages: Page[]) => {
setPages(pages || []);
setChoices((pages || []).map(page => page.title))
}).finally(() => {
setLoading(false);
});
}
}, [contentTypeName]);
return (
<div className={`metadata_field ${showRequiredState ? 'required' : ''}`}>
<FieldTitle
label={label}
icon={<DocumentPlusIcon />}
required={required} />
{
loading ? (
<div className='metadata_field__wrapper'>
<div className='metadata_field__loading'>
{loading ? (
<div className="metadata_field__wrapper">
<div className="metadata_field__loading">
{l10n.t(LocalizationKey.panelFieldsContentTypeRelationshipFieldLoading)}
</div>
</div>
) : (
<Combobox
value={crntSelected}
onChange={onSelect}
>
<Combobox value={crntSelected} onChange={onSelect}>
{({ open }) => (
<div className="relative">
<div className="relative w-full cursor-default overflow-hidden text-left focus:outline-none border border-solid border-[var(--vscode-inputValidation-infoBorder)] rounded">
<div className="relative w-full cursor-default overflow-hidden rounded border border-solid border-[var(--vscode-inputValidation-infoBorder)] text-left focus:outline-none">
<Combobox.Input
className="w-full border-none py-2 pl-3 pr-10 leading-5 focus:ring-0 text-[var(--vscode-input-foreground)]"
className="w-full border-none py-2 pl-3 pr-10 leading-5 text-[var(--vscode-input-foreground)] focus:ring-0"
onChange={(e) => setFilter(e.target.value)}
value={filter || ""}
value={filter || ''}
placeholder={l10n.t(LocalizationKey.panelFieldsChoiceFieldSelect, label)}
ref={inputRef} />
ref={inputRef}
/>
<Combobox.Button
className="absolute inset-y-0 right-0 flex items-center w-8 bg-inherit rounded-none text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-button-foreground)]">
<ChevronDownIcon
className="h-5 w-5"
aria-hidden="true"
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex w-8 items-center rounded-none bg-inherit text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-button-foreground)]">
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
</Combobox.Button>
</div>
@@ -208,7 +212,7 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
afterLeave={() => setFilter('')}
>
<Combobox.Options
className="field_dropdown absolute max-h-60 w-full shadow-lg overflow-auto py-1 px-0 space-y-1 text-base focus:outline-none z-50 bg-[var(--vscode-dropdown-background)] text-[var(--vscode-dropdown-foreground)] border border-solid border-[var(--vscode-dropdown-border)]"
className="field_dropdown absolute z-50 max-h-60 w-full space-y-1 overflow-auto border border-solid border-[var(--vscode-dropdown-border)] bg-[var(--vscode-dropdown-background)] px-0 py-1 text-base text-[var(--vscode-dropdown-foreground)] shadow-lg focus:outline-none"
style={{
bottom: getDropdownStyle(open)
}}
@@ -217,8 +221,15 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
<Combobox.Option
key={choice.fmFilePath}
value={getValue(choice, contentTypeValue)}
className={({ active }) => `py-[var(--input-padding-vertical)] px-[var(--input-padding-horizontal)] list-none cursor-pointer hover:text-[var(--vscode-button-foreground)] hover:bg-[var(--vscode-button-hoverBackground)] ${active ? "text-[var(--vscode-button-foreground)] bg-[var(--vscode-button-hoverBackground)] " : ""}`}>
className={({ active }) =>
`cursor-pointer list-none px-[var(--input-padding-horizontal)] py-[var(--input-padding-vertical)] hover:bg-[var(--vscode-button-hoverBackground)] hover:text-[var(--vscode-button-foreground)] ${active
? 'bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] '
: ''
}`
}
>
{choice.title}
<div className="mt-0.5 text-xs opacity-60">{choice.slug}</div>
</Combobox.Option>
))}
@@ -232,23 +243,21 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
</div>
)}
</Combobox>
)
}
)}
<FieldMessage
name={label.toLowerCase()}
description={description}
showRequired={showRequiredState}
/>
<FieldMessage
name={label.toLowerCase()}
description={description}
showRequired={showRequiredState}
/>
{
pages.length > 0 && (
crntSelected instanceof Array
{pages.length > 0 &&
(crntSelected instanceof Array
? crntSelected.map((value: string) => (
<ChoiceButton
key={value}
value={value}
className='w-full mr-0 flex justify-between'
className="mr-0 flex w-full justify-between"
title={getChoiceValue(value)}
onClick={removeSelected}
/>
@@ -257,13 +266,11 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
<ChoiceButton
key={crntSelected}
value={crntSelected}
className='w-full mr-0 flex justify-between'
className="mr-0 flex w-full justify-between"
title={getChoiceValue(crntSelected)}
onClick={removeSelected}
/>
)
)
}
</div>
)
};
))}
</div>
);
};
@@ -175,7 +175,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
* Inserts a tag which is not known
* @param closeMenu
*/
const insertUnkownTag = (closeMenu: (cb?: any) => void) => {
const insertUnkownTag = (closeMenu: (cb?: unknown) => void) => {
if (inputValue) {
onSelect(inputValue);
closeMenu();
@@ -211,7 +211,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
* @param e
*/
const onEnterData = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>, closeMenu: Function, highlightedIndex: any) => {
(e: React.KeyboardEvent<HTMLInputElement>, closeMenu: () => void, highlightedIndex: number | null) => {
if (
e.key === 'Enter' &&
e.type === 'keydown' &&
@@ -433,8 +433,8 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
<input
{...getInputProps({
ref: inputRef,
onFocus: openMenu as any,
onClick: openMenu as any,
onFocus: openMenu as () => void,
onClick: openMenu as () => void,
onKeyDown: (e) => onEnterData(e, closeMenu, highlightedIndex),
onBlur: () => {
closeMenu();
@@ -453,7 +453,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
className={`article__tags__input__button`}
title={localize(LocalizationKey.panelTagPickerUnkown)}
disabled={!inputValue || checkIsDisabled()}
onClick={() => insertUnkownTag(closeMenu)}
onClick={() => insertUnkownTag(closeMenu as (cb?: unknown) => void)}
>
<AddIcon />
</button>
@@ -16,7 +16,6 @@ const DEBOUNCE_TIME = 300;
export interface ITextFieldProps extends BaseFieldProps<string> {
singleLine: boolean | undefined;
wysiwyg: boolean | undefined;
limit: number | undefined;
rows?: number;
name: string;
@@ -26,12 +25,9 @@ export interface ITextFieldProps extends BaseFieldProps<string> {
onChange: (txtValue: string) => void;
}
const WysiwygField = React.lazy(() => import('./WysiwygField'));
export const TextField: React.FunctionComponent<ITextFieldProps> = ({
placeholder,
singleLine,
wysiwyg,
limit,
label,
description,
@@ -199,13 +195,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
</div>
)}
{wysiwyg ? (
<React.Suspense
fallback={<div>{localize(LocalizationKey.panelFieldsTextFieldLoading)}</div>}
>
<WysiwygField text={text || ''} onChange={onTextChange} />
</React.Suspense>
) : singleLine ? (
{singleLine ? (
<input
className={`metadata_field__input`}
value={text || ''}
@@ -27,12 +27,14 @@ import {
NumberField,
CustomField,
FieldCollection,
TagPicker
TagPicker,
WYSIWYGType
} from '.';
import { fieldWhenClause } from '../../../utils/fieldWhenClause';
import { ContentTypeRelationshipField } from './ContentTypeRelationshipField';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { LocalizationKey, localize } from '../../../localization';
import { GeneralCommands } from '../../../constants';
const WysiwygField = React.lazy(() => import('./WysiwygField'));
export interface IWrapperFieldProps {
field: Field;
@@ -210,24 +212,43 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
limit = settings?.seo.description;
}
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TextField
name={field.name}
label={field.title || field.name}
description={field.description}
singleLine={field.single}
limit={limit}
wysiwyg={field.wysiwyg}
rows={4}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required}
settings={settings}
actions={field.actions}
/>
</FieldBoundary>
);
if (field.wysiwyg) {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<React.Suspense
fallback={<div>{localize(LocalizationKey.panelFieldsTextFieldLoading)}</div>}
>
<WysiwygField
name={field.name}
label={field.title || field.name}
description={field.description}
limit={limit}
type={typeof field.wysiwyg === 'boolean' ? 'html' : field.wysiwyg.toLowerCase() as WYSIWYGType}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required} />
</React.Suspense>
</FieldBoundary>
);
} else {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TextField
name={field.name}
label={field.title || field.name}
description={field.description}
singleLine={field.single}
limit={limit}
rows={4}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required}
settings={settings}
actions={field.actions}
/>
</FieldBoundary>
);
}
} else if (field.type === 'number') {
let nrValue: number | null = field.numberOptions?.isDecimal ? parseFloat(fieldValue as string) : parseInt(fieldValue as string);
if (isNaN(nrValue)) {
@@ -497,6 +518,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
required={!!field.required}
contentTypeName={field.contentTypeName}
contentTypeValue={field.contentTypeValue}
sameContentLocale={field.sameContentLocale}
multiSelect={field.multiple}
onChange={onFieldChange}
/>
@@ -555,7 +577,10 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
</FieldBoundary>
);
} else {
console.warn(l10n.t(LocalizationKey.panelFieldsWrapperFieldUnknown, field.type));
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: localize(LocalizationKey.panelFieldsWrapperFieldUnknown, field.type),
location: 'PANEL'
});
return null;
}
};
@@ -1,26 +1,188 @@
import * as React from 'react';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ReactQuill = require('react-quill');
import 'react-quill/dist/quill.snow.css';
export interface IWysiwygFieldProps {
text: string;
import { PencilIcon } from '@heroicons/react/24/outline';
import { unified } from "unified";
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import remarkStringify from "remark-stringify";
import rehypeParse from "rehype-parse";
import rehypeRemark from "rehype-remark";
import rehypeStringify from 'rehype-stringify'
import { differenceInSeconds } from "date-fns";
import { BaseFieldProps } from '../../../models';
import { FieldMessage, FieldTitle } from '.';
import { LocalizationKey, localize } from '../../../localization';
import { useRecoilState } from 'recoil';
import { RequiredFieldsAtom } from '../../state';
import { useDebounce } from '../../../hooks/useDebounce';
const DEBOUNCE_TIME = 500;
function markdownToHtml(markdownText: string) {
const file = unified()
.use(remarkParse)
.use(remarkRehype)
.use(remarkGfm)
.use(rehypeStringify)
.processSync(markdownText);
const htmlContents = String(file);
return htmlContents.replace(/<del>/g, '<s>').replace(/<\/del>/g, '</s>');
}
function htmlToMarkdown(htmlText: string) {
const file = unified()
.use(rehypeParse, { emitParseErrors: true, duplicateAttribute: false })
.use(rehypeRemark)
.use(remarkGfm)
.use(remarkStringify)
.processSync(htmlText);
return String(file);
}
export type WYSIWYGType = "html" | "markdown";
export interface IWysiwygFieldProps extends BaseFieldProps<string> {
name: string;
limit?: number;
type: WYSIWYGType;
onChange: (txtValue: string) => void;
}
const WysiwygField: React.FunctionComponent<IWysiwygFieldProps> = ({
text,
onChange
label,
description,
value,
type = "html",
onChange,
limit,
required
}: React.PropsWithChildren<IWysiwygFieldProps>) => {
const modules = {
toolbar: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
]
};
const [, setRequiredFields] = useRecoilState(RequiredFieldsAtom);
const [lastUpdated, setLastUpdated] = React.useState<number | null>(null);
const [text, setText] = React.useState<string | null | undefined>(type === "html" ? value : markdownToHtml(value || ""));
const debouncedText = useDebounce<string | null | undefined>(text, DEBOUNCE_TIME);
return <ReactQuill modules={modules} value={text || ''} onChange={onChange} />;
const onTextChange = (newValue: string) => {
setText(newValue);
setLastUpdated(Date.now());
}
const modules = React.useMemo(() => {
const styles = ['bold', 'italic', 'strike'];
if (type === "html") {
styles.push('underline');
}
return {
toolbar: [
[{ header: [1, 2, 3, false] }],
styles,
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
]
};
}, [type]);
const isValid = React.useMemo(() => {
let temp = true;
if (limit && limit !== -1) {
temp = (text || '').length <= limit;
}
return temp;
}, [limit, text]);
const updateRequired = React.useCallback(
(isValid: boolean) => {
setRequiredFields((prev) => {
let clone = Object.assign([], prev);
if (isValid) {
clone = clone.filter((item) => item !== label);
} else {
clone.push(label);
}
return clone;
});
},
[setRequiredFields]
);
const showRequiredState = React.useMemo(() => {
return required && !text;
}, [required, text]);
const border = React.useMemo(() => {
if (showRequiredState) {
updateRequired(false);
return '1px solid var(--vscode-inputValidation-errorBorder)';
} else if (!isValid) {
updateRequired(true);
return '1px solid var(--vscode-inputValidation-warningBorder)';
} else {
updateRequired(true);
return '1px solid var(--vscode-inputValidation-infoBorder)';
}
}, [showRequiredState, isValid]);
/**
* Update the text value when the value changes
*/
React.useEffect(() => {
if (text !== value && (lastUpdated === null || differenceInSeconds(Date.now(), lastUpdated) > 2)) {
setText(type === "html" ? value : markdownToHtml(value || ""));
}
setLastUpdated(null);
}, [value]);
/**
* Update the value when the debounced text changes
*/
React.useEffect(() => {
if (debouncedText !== undefined && value !== debouncedText && lastUpdated !== null) {
const valueToUpdate = type === "html" ? debouncedText : htmlToMarkdown(debouncedText || "");
onChange(valueToUpdate || "");
}
}, [debouncedText]);
return (
<div className={`metadata_field`}>
<FieldTitle
label={label}
icon={<PencilIcon />}
required={required}
/>
<ReactQuill
modules={modules}
value={text || ''}
onChange={onTextChange}
style={{ border }}
/>
{limit && limit > 0 && (text || '').length > limit && (
<div className={`metadata_field__limit`}>
{localize(LocalizationKey.panelFieldsTextFieldLimit, `${(text || '').length}/${limit}`)}
</div>
)}
<FieldMessage
description={description}
name={label.toLowerCase()}
showRequired={showRequiredState}
/>
</div>
);
};
export default WysiwygField;
@@ -38,8 +38,6 @@ export const GitAction: React.FunctionComponent<IGitActionProps> = ({
if (command === GeneralCommands.toWebview.git.syncingStart) {
setIsSyncing(payload || "syncing");
} else if (command === GeneralCommands.toWebview.git.syncingStart) {
setIsSyncing("syncing");
} else if (command === GeneralCommands.toWebview.git.syncingEnd) {
setCommitMessage(undefined);
setIsSyncing("idle");
@@ -1,10 +1,8 @@
import * as React from 'react';
export interface IAddIconProps {}
export interface IAddIconProps { }
export const AddIcon: React.FunctionComponent<IAddIconProps> = (
props: React.PropsWithChildren<IAddIconProps>
) => {
export const AddIcon: React.FunctionComponent<IAddIconProps> = () => {
return (
<svg
width="16"
@@ -1,10 +1,8 @@
import * as React from 'react';
export interface IArchiveIconProps {}
export interface IArchiveIconProps { }
export const ArchiveIcon: React.FunctionComponent<IArchiveIconProps> = (
props: React.PropsWithChildren<IArchiveIconProps>
) => {
export const ArchiveIcon: React.FunctionComponent<IArchiveIconProps> = () => {
return (
<svg
width="16"

Some files were not shown because too many files have changed in this diff Show More