From 22ce41c3eb26aba5b7a13d3cabd73e87fc5a75e9 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 21 Nov 2024 16:14:25 +0100 Subject: [PATCH] #705 - UX improvements for SEO panel --- CHANGELOG.md | 1 + assets/media/styles.css | 5 - l10n/bundle.l10n.de.json | 8 +- l10n/bundle.l10n.fr.json | 8 +- l10n/bundle.l10n.it.json | 8 +- l10n/bundle.l10n.ja.json | 8 +- l10n/bundle.l10n.json | 12 +- package-lock.json | 15 + package.json | 334 ++++++++++-------- src/localization/localization.enum.ts | 24 +- .../components/ArticleDetails.tsx | 83 ++--- src/panelWebView/components/SeoFieldInfo.tsx | 9 +- .../components/SeoKeywordInfo.tsx | 74 ++-- src/panelWebView/components/SeoKeywords.tsx | 112 +++++- src/panelWebView/components/SeoStatus.tsx | 27 +- src/panelWebView/components/ValidInfo.tsx | 10 +- 16 files changed, 405 insertions(+), 333 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddca57df..7a18b36a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 🎨 Enhancements +- [#705](https://github.com/estruyf/vscode-front-matter/issues/705): UX improvements for the panel view - [#887](https://github.com/estruyf/vscode-front-matter/issues/887): Added new `frontMatter.global.timezone` setting, by default it is set to `UTC` for date formatting - [#888](https://github.com/estruyf/vscode-front-matter/issues/888): Added the ability to prompt GitHub Copilot from a custom script/action diff --git a/assets/media/styles.css b/assets/media/styles.css index ae759269..8cb1254f 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -99,11 +99,6 @@ margin-right: 0.5rem; } -.seo__status__details, -.seo__status__keywords { - margin-bottom: 1rem; -} - .collapsible__body h4 { text-align: center; font-weight: bold; diff --git a/l10n/bundle.l10n.de.json b/l10n/bundle.l10n.de.json index cc624e59..7b42c77e 100644 --- a/l10n/bundle.l10n.de.json +++ b/l10n/bundle.l10n.de.json @@ -258,9 +258,6 @@ "panel.fields.textField.limit": "Feldgrenze erreicht {0}", "panel.fields.wrapperField.unknown": "Unbekannter Feldtyp: {0}", "panel.actions.title": "Aktionen", - "panel.articleDetails.title": "Weitere Details", - "panel.articleDetails.type": "Typ", - "panel.articleDetails.total": "Gesamt", "panel.articleDetails.headings": "Überschriften", "panel.articleDetails.paragraphs": "Absätze", "panel.articleDetails.internalLinks": "Interne Links", @@ -299,16 +296,13 @@ "panel.publishAction.publish": "Veröffentlichen", "panel.publishAction.unpublish": "Zurück zu Entwurf", "panel.seoDetails.recommended": "Empfohlen", - "panel.seoKeywordInfo.density": "Stichwortdichte {0} *", "panel.seoKeywordInfo.validInfo.label": "Verwendet in Überschrift(en)", "panel.seoKeywordInfo.validInfo.content": "Inhalt", "panel.seoKeywords.title": "Stichwörter", "panel.seoKeywords.header.keyword": "Stichwort", "panel.seoKeywords.header.details": "Details", - "panel.seoKeywords.density": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.", - "panel.seoStatus.title": "Empfehlungen", + "panel.seoKeywords.density.description": "* Eine Stichwortdichte von 1-1,5 % ist in den meisten Fällen ausreichend.", "panel.seoStatus.header.property": "Eigenschaft", - "panel.seoStatus.header.length": "Länge", "panel.seoStatus.header.valid": "Gültig", "panel.seoStatus.seoFieldInfo.characters": "{0} Zeichen", "panel.seoStatus.seoFieldInfo.words": "{0} Wörter", diff --git a/l10n/bundle.l10n.fr.json b/l10n/bundle.l10n.fr.json index e7da0220..46e9acf7 100644 --- a/l10n/bundle.l10n.fr.json +++ b/l10n/bundle.l10n.fr.json @@ -263,9 +263,6 @@ "panel.fields.textField.limit": "Limite de champ atteinte {0}", "panel.fields.wrapperField.unknown": "Type de champ inconnu : {0}", "panel.actions.title": "Actions", - "panel.articleDetails.title": "Plus de détails", - "panel.articleDetails.type": "Type", - "panel.articleDetails.total": "Total", "panel.articleDetails.headings": "En-têtes", "panel.articleDetails.paragraphs": "Paragraphes", "panel.articleDetails.internalLinks": "Liens internes", @@ -304,16 +301,13 @@ "panel.publishAction.publish": "Publié", "panel.publishAction.unpublish": "Retourner au brouillon", "panel.seoDetails.recommended": "Recommandé", - "panel.seoKeywordInfo.density": "Utilisation du mot clé {0} *", "panel.seoKeywordInfo.validInfo.label": "Utilisé dans le ou les en-tête(s)", "panel.seoKeywordInfo.validInfo.content": "Contenu", "panel.seoKeywords.title": "Mot-clés", "panel.seoKeywords.header.keyword": "Mot-clé", "panel.seoKeywords.header.details": "Détails", - "panel.seoKeywords.density": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas", - "panel.seoStatus.title": "Recommandations", + "panel.seoKeywords.density.description": "* Une densité de mot-clé de 1-1.5% est suffisante dans la plupart des cas", "panel.seoStatus.header.property": "Propriété", - "panel.seoStatus.header.length": "Longueur", "panel.seoStatus.header.valid": "Valide", "panel.seoStatus.seoFieldInfo.characters": "{0} caractères", "panel.seoStatus.seoFieldInfo.words": "{0} mots", diff --git a/l10n/bundle.l10n.it.json b/l10n/bundle.l10n.it.json index 3a6946db..f8fe310d 100644 --- a/l10n/bundle.l10n.it.json +++ b/l10n/bundle.l10n.it.json @@ -263,9 +263,6 @@ "panel.fields.textField.limit": "Limite di campi raggiunto {0}", "panel.fields.wrapperField.unknown": "Tipo di campo sconosciuto: {0}", "panel.actions.title": "Azioni", - "panel.articleDetails.title": "Più dettagli", - "panel.articleDetails.type": "Digitare", - "panel.articleDetails.total": "Totale", "panel.articleDetails.headings": "Intestazioni", "panel.articleDetails.paragraphs": "Paragrafi", "panel.articleDetails.internalLinks": "Collegamenti esterni", @@ -304,16 +301,13 @@ "panel.publishAction.publish": "Pubblica", "panel.publishAction.unpublish": "Tornare alla bozza", "panel.seoDetails.recommended": "Raccomandato", - "panel.seoKeywordInfo.density": "Utilizzo delle parole chiave {0} *", "panel.seoKeywordInfo.validInfo.label": "Utilizzato nelle rubriche", "panel.seoKeywordInfo.validInfo.content": "Contenuto", "panel.seoKeywords.title": "Parole chiavi", "panel.seoKeywords.header.keyword": "Parola chiave", "panel.seoKeywords.header.details": "Dettagli", - "panel.seoKeywords.density": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.", - "panel.seoStatus.title": "Consigli", + "panel.seoKeywords.density.description": "* Una densità di parole chiave dell'1-1,5% è sufficiente nella maggior parte dei casi.", "panel.seoStatus.header.property": "Proprietà", - "panel.seoStatus.header.length": "Lunghezza", "panel.seoStatus.header.valid": "Valido", "panel.seoStatus.seoFieldInfo.characters": "{0} caratteri", "panel.seoStatus.seoFieldInfo.words": "{0} parole", diff --git a/l10n/bundle.l10n.ja.json b/l10n/bundle.l10n.ja.json index 4e877803..a931e9fe 100644 --- a/l10n/bundle.l10n.ja.json +++ b/l10n/bundle.l10n.ja.json @@ -438,9 +438,6 @@ "panel.actions.title": "コマンド", - "panel.articleDetails.title": "詳細", - "panel.articleDetails.type": "項目", - "panel.articleDetails.total": "数", "panel.articleDetails.headings": "見出し", "panel.articleDetails.paragraphs": "パラグラフ", "panel.articleDetails.internalLinks": "内部リンク", @@ -489,18 +486,15 @@ "panel.seoDetails.recommended": "推奨", - "panel.seoKeywordInfo.density": "キーワード出現率 {0} *", "panel.seoKeywordInfo.validInfo.label": "見出しへの利用", "panel.seoKeywordInfo.validInfo.content": "本文", "panel.seoKeywords.title": "キーワード", "panel.seoKeywords.header.keyword": "キーワード", "panel.seoKeywords.header.details": "詳細", - "panel.seoKeywords.density": "* キーワード出現率は通常1~1.5%で十分です。", + "panel.seoKeywords.density.description": "* キーワード出現率は通常1~1.5%で十分です。", - "panel.seoStatus.title": "推奨項目", "panel.seoStatus.header.property": "項目", - "panel.seoStatus.header.length": "長さ", "panel.seoStatus.header.valid": "有効", "panel.seoStatus.seoFieldInfo.characters": "{0} 文字", "panel.seoStatus.seoFieldInfo.words": "{0} 語", diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index f89e96ff..d27383e5 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -448,9 +448,6 @@ "panel.actions.title": "Actions", - "panel.articleDetails.title": "More details", - "panel.articleDetails.type": "Type", - "panel.articleDetails.total": "Total", "panel.articleDetails.headings": "Headings", "panel.articleDetails.paragraphs": "Paragraphs", "panel.articleDetails.internalLinks": "Internal links", @@ -499,18 +496,17 @@ "panel.seoDetails.recommended": "Recommended", - "panel.seoKeywordInfo.density": "Keyword usage {0} *", + "panel.seoKeywords.density": "Keyword density", "panel.seoKeywordInfo.validInfo.label": "Used in heading(s)", "panel.seoKeywordInfo.validInfo.content": "Content", "panel.seoKeywords.title": "Keywords", "panel.seoKeywords.header.keyword": "Keyword", "panel.seoKeywords.header.details": "Details", - "panel.seoKeywords.density": "* A keyword density of 1-1.5% is sufficient in most cases.", + "panel.seoKeywords.density.description": "* A keyword density of 1-1.5% is sufficient in most cases.", - "panel.seoStatus.title": "Recommendations", + "panel.seoStatus.title": "Insights", "panel.seoStatus.header.property": "Property", - "panel.seoStatus.header.length": "Length", "panel.seoStatus.header.valid": "Valid", "panel.seoStatus.seoFieldInfo.characters": "{0} chars", "panel.seoStatus.seoFieldInfo.words": "{0} words", @@ -589,7 +585,7 @@ "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", diff --git a/package-lock.json b/package-lock.json index 1afddfa8..66dde5ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", + "react-tooltip": "^5.28.0", "recoil": "^0.7.7", "rehype-parse": "^9.0.1", "rehype-remark": "^10.0.0", @@ -14702,6 +14703,20 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "dev": true, + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index d0510841..173832cc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "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" @@ -70,7 +71,8 @@ "**/.frontmatter/config/*.json": "jsonc" } }, - "keybindings": [{ + "keybindings": [ + { "command": "frontMatter.dashboard", "key": "alt+d" }, @@ -94,19 +96,23 @@ } ], "viewsContainers": { - "activitybar": [{ - "id": "frontmatter-explorer", - "title": "FM", - "icon": "$(fm-logo)" - }] + "activitybar": [ + { + "id": "frontmatter-explorer", + "title": "FM", + "icon": "$(fm-logo)" + } + ] }, "views": { - "frontmatter-explorer": [{ - "id": "frontMatter.explorer", - "name": "Front Matter", - "icon": "$(fm-logo)", - "type": "webview" - }] + "frontmatter-explorer": [ + { + "id": "frontMatter.explorer", + "name": "Front Matter", + "icon": "$(fm-logo)", + "type": "webview" + } + ] }, "configuration": { "title": "%settings.configuration.title%", @@ -174,7 +180,8 @@ "frontMatter.content.defaultFileType": { "type": "string", "default": "md", - "oneOf": [{ + "oneOf": [ + { "enum": [ "md", "mdx" @@ -190,7 +197,8 @@ "frontMatter.content.defaultSorting": { "type": "string", "default": "", - "oneOf": [{ + "oneOf": [ + { "enum": [ "LastModifiedAsc", "LastModifiedDesc", @@ -550,7 +558,8 @@ "categories" ], "markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%", - "items": [{ + "items": [ + { "type": "string", "enum": [ "contentFolders", @@ -624,7 +633,8 @@ "command": { "$id": "#scriptCommand", "type": "string", - "anyOf": [{ + "anyOf": [ + { "enum": [ "node", "bash", @@ -820,7 +830,8 @@ "title", "file" ], - "anyOf": [{ + "anyOf": [ + { "required": [ "schema" ] @@ -888,7 +899,8 @@ "id", "path" ], - "anyOf": [{ + "anyOf": [ + { "required": [ "schema" ] @@ -1135,26 +1147,29 @@ } } }, - "default": [{ - "name": "default", - "fileTypes": null, - "fields": [{ - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Caption", - "name": "caption", - "type": "string" - }, - { - "title": "Alt text", - "name": "alt", - "type": "string" - } - ] - }], + "default": [ + { + "name": "default", + "fileTypes": null, + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Caption", + "name": "caption", + "type": "string" + }, + { + "title": "Alt text", + "name": "alt", + "type": "string" + } + ] + } + ], "scope": "Media" }, "frontMatter.media.supportedMimeTypes": { @@ -1257,7 +1272,8 @@ "fileType": { "type": "string", "default": "", - "oneOf": [{ + "oneOf": [ + { "enum": [ "md", "mdx" @@ -1403,7 +1419,8 @@ "default": "", "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%", "not": { - "anyOf": [{ + "anyOf": [ + { "const": "" }, { @@ -1609,7 +1626,8 @@ "type", "name" ], - "allOf": [{ + "allOf": [ + { "if": { "properties": { "type": { @@ -1821,48 +1839,51 @@ "fields" ] }, - "default": [{ - "name": "default", - "pageBundle": false, - "fields": [{ - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Description", - "name": "description", - "type": "string" - }, - { - "title": "Publishing date", - "name": "date", - "type": "datetime", - "default": "{{now}}", - "isPublishDate": true - }, - { - "title": "Content preview", - "name": "preview", - "type": "image" - }, - { - "title": "Is in draft", - "name": "draft", - "type": "boolean" - }, - { - "title": "Tags", - "name": "tags", - "type": "tags" - }, - { - "title": "Categories", - "name": "categories", - "type": "categories" - } - ] - }], + "default": [ + { + "name": "default", + "pageBundle": false, + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime", + "default": "{{now}}", + "isPublishDate": true + }, + { + "title": "Content preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "boolean" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + } + ] + } + ], "scope": "Taxonomy" }, "frontMatter.taxonomy.customTaxonomy": { @@ -1875,7 +1896,8 @@ "type": "string", "description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%", "not": { - "anyOf": [{ + "anyOf": [ + { "const": "" }, { @@ -2074,7 +2096,8 @@ } } }, - "commands": [{ + "commands": [ + { "command": "frontMatter.project.switch", "title": "%command.frontMatter.project.switch%", "category": "Front Matter", @@ -2415,16 +2438,21 @@ } } ], - "submenus": [{ - "id": "frontmatter.submenu", - "label": "Front Matter" - }], + "submenus": [ + { + "id": "frontmatter.submenu", + "label": "Front Matter" + } + ], "menus": { - "webview/context": [{ - "command": "workbench.action.webview.openDeveloperTools", - "when": "frontMatter:isDevelopment" - }], - "editor/title": [{ + "webview/context": [ + { + "command": "workbench.action.webview.openDeveloperTools", + "when": "frontMatter:isDevelopment" + } + ], + "editor/title": [ + { "command": "frontMatter.markup.heading", "group": "navigation@-133", "when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg && activeEditor == 'workbench.editors.files.textFileEditor'" @@ -2510,11 +2538,14 @@ "when": "resourceFilename == 'frontmatter.json'" } ], - "explorer/context": [{ - "submenu": "frontmatter.submenu", - "group": "frontmatter@1" - }], - "frontmatter.submenu": [{ + "explorer/context": [ + { + "submenu": "frontmatter.submenu", + "group": "frontmatter@1" + } + ], + "frontmatter.submenu": [ + { "command": "frontMatter.createFromTemplate", "when": "explorerResourceIsFolder", "group": "frontmatter@1" @@ -2530,7 +2561,8 @@ "group": "frontmatter@3" } ], - "commandPalette": [{ + "commandPalette": [ + { "command": "frontMatter.init", "when": "frontMatterCanInit" }, @@ -2707,7 +2739,8 @@ "when": "frontMatter:file:isValid == true" } ], - "view/title": [{ + "view/title": [ + { "command": "frontMatter.docs", "group": "navigation@-1", "when": "view == frontMatter.explorer" @@ -2744,13 +2777,16 @@ } ] }, - "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": [ @@ -2763,45 +2799,48 @@ "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" - ] - }, - { - "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" + "walkthroughs": [ + { + "id": "frontmatter.welcome", + "title": "Get started with Front Matter", + "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", + "steps": [ + { + "id": "frontmatter.welcome.init", + "title": "Get started", + "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", + "media": { + "markdown": "assets/walkthrough/get-started.md" + }, + "completionEvents": [ + "onContext:frontMatterInitialized" + ] }, - "completionEvents": [ - "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" + { + "id": "frontmatter.welcome.documentation", + "title": "Documentation", + "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", + "media": { + "markdown": "assets/walkthrough/documentation.md" + }, + "completionEvents": [ + "onLink:https://frontmatter.codes/docs" + ] }, - "completionEvents": [ - "onLink:https://github.com/sponsors/estruyf" - ] - } - ] - }] + { + "id": "frontmatter.welcome.supporter", + "title": "Support the project", + "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", + "media": { + "markdown": "assets/walkthrough/support-the-project.md" + }, + "completionEvents": [ + "onLink:https://github.com/sponsors/estruyf" + ] + } + ] + } + ] }, "scripts": { "dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*", @@ -2900,6 +2939,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", + "react-tooltip": "^5.28.0", "recoil": "^0.7.7", "rehype-parse": "^9.0.1", "rehype-remark": "^10.0.0", @@ -2938,4 +2978,4 @@ "vsce": { "dependencies": false } -} \ No newline at end of file +} diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 2af534f4..dc001c3a 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -1448,18 +1448,6 @@ export enum LocalizationKey { * Actions */ panelActionsTitle = 'panel.actions.title', - /** - * More details - */ - panelArticleDetailsTitle = 'panel.articleDetails.title', - /** - * Type - */ - panelArticleDetailsType = 'panel.articleDetails.type', - /** - * Total - */ - panelArticleDetailsTotal = 'panel.articleDetails.total', /** * Headings */ @@ -1613,9 +1601,9 @@ export enum LocalizationKey { */ panelSeoDetailsRecommended = 'panel.seoDetails.recommended', /** - * Keyword usage {0} * + * Keyword density */ - panelSeoKeywordInfoDensity = 'panel.seoKeywordInfo.density', + panelSeoKeywordsDensity = 'panel.seoKeywords.density', /** * Used in heading(s) */ @@ -1639,19 +1627,15 @@ export enum LocalizationKey { /** * * A keyword density of 1-1.5% is sufficient in most cases. */ - panelSeoKeywordsDensity = 'panel.seoKeywords.density', + panelSeoKeywordsDensityDescription = 'panel.seoKeywords.density.description', /** - * Recommendations + * Insights */ panelSeoStatusTitle = 'panel.seoStatus.title', /** * Property */ panelSeoStatusHeaderProperty = 'panel.seoStatus.header.property', - /** - * Length - */ - panelSeoStatusHeaderLength = 'panel.seoStatus.header.length', /** * Valid */ diff --git a/src/panelWebView/components/ArticleDetails.tsx b/src/panelWebView/components/ArticleDetails.tsx index 0fc8cbdb..747d100a 100644 --- a/src/panelWebView/components/ArticleDetails.tsx +++ b/src/panelWebView/components/ArticleDetails.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../localization'; -import { VSCodeTable, VSCodeTableBody, VSCodeTableCell, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; +import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface IArticleDetailsProps { details: { @@ -22,59 +22,42 @@ const ArticleDetails: React.FunctionComponent = ({ } return ( -
-

{l10n.t(LocalizationKey.panelArticleDetailsTitle)}

+ <> + {details?.headings !== undefined && ( + + {l10n.t(LocalizationKey.panelArticleDetailsHeadings)} + {details.headings} + + )} - - - - - {l10n.t(LocalizationKey.panelArticleDetailsType)} - - - {l10n.t(LocalizationKey.panelArticleDetailsTotal)} - - - + {details?.paragraphs !== undefined && ( + + {l10n.t(LocalizationKey.panelArticleDetailsParagraphs)} + {details.paragraphs} + + )} - - {details?.headings !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsHeadings)} - {details.headings} - - )} + {details?.internalLinks !== undefined && ( + + {l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)} + {details.internalLinks} + + )} - {details?.paragraphs !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsParagraphs)} - {details.paragraphs} - - )} + {details?.externalLinks !== undefined && ( + + {l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)} + {details.externalLinks} + + )} - {details?.internalLinks !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsInternalLinks)} - {details.internalLinks} - - )} - - {details?.externalLinks !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsExternalLinks)} - {details.externalLinks} - - )} - - {details?.images !== undefined && ( - - {l10n.t(LocalizationKey.panelArticleDetailsImages)} - {details.images} - - )} - - -
+ {details?.images !== undefined && ( + + {l10n.t(LocalizationKey.panelArticleDetailsImages)} + {details.images} + + )} + ); }; diff --git a/src/panelWebView/components/SeoFieldInfo.tsx b/src/panelWebView/components/SeoFieldInfo.tsx index 8b30bde3..6679e0a2 100644 --- a/src/panelWebView/components/SeoFieldInfo.tsx +++ b/src/panelWebView/components/SeoFieldInfo.tsx @@ -7,19 +7,20 @@ export interface ISeoFieldInfoProps { value: string; recommendation: string; isValid?: boolean; + className?: string; } const SeoFieldInfo: React.FunctionComponent = ({ title, value, recommendation, - isValid + isValid, + className }: React.PropsWithChildren) => { return ( - + {title} - {value}/{recommendation} - {isValid !== undefined ? : -} + {isValid !== undefined ? : } {value}/{recommendation} ); }; diff --git a/src/panelWebView/components/SeoKeywordInfo.tsx b/src/panelWebView/components/SeoKeywordInfo.tsx index 38418233..fdd2cfe7 100644 --- a/src/panelWebView/components/SeoKeywordInfo.tsx +++ b/src/panelWebView/components/SeoKeywordInfo.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; import { ValidInfo } from './ValidInfo'; -import * as l10n from '@vscode/l10n'; -import { LocalizationKey } from '../../localization'; import { VSCodeTableCell, VSCodeTableRow } from './VSCode/VSCodeTable'; export interface ISeoKeywordInfoProps { @@ -31,14 +29,14 @@ const SeoKeywordInfo: React.FunctionComponent = ({ const pattern = new RegExp(`(^${keyword.toLowerCase()}(?=\\s|$))|(\\s${keyword.toLowerCase()}(?=\\s|$))`, 'ig'); const count = (content.match(pattern) || []).length; const density = (count / wordCount) * 100; - const densityTitle = l10n.t(LocalizationKey.panelSeoKeywordInfoDensity, `${density.toFixed(2)}%`); + const densityTitle = `${density.toFixed(2)}% *`; if (density < 0.75) { - return ; + return ; } else if (density >= 0.75 && density < 1.5) { - return ; + return ; } else { - return ; + return ; } }; @@ -63,7 +61,7 @@ const SeoKeywordInfo: React.FunctionComponent = ({ } const exists = headings.filter((heading) => validateKeywords(heading, keyword)); - return 0} />; + return 0} />; }; if (!keyword || typeof keyword !== 'string') { @@ -73,39 +71,35 @@ const SeoKeywordInfo: React.FunctionComponent = ({ return ( {keyword} - -
- -
-
- -
-
- -
-
- -
- {headings && headings.length > 0 && -
{checkHeadings()}
} - {wordCount && -
{density()}
} + + + + + + + + + + + + + + {checkHeadings()} + + + {density()}
); diff --git a/src/panelWebView/components/SeoKeywords.tsx b/src/panelWebView/components/SeoKeywords.tsx index 094a73ed..226a9654 100644 --- a/src/panelWebView/components/SeoKeywords.tsx +++ b/src/panelWebView/components/SeoKeywords.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { SeoKeywordInfo } from './SeoKeywordInfo'; import { ErrorBoundary } from '@sentry/react'; -import * as l10n from '@vscode/l10n'; -import { LocalizationKey } from '../../localization'; +import { Tooltip } from 'react-tooltip' +import { LocalizationKey, localize } from '../../localization'; import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; +import { Icon } from 'vscrui'; export interface ISeoKeywordsProps { keywords: string[] | null; @@ -22,6 +23,8 @@ const SeoKeywords: React.FunctionComponent = ({ }: React.PropsWithChildren) => { const [isReady, setIsReady] = React.useState(false); + const tooltipClasses = `!py-[2px] !px-[8px] !rounded-[3px] !border-[var(--vscode-editorHoverWidget-border)] !border !border-solid !bg-[var(--vscode-editorHoverWidget-background)] !text-[var(--vscode-editorHoverWidget-foreground)] !font-normal !opacity-100`; + const validateKeywords = () => { if (!keywords) { return []; @@ -51,17 +54,106 @@ const SeoKeywords: React.FunctionComponent = ({ } return ( -
-

{l10n.t(LocalizationKey.panelSeoKeywordsTitle)}

+
+

{localize(LocalizationKey.panelSeoKeywordsTitle)}

- + - {l10n.t(LocalizationKey.panelSeoKeywordsHeaderKeyword)} + {localize(LocalizationKey.panelSeoKeywordsHeaderKeyword)} - - {l10n.t(LocalizationKey.panelSeoKeywordsHeaderDetails)} + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + H1 + + +
+
+ +
+ + +
@@ -79,10 +171,10 @@ const SeoKeywords: React.FunctionComponent = ({ {data.wordCount && (
- {l10n.t(LocalizationKey.panelSeoKeywordsDensity)} + {localize(LocalizationKey.panelSeoKeywordsDensityDescription)}
)} -
+ ); }; diff --git a/src/panelWebView/components/SeoStatus.tsx b/src/panelWebView/components/SeoStatus.tsx index f7bf280e..5f7c06e3 100644 --- a/src/panelWebView/components/SeoStatus.tsx +++ b/src/panelWebView/components/SeoStatus.tsx @@ -4,13 +4,13 @@ import { TagType } from '../TagType'; import { ArticleDetails } from './ArticleDetails'; import { Collapsible } from './Collapsible'; import FieldBoundary from './ErrorBoundary/FieldBoundary'; -import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon'; import { SeoFieldInfo } from './SeoFieldInfo'; import { SeoKeywords } from './SeoKeywords'; import { TagPicker } from './Fields/TagPicker'; import { LocalizationKey, localize } from '../../localization'; -import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable'; +import { VSCodeTable, VSCodeTableBody } from './VSCode/VSCodeTable'; import useContentType from '../../hooks/useContentType'; +import { Icon } from 'vscrui'; export interface ISeoStatusProps { seo: SEO; @@ -38,19 +38,11 @@ const SeoStatus: React.FunctionComponent = ({ const descriptionFieldName = contentType?.fields.find(f => f.name === descriptionField)?.title || descriptionField; return ( -
-
-

{localize(LocalizationKey.panelSeoStatusTitle)}

+
+
+

{localize(LocalizationKey.panelSeoStatusTitle)}

- - - {localize(LocalizationKey.panelSeoStatusHeaderProperty)} - {localize(LocalizationKey.panelSeoStatusHeaderLength)} - {localize(LocalizationKey.panelSeoStatusHeaderValid)} - - - {metadata[titleField] && seo.title > 0 ? ( = ({ value={metadata[titleField].length} recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.title)} isValid={metadata[titleField].length <= seo.title} + className={`border-t border-t-[var(--vscode-editorGroup-border)]`} /> ) : null} @@ -86,9 +79,11 @@ const SeoStatus: React.FunctionComponent = ({ recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoWords, seo.content)} /> ) : null} + + -
+ = ({ } + icon={} crntSelected={(metadata.keywords as string[]) || []} options={[]} freeform={true} @@ -112,8 +107,6 @@ const SeoStatus: React.FunctionComponent = ({ disableConfigurable /> - -
); }, [contentType, metadata, seo, focusElm, unsetFocus]); diff --git a/src/panelWebView/components/ValidInfo.tsx b/src/panelWebView/components/ValidInfo.tsx index 2e817775..1ab33f54 100644 --- a/src/panelWebView/components/ValidInfo.tsx +++ b/src/panelWebView/components/ValidInfo.tsx @@ -4,21 +4,23 @@ import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline' export interface IValidInfoProps { label?: string; isValid: boolean; + className?: string; } const ValidInfo: React.FunctionComponent = ({ label, - isValid + isValid, + className, }: React.PropsWithChildren) => { return ( - <> +
{isValid ? ( ) : ( )} - {label && {label}} - + {label && {label}} +
); };