From bea11bf7df854ff8eaf0f65b013375be1e70f7de Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 21 Jul 2021 14:42:52 +0200 Subject: [PATCH] Collapsible headers --- README.md | 11 ++++ assets/media/styles.css | 13 +++-- assets/media/vscode.css | 5 +- package.json | 2 +- src/viewpanel/components/Actions.tsx | 30 +++++----- src/viewpanel/components/ArticleDetails.tsx | 6 +- src/viewpanel/components/Collapsible.tsx | 31 +++++++++++ src/viewpanel/components/SeoDetails.tsx | 2 +- src/viewpanel/components/SeoKeywords.tsx | 15 +++++ src/viewpanel/components/SeoStatus.tsx | 58 ++++++++++++-------- src/viewpanel/components/VscodeComponents.ts | 3 +- src/viewpanel/index.tsx | 1 + 12 files changed, 126 insertions(+), 51 deletions(-) create mode 100644 src/viewpanel/components/Collapsible.tsx create mode 100644 src/viewpanel/components/SeoKeywords.tsx diff --git a/README.md b/README.md index dc3a9a57..6a6d02bb 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,17 @@ Specifies the optimal description length for SEO (set to `-1` to turn it off). D "frontMatter.taxonomy.seoDescriptionLength": 160 } ``` + +### `frontMatter.taxonomy.seoContentLength` + +Specifies the optimal minimum length for your articles. Between 1,760 words – 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off). + +```json +{ + "frontMatter.taxonomy.seoContentLength": 1760 +} +``` + ### `frontMatter.taxonomy.seoDescriptionLength` Specifies the name of the SEO description field for your page. Default is `description`. diff --git a/assets/media/styles.css b/assets/media/styles.css index e8fb6645..16e119d1 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -21,6 +21,10 @@ } } +.collapsible__body { + padding: 1rem 2rem; +} + #app, .frontmatter { height: 100%; } @@ -63,7 +67,8 @@ } .frontmatter { - padding: var(--input-margin-vertical) 0; + padding-top: 0; + padding-bottom: var(--input-margin-vertical); display: flex; flex-direction: column; justify-content: space-between; @@ -89,11 +94,11 @@ margin-right: 0.5rem; } -.seo__status__details { - margin-bottom: 2rem; +.seo__status__details, .seo__status__keywords { + margin-bottom: 1rem; } -.seo__status__details h4 { +.collapsible__body h4 { text-align: center; font-weight: bold; } diff --git a/assets/media/vscode.css b/assets/media/vscode.css index 8c491f11..cf65988c 100644 --- a/assets/media/vscode.css +++ b/assets/media/vscode.css @@ -1,5 +1,5 @@ :root { - --container-paddding: 20px; + --container-padding: 20px; --input-padding-vertical: 6px; --input-padding-horizontal: 4px; --input-margin-vertical: 4px; @@ -11,7 +11,6 @@ html, body { } body { - padding: 0 var(--container-paddding); color: var(--vscode-foreground); font-size: var(--vscode-font-size); font-weight: var(--vscode-font-weight); @@ -21,7 +20,7 @@ body { ol, ul { - padding-left: var(--container-paddding); + padding-left: var(--container-padding); } *:focus { diff --git a/package.json b/package.json index 5ab497bc..fa8c8b5c 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "frontMatter.taxonomy.seoContentLengh": { "type": "number", "default": 1760, - "description": "Specifies the optimal minimum length for your articles. Between 1,760 words – 2,400 is the absolute ideal blog length for SEO in 2021. (set to `-1` to turn it off)." + "description": "Specifies the optimal minimum length for your articles. Between 1,760 words – 2,400 is the absolute ideal article length for SEO in 2021. (set to `-1` to turn it off)." }, "frontMatter.taxonomy.seoDescriptionField": { "type": "string", diff --git a/src/viewpanel/components/Actions.tsx b/src/viewpanel/components/Actions.tsx index 630d4894..22d2ae2f 100644 --- a/src/viewpanel/components/Actions.tsx +++ b/src/viewpanel/components/Actions.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { PanelSettings } from '../../models/PanelSettings'; +import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; import { DateAction } from './DateAction'; -import { Icon } from './Icon'; import { PublishAction } from './PublishAction'; import { SlugAction } from './SlugAction'; @@ -19,22 +19,22 @@ export const Actions: React.FunctionComponent = (props: React.Pro } return ( -
-

Actions

+ +
+ { metadata && metadata.title && } - { metadata && metadata.title && } - - + - { metadata && typeof metadata.draft !== undefined && } + { metadata && typeof metadata.draft !== undefined && } - { - (settings && settings.scripts && settings.scripts.length > 0) && ( - settings.scripts.map((value) => ( - - )) - ) - } -
+ { + (settings && settings.scripts && settings.scripts.length > 0) && ( + settings.scripts.map((value) => ( + + )) + ) + } +
+ ); }; \ No newline at end of file diff --git a/src/viewpanel/components/ArticleDetails.tsx b/src/viewpanel/components/ArticleDetails.tsx index 9f8c0a67..a0ba922f 100644 --- a/src/viewpanel/components/ArticleDetails.tsx +++ b/src/viewpanel/components/ArticleDetails.tsx @@ -10,9 +10,7 @@ export interface IArticleDetailsProps { } export const ArticleDetails: React.FunctionComponent = ({details}: React.PropsWithChildren) => { - - console.log(details); - + if (!details || (details.headings === undefined && details.paragraphs === undefined)) { return null; } @@ -21,7 +19,7 @@ export const ArticleDetails: React.FunctionComponent = ({d

More details

- + Type Total diff --git a/src/viewpanel/components/Collapsible.tsx b/src/viewpanel/components/Collapsible.tsx new file mode 100644 index 00000000..3d3baf1d --- /dev/null +++ b/src/viewpanel/components/Collapsible.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { VsCollapsible } from './VscodeComponents'; + +export interface ICollapsibleProps { + title: string; + sendUpdate?: (open: boolean) => void; +} + +export const Collapsible: React.FunctionComponent = ({children, title, sendUpdate}: React.PropsWithChildren) => { + const [ isOpen, setIsOpen ] = React.useState(true); + + // This is a work around for a lit-element issue of duplicate slot names + const triggerClick = (e: React.MouseEvent) => { + if ((e.target as any).tagName === 'VSCODE-COLLAPSIBLE') { + setIsOpen(prev => { + if (sendUpdate) { + sendUpdate(!prev); + } + return !prev; + }); + } + } + + return ( + +
+ {children} +
+
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoDetails.tsx b/src/viewpanel/components/SeoDetails.tsx index 6d188eb3..d4c25c91 100644 --- a/src/viewpanel/components/SeoDetails.tsx +++ b/src/viewpanel/components/SeoDetails.tsx @@ -24,7 +24,7 @@ export const SeoDetails: React.FunctionComponent = (props: Rea

{title}

- + {valueTitle} Recommended diff --git a/src/viewpanel/components/SeoKeywords.tsx b/src/viewpanel/components/SeoKeywords.tsx new file mode 100644 index 00000000..a98b5f26 --- /dev/null +++ b/src/viewpanel/components/SeoKeywords.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface ISeoKeywordsProps { + keywords: string[] | null; +} + +export const SeoKeywords: React.FunctionComponent = ({keywords}: React.PropsWithChildren) => { + return ( +
+

Keywords

+ +

{keywords?.join(',')}

+
+ ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/SeoStatus.tsx b/src/viewpanel/components/SeoStatus.tsx index 7c587b5e..33715886 100644 --- a/src/viewpanel/components/SeoStatus.tsx +++ b/src/viewpanel/components/SeoStatus.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import { SEO } from '../../models/PanelSettings'; import { ArticleDetails } from './ArticleDetails'; -import { Icon } from './Icon'; +import { Collapsible } from './Collapsible'; import { SeoDetails } from './SeoDetails'; +import { SeoKeywords } from './SeoKeywords'; export interface ISeoStatusProps { seo: SEO; @@ -12,6 +13,7 @@ export interface ISeoStatusProps { export const SeoStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { const { data, seo } = props; const { title } = data; + const [ isOpen, setIsOpen ] = React.useState(true); const { descriptionField } = seo; @@ -19,27 +21,39 @@ export const SeoStatus: React.FunctionComponent = (props: React return null; } + const renderContent = () => { + console.log(`render`); + + if (!isOpen) { + return null; + } + + return ( +
+ { (title && seo.title > 0) && } + + { (data[descriptionField] && seo.description > 0) && } + + { + seo.content > 0 && data?.articleDetails?.wordCount && ( + + ) + } + + + + +
+ ); + }; + return ( -
-

- SEO Status -

- - { (title && seo.title > 0) && } - - { (data[descriptionField] && seo.description > 0) && } - - { - seo.content > 0 && data?.articleDetails?.wordCount && ( - - ) - } - - -
+ setIsOpen(value)}> + { renderContent() } + ); }; \ No newline at end of file diff --git a/src/viewpanel/components/VscodeComponents.ts b/src/viewpanel/components/VscodeComponents.ts index 65df271e..d4cb77f2 100644 --- a/src/viewpanel/components/VscodeComponents.ts +++ b/src/viewpanel/components/VscodeComponents.ts @@ -5,4 +5,5 @@ export const VsTableHeader = wrapWc(`vscode-table-header`); export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`); export const VsTableBody = wrapWc(`vscode-table-body`); export const VsTableRow = wrapWc(`vscode-table-row`); -export const VsTableCell = wrapWc(`vscode-table-cell`); \ No newline at end of file +export const VsTableCell = wrapWc(`vscode-table-cell`); +export const VsCollapsible = wrapWc(`vscode-collapsible`); \ No newline at end of file diff --git a/src/viewpanel/index.tsx b/src/viewpanel/index.tsx index 336825bd..28bf3f1a 100644 --- a/src/viewpanel/index.tsx +++ b/src/viewpanel/index.tsx @@ -8,6 +8,7 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-header-cell'; import '@bendera/vscode-webview-elements/dist/vscode-table-body'; import '@bendera/vscode-webview-elements/dist/vscode-table-row'; import '@bendera/vscode-webview-elements/dist/vscode-table-cell'; +import '@bendera/vscode-webview-elements/dist/vscode-collapsible'; declare const acquireVsCodeApi: () => { getState: () => T;