mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Collapsible headers
This commit is contained in:
11
README.md
11
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`.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<IActionsProps> = (props: React.Pro
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`section article__actions`}>
|
||||
<h3><Icon name={`rocket`} /> Actions</h3>
|
||||
<Collapsible title="Actions">
|
||||
<div className={`article__actions`}>
|
||||
{ metadata && metadata.title && <SlugAction value={metadata.title} crntValue={metadata.slug} slugOpts={settings.slug} /> }
|
||||
|
||||
{ metadata && metadata.title && <SlugAction value={metadata.title} crntValue={metadata.slug} slugOpts={settings.slug} /> }
|
||||
|
||||
<DateAction />
|
||||
<DateAction />
|
||||
|
||||
{ metadata && typeof metadata.draft !== undefined && <PublishAction draft={metadata.draft} />}
|
||||
{ metadata && typeof metadata.draft !== undefined && <PublishAction draft={metadata.draft} />}
|
||||
|
||||
{
|
||||
(settings && settings.scripts && settings.scripts.length > 0) && (
|
||||
settings.scripts.map((value) => (
|
||||
<CustomScript key={value.title.replace(/ /g, '')} {...value} />
|
||||
))
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
(settings && settings.scripts && settings.scripts.length > 0) && (
|
||||
settings.scripts.map((value) => (
|
||||
<CustomScript key={value.title.replace(/ /g, '')} {...value} />
|
||||
))
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -10,9 +10,7 @@ export interface IArticleDetailsProps {
|
||||
}
|
||||
|
||||
export const ArticleDetails: React.FunctionComponent<IArticleDetailsProps> = ({details}: React.PropsWithChildren<IArticleDetailsProps>) => {
|
||||
|
||||
console.log(details);
|
||||
|
||||
|
||||
if (!details || (details.headings === undefined && details.paragraphs === undefined)) {
|
||||
return null;
|
||||
}
|
||||
@@ -21,7 +19,7 @@ export const ArticleDetails: React.FunctionComponent<IArticleDetailsProps> = ({d
|
||||
<div className={`seo__status__details valid`}>
|
||||
<h4>More details</h4>
|
||||
|
||||
<VsTable>
|
||||
<VsTable bordered>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell>Type</VsTableHeaderCell>
|
||||
<VsTableHeaderCell>Total</VsTableHeaderCell>
|
||||
|
||||
31
src/viewpanel/components/Collapsible.tsx
Normal file
31
src/viewpanel/components/Collapsible.tsx
Normal file
@@ -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<ICollapsibleProps> = ({children, title, sendUpdate}: React.PropsWithChildren<ICollapsibleProps>) => {
|
||||
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<HTMLElement>) => {
|
||||
if ((e.target as any).tagName === 'VSCODE-COLLAPSIBLE') {
|
||||
setIsOpen(prev => {
|
||||
if (sendUpdate) {
|
||||
sendUpdate(!prev);
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<VsCollapsible title={title} onClick={triggerClick} open={isOpen}>
|
||||
<div className={`section collapsible__body`} slot="body">
|
||||
{children}
|
||||
</div>
|
||||
</VsCollapsible>
|
||||
);
|
||||
};
|
||||
@@ -24,7 +24,7 @@ export const SeoDetails: React.FunctionComponent<ISeoDetailsProps> = (props: Rea
|
||||
<div className={`seo__status__details ${validate()}`}>
|
||||
<h4>{title}</h4>
|
||||
|
||||
<VsTable>
|
||||
<VsTable {...{bordered:true}}>
|
||||
<VsTableHeader slot="header">
|
||||
<VsTableHeaderCell className={validate()}>{valueTitle}</VsTableHeaderCell>
|
||||
<VsTableHeaderCell>Recommended</VsTableHeaderCell>
|
||||
|
||||
15
src/viewpanel/components/SeoKeywords.tsx
Normal file
15
src/viewpanel/components/SeoKeywords.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ISeoKeywordsProps {
|
||||
keywords: string[] | null;
|
||||
}
|
||||
|
||||
export const SeoKeywords: React.FunctionComponent<ISeoKeywordsProps> = ({keywords}: React.PropsWithChildren<ISeoKeywordsProps>) => {
|
||||
return (
|
||||
<div className={`seo__status__keywords`}>
|
||||
<h4>Keywords</h4>
|
||||
|
||||
<p>{keywords?.join(',')}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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<ISeoStatusProps> = (props: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
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<ISeoStatusProps> = (props: React
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
console.log(`render`);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ (title && seo.title > 0) && <SeoDetails title="Title" valueTitle="Length" allowedLength={seo.title} value={title.length} /> }
|
||||
|
||||
{ (data[descriptionField] && seo.description > 0) && <SeoDetails title="Description" valueTitle="Length" allowedLength={seo.description} value={data[descriptionField].length} /> }
|
||||
|
||||
{
|
||||
seo.content > 0 && data?.articleDetails?.wordCount && (
|
||||
<SeoDetails title="Article length"
|
||||
valueTitle="Words"
|
||||
allowedLength={seo.content}
|
||||
value={data?.articleDetails?.wordCount}
|
||||
noValidation />
|
||||
)
|
||||
}
|
||||
|
||||
<SeoKeywords keywords={data?.keywords} />
|
||||
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section seo__status">
|
||||
<h3>
|
||||
<Icon name="search" /> SEO Status
|
||||
</h3>
|
||||
|
||||
{ (title && seo.title > 0) && <SeoDetails title="Title" valueTitle="Length" allowedLength={seo.title} value={title.length} /> }
|
||||
|
||||
{ (data[descriptionField] && seo.description > 0) && <SeoDetails title="Description" valueTitle="Length" allowedLength={seo.description} value={data[descriptionField].length} /> }
|
||||
|
||||
{
|
||||
seo.content > 0 && data?.articleDetails?.wordCount && (
|
||||
<SeoDetails title="Article length"
|
||||
valueTitle="Words"
|
||||
allowedLength={seo.content}
|
||||
value={data?.articleDetails?.wordCount}
|
||||
noValidation />
|
||||
)
|
||||
}
|
||||
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
</div>
|
||||
<Collapsible title="SEO Status" sendUpdate={(value) => setIsOpen(value)}>
|
||||
{ renderContent() }
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -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`);
|
||||
export const VsTableCell = wrapWc(`vscode-table-cell`);
|
||||
export const VsCollapsible = wrapWc(`vscode-collapsible`);
|
||||
@@ -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: <T = unknown>() => {
|
||||
getState: () => T;
|
||||
|
||||
Reference in New Issue
Block a user