From f500749644b4129552b8be7eb5fb880ba520897e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 20 Oct 2021 15:21:27 +0200 Subject: [PATCH 01/11] Fix slug punctuation --- CHANGELOG.md | 6 ++++++ src/helpers/ArticleHelper.ts | 1 - src/helpers/SlugHelper.ts | 24 ++++++++++++++++-------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d00319d..9498cd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [5.x.x] - 2021-xx-xx + +### 🐞 Fixes + +- Value check when generating slug from title + ## [5.2.0] - 2021-10-19 ### 🎨 Enhancements diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index ffd9a6b1..8aceac5d 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -251,7 +251,6 @@ export class ArticleHelper { const items = [{ title: "Check file", action: async () => { - console.log(fileName); await EditorHelper.showFile(fileName) } }]; diff --git a/src/helpers/SlugHelper.ts b/src/helpers/SlugHelper.ts index 2dedc629..70ffc0a7 100644 --- a/src/helpers/SlugHelper.ts +++ b/src/helpers/SlugHelper.ts @@ -14,14 +14,18 @@ export class SlugHelper { // Remove punctuation from input string, and split it into words. let cleanTitle = this.removePunctuation(articleTitle); - cleanTitle = cleanTitle.toLowerCase(); - // Split into words - let words = cleanTitle.split(/\s/); - // Removing stop words - words = this.removeStopWords(words); - cleanTitle = words.join("-"); - cleanTitle = this.replaceCharacters(cleanTitle); - return cleanTitle; + if (cleanTitle) { + cleanTitle = cleanTitle.toLowerCase(); + // Split into words + let words = cleanTitle.split(/\s/); + // Removing stop words + words = this.removeStopWords(words); + cleanTitle = words.join("-"); + cleanTitle = this.replaceCharacters(cleanTitle); + return cleanTitle; + } + + return null; } /** @@ -30,6 +34,10 @@ export class SlugHelper { * @param value */ private static removePunctuation(value: string): string { + if (typeof value !== "string") { + return ""; + } + const punctuationless = value?.replace(/[\.,-\/#!$@%\^&\*;:{}=\-_`'"~()+\?<>]/g, " "); // Remove double spaces return punctuationless?.replace(/\s{2,}/g," "); From a7aab96f0e04b3b8274d99762ff7a5b415c94013 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 25 Oct 2021 10:11:31 +0200 Subject: [PATCH 02/11] Fix time formatting --- src/commands/Article.ts | 4 ++-- src/commands/Preview.ts | 2 +- src/helpers/ArticleHelper.ts | 2 +- src/helpers/DateHelper.ts | 6 +++++- src/panelWebView/components/Fields/DateTimeField.tsx | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 93fcb779..5fe71708 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -172,7 +172,7 @@ export class Article { let newFileName = `${slugName}${ext}`; if (filePrefix && typeof filePrefix === "string") { - newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix))}-${newFileName}`; + newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix) as string)}-${newFileName}`; } const newPath = editor.document.uri.fsPath.replace(fileName, newFileName); @@ -244,7 +244,7 @@ export class Article { const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; if (dateFormat && typeof dateFormat === "string") { - return format(dateValue, DateHelper.formatUpdate(dateFormat)); + return format(dateValue, DateHelper.formatUpdate(dateFormat) as string); } else { return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString(); } diff --git a/src/commands/Preview.ts b/src/commands/Preview.ts index 90dd53bc..b38a1764 100644 --- a/src/commands/Preview.ts +++ b/src/commands/Preview.ts @@ -35,7 +35,7 @@ export class Preview { if (settings.pathname) { const articleDate = ArticleHelper.getDate(article); try { - slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(settings.pathname)), slug); + slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(settings.pathname) as string), slug); } catch (error) { slug = join(settings.pathname, slug); } diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 8aceac5d..c19ec986 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -204,7 +204,7 @@ export class ArticleHelper { let newFileName = `${sanitizedName}.md`; if (prefix && typeof prefix === "string") { - newFileName = `${format(new Date(), DateHelper.formatUpdate(prefix))}-${newFileName}`; + newFileName = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}-${newFileName}`; } newFilePath = join(folderPath, newFileName); diff --git a/src/helpers/DateHelper.ts b/src/helpers/DateHelper.ts index f86dc862..b9ae7221 100644 --- a/src/helpers/DateHelper.ts +++ b/src/helpers/DateHelper.ts @@ -3,7 +3,11 @@ import { parse, parseISO, parseJSON } from "date-fns"; export class DateHelper { - public static formatUpdate(value: string) { + public static formatUpdate(value: string | null | undefined): string | null { + if (!value) { + return null; + } + value = value.replace(/YYYY/g, 'yyyy'); value = value.replace(/DD/g, 'dd'); return value; diff --git a/src/panelWebView/components/Fields/DateTimeField.tsx b/src/panelWebView/components/Fields/DateTimeField.tsx index 7d63cf2a..532adfa7 100644 --- a/src/panelWebView/components/Fields/DateTimeField.tsx +++ b/src/panelWebView/components/Fields/DateTimeField.tsx @@ -52,7 +52,7 @@ export const DateTimeField: React.FunctionComponent = ({lab selected={dateValue as Date || new Date()} onChange={onDateChange} timeInputLabel="Time:" - dateFormat={format || "MM/dd/yyyy HH:mm"} + dateFormat={DateHelper.formatUpdate(format) || "MM/dd/yyyy HH:mm"} customInput={()} showTimeInput /> From 7291e6aac6146ebbbd5a29526427cc08abe97b26 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 25 Oct 2021 10:24:08 +0200 Subject: [PATCH 03/11] Fix tag replacement --- CHANGELOG.md | 4 +++- src/panelWebView/components/Tags.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9498cd7a..344bcc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # Change Log -## [5.x.x] - 2021-xx-xx +## [5.2.1] - 2021-10-25 ### 🐞 Fixes - Value check when generating slug from title +- Fix for date time formatting with `DD` and `YYYY` tokens +- Fix in tag space replacing when object is passed ## [5.2.0] - 2021-10-19 diff --git a/src/panelWebView/components/Tags.tsx b/src/panelWebView/components/Tags.tsx index a7f2cead..c699997f 100644 --- a/src/panelWebView/components/Tags.tsx +++ b/src/panelWebView/components/Tags.tsx @@ -18,7 +18,7 @@ const Tags: React.FunctionComponent = (props: React.PropsWithChildre const unknownTags = values.filter(v => !options.includes(v)); const generateKey = (tag: string, idx: number) => { - if (tag) { + if (tag && typeof tag === 'string') { return `${tag.replace(/ /g, "_")}-${idx}`; } return `tag-${idx}`; From 04c401207f8c038831ffdb3afff9a2d01cacf46e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 25 Oct 2021 12:53:04 +0200 Subject: [PATCH 04/11] #158 - New draft field setting + choice implementation --- package.json | 42 ++++++++++++++- src/commands/Dashboard.ts | 8 +-- src/commands/StatusListener.ts | 7 +++ src/constants/ContentType.ts | 2 +- src/constants/settings.ts | 1 + .../components/Contents/Item.tsx | 4 +- .../components/Navigation.tsx | 52 ++++++++++++++----- src/dashboardWebView/components/Status.tsx | 14 ++++- src/dashboardWebView/hooks/usePages.tsx | 27 +++++++--- src/dashboardWebView/models/Settings.ts | 3 +- src/dashboardWebView/state/atom/TabAtom.ts | 2 +- src/explorerView/ExplorerView.ts | 7 +-- src/helpers/ContentType.ts | 40 ++++++++++++-- src/models/DraftField.ts | 5 ++ src/models/PanelSettings.ts | 4 +- src/models/index.ts | 1 + .../components/Fields/DraftField.tsx | 40 ++++++++++++++ src/panelWebView/components/Metadata.tsx | 15 ++++++ 18 files changed, 235 insertions(+), 39 deletions(-) create mode 100644 src/models/DraftField.ts create mode 100644 src/panelWebView/components/Fields/DraftField.tsx diff --git a/package.json b/package.json index 45959f04..8ae47c11 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,43 @@ "markdownDescription": "Specify if you want to automatically update the modified date of your article/page. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.autoupdatedate)", "scope": "Content" }, + "frontMatter.content.draftField": { + "type": "object", + "markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.draftField)", + "default": { + "name": "draft", + "type": "boolean" + }, + "properties": { + "type": { + "type": "string", + "enum": [ + "boolean", + "choice" + ], + "description": "Define the type of field" + }, + "name": { + "type": "string", + "description": "Name of the field to use" + }, + "choices": { + "type": "array", + "description": "List of choices for the field", + "items": { + "type": [ + "string" + ] + } + } + }, + "additionalProperties": false, + "required": [ + "type", + "name" + ], + "scope": "Content" + }, "frontMatter.content.fmHighlight": { "type": "boolean", "default": true, @@ -273,7 +310,8 @@ "image", "choice", "tags", - "categories" + "categories", + "draft" ], "description": "Define the type of field" }, @@ -733,4 +771,4 @@ "dependencies": { "@docsearch/js": "^3.0.0-alpha.40" } -} +} \ No newline at end of file diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index c6ac8102..e90f0b2e 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -1,10 +1,10 @@ -import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID } from '../constants'; +import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD } from '../constants'; import { ArticleHelper } from './../helpers/ArticleHelper'; import { basename, dirname, extname, join, parse } from "path"; import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs"; import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode"; import { Settings as SettingsHelper } from '../helpers'; -import { Framework, TaxonomyType } from '../models'; +import { DraftField, Framework, TaxonomyType } from '../models'; import { Folders } from './Folders'; import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { DashboardMessage } from '../dashboardWebView/DashboardMessage'; @@ -25,6 +25,7 @@ import imageSize from 'image-size'; import { parseWinPath } from '../helpers/parseWinPath'; import { DateHelper } from '../helpers/DateHelper'; import { FrameworkDetector } from '../helpers/FrameworkDetector'; +import { ContentType } from '../helpers/ContentType'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -278,6 +279,7 @@ export class Dashboard { pageViewType: await ext.getState(ExtensionState.PagesView), mediaSnippet: SettingsHelper.get(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [], contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], + draftField: SettingsHelper.get(SETTINGS_CONTENT_DRAFT_FIELD), contentFolders: Folders.get().map(f => f.path), crntFramework: SettingsHelper.get(SETTINGS_FRAMEWORK_ID), framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null, @@ -479,7 +481,7 @@ export class Dashboard { fmModified: file.mtime, fmFilePath: file.filePath, fmFileName: file.fileName, - fmDraft: article?.data.draft ? "Draft" : "Published", + fmDraft: ContentType.getDraftStatus(article?.data), fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null, // Make sure these are always set title: article?.data.title, diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index ac5b887c..7b11e678 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import { ArticleHelper, SeoHelper, Settings } from '../helpers'; import { ExplorerView } from '../explorerView/ExplorerView'; import { DefaultFields } from '../constants'; +import { ContentType } from '../helpers/ContentType'; export class StatusListener { @@ -15,6 +16,12 @@ export class StatusListener { public static async verify(frontMatterSB: vscode.StatusBarItem, collection: vscode.DiagnosticCollection) { const draftMsg = "in draft"; const publishMsg = "to publish"; + + const draft = ContentType.getDraftField(); + if (!draft || draft.type !== "boolean") { + frontMatterSB.hide(); + return; + } let editor = vscode.window.activeTextEditor; if (editor && ArticleHelper.isMarkdownFile()) { diff --git a/src/constants/ContentType.ts b/src/constants/ContentType.ts index 181993f2..5f243b15 100644 --- a/src/constants/ContentType.ts +++ b/src/constants/ContentType.ts @@ -29,7 +29,7 @@ export const DEFAULT_CONTENT_TYPE: ContentType = { { "title": "Is in draft", "name": "draft", - "type": "boolean" + "type": "draft" }, { "title": "Tags", diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 4643dbc0..55a4dd12 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -38,6 +38,7 @@ export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate"; export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders"; export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder"; export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight"; +export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField"; export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index 31488d7c..08479f58 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -41,7 +41,7 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti
- +
@@ -64,7 +64,7 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti
- +
diff --git a/src/dashboardWebView/components/Navigation.tsx b/src/dashboardWebView/components/Navigation.tsx index 1fcd927d..3f04e806 100644 --- a/src/dashboardWebView/components/Navigation.tsx +++ b/src/dashboardWebView/components/Navigation.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { Tab } from '../constants/Tab'; -import { TabAtom } from '../state'; +import { SettingsAtom, TabAtom } from '../state'; export interface INavigationProps { totalPages: number; @@ -15,19 +15,47 @@ export const tabs = [ export const Navigation: React.FunctionComponent = ({totalPages}: React.PropsWithChildren) => { const [ crntTab, setCrntTab ] = useRecoilState(TabAtom); + const settings = useRecoilValue(SettingsAtom); return ( ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Status.tsx b/src/dashboardWebView/components/Status.tsx index fea36c35..cc7b6677 100644 --- a/src/dashboardWebView/components/Status.tsx +++ b/src/dashboardWebView/components/Status.tsx @@ -1,10 +1,22 @@ import * as React from 'react'; +import { useRecoilValue } from 'recoil'; +import { SettingsAtom } from '../state'; export interface IStatusProps { - draft: boolean; + draft: boolean | string; } export const Status: React.FunctionComponent = ({draft}: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsAtom); + + if (settings?.draftField && settings.draftField.type === "choice") { + if (draft) { + return {draft}; + } else { + return null; + } + } + return ( {draft ? "Draft" : "Published"} ); diff --git a/src/dashboardWebView/hooks/usePages.tsx b/src/dashboardWebView/hooks/usePages.tsx index 1c13dbe8..59af0a75 100644 --- a/src/dashboardWebView/hooks/usePages.tsx +++ b/src/dashboardWebView/hooks/usePages.tsx @@ -4,7 +4,7 @@ import { Tab } from '../constants/Tab'; import { Page } from '../models/Page'; import Fuse from 'fuse.js'; import { useRecoilValue } from 'recoil'; -import { CategorySelector, FolderSelector, SearchSelector, SortingSelector, TabSelector, TagSelector } from '../state'; +import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingSelector, TabSelector, TagSelector } from '../state'; const fuseOptions: Fuse.IFuseOptions = { keys: [ @@ -16,6 +16,7 @@ const fuseOptions: Fuse.IFuseOptions = { export default function usePages(pages: Page[]) { const [ pageItems, setPageItems ] = useState([]); + const settings = useRecoilValue(SettingsSelector); const tab = useRecoilValue(TabSelector); const sorting = useRecoilValue(SortingSelector); const folder = useRecoilValue(FolderSelector); @@ -24,6 +25,8 @@ export default function usePages(pages: Page[]) { const category = useRecoilValue(CategorySelector); useEffect(() => { + const draftField = settings?.draftField; + // Check if search needs to be performed let searchedPages = pages; if (search) { @@ -34,12 +37,22 @@ export default function usePages(pages: Page[]) { // Filter the pages let pagesToShow: Page[] = Object.assign([], searchedPages); - if (tab === Tab.Published) { - pagesToShow = searchedPages.filter(page => !page.draft); - } else if (tab === Tab.Draft) { - pagesToShow = searchedPages.filter(page => !!page.draft); + + if (draftField && draftField.type === 'choice') { + if (tab !== Tab.All) { + pagesToShow = pagesToShow.filter(page => page.fmDraft === tab); + } else { + pagesToShow = searchedPages; + } } else { - pagesToShow = searchedPages; + const draftFieldName = draftField?.name || "draft"; + if (tab === Tab.Published) { + pagesToShow = searchedPages.filter(page => !page[draftFieldName]); + } else if (tab === Tab.Draft) { + pagesToShow = searchedPages.filter(page => !!page[draftFieldName]); + } else { + pagesToShow = searchedPages; + } } // Sort the pages @@ -69,7 +82,7 @@ export default function usePages(pages: Page[]) { } setPageItems(pagesSorted); - }, [ pages, tab, sorting, folder, search, tag, category ]); + }, [ settings?.draftField, pages, tab, sorting, folder, search, tag, category ]); return { pageItems diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index f6eae415..36b8d5c2 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '../../models/VersionInfo'; import { ViewType } from '../state'; import { ContentFolder } from '../../models/ContentFolder'; -import { ContentType, Framework } from '../../models'; +import { ContentType, DraftField, Framework } from '../../models'; export interface Settings { beta: boolean; @@ -19,4 +19,5 @@ export interface Settings { contentFolders: string[]; crntFramework: string; framework: Framework | null | undefined; + draftField: DraftField | null | undefined; } \ No newline at end of file diff --git a/src/dashboardWebView/state/atom/TabAtom.ts b/src/dashboardWebView/state/atom/TabAtom.ts index abb17261..49d95e88 100644 --- a/src/dashboardWebView/state/atom/TabAtom.ts +++ b/src/dashboardWebView/state/atom/TabAtom.ts @@ -1,7 +1,7 @@ import { atom } from 'recoil'; import { Tab } from '../../constants/Tab'; -export const TabAtom = atom({ +export const TabAtom = atom({ key: 'TabAtom', default: Tab.All }); \ No newline at end of file diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 2a3f8a2c..07033d73 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,6 +1,6 @@ import { DashboardData } from '../models/DashboardData'; import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD } from '../constants'; import * as os from 'os'; import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; @@ -9,7 +9,7 @@ import { Command } from "../panelWebView/Command"; import { CommandToCode } from '../panelWebView/CommandToCode'; import { Article } from '../commands'; import { TagType } from '../panelWebView/TagType'; -import { TaxonomyType } from '../models'; +import { DraftField, TaxonomyType } from '../models'; import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { Content } from 'mdast'; @@ -403,7 +403,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { preview: Preview.getSettings(), commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [], contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], - dashboardViewData: Dashboard.viewData + dashboardViewData: Dashboard.viewData, + draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD) } as PanelSettings }); } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index b7500fe5..9118042a 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -1,18 +1,48 @@ import { ArticleHelper, Settings } from "."; -import { SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from "../constants"; -import { ContentType as IContentType } from '../models'; +import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { ContentType as IContentType, DraftField } from '../models'; import { Uri, workspace, window } from 'vscode'; import { Folders } from "../commands/Folders"; import { Questions } from "./Questions"; -import { format } from "date-fns"; -import { join } from "path"; -import { existsSync, mkdirSync, writeFileSync } from "fs"; +import { writeFileSync } from "fs"; import { Notifications } from "./Notifications"; import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType"; +import { GrayMatterFile } from "gray-matter"; export class ContentType { + /** + * Retrieve the draft field + * @returns + */ + public static getDraftField() { + const draftField = Settings.get(SETTINGS_CONTENT_DRAFT_FIELD); + if (draftField) { + return draftField; + } + + return null; + } + + /** + * Retrieve the field its status + * @param data + * @returns + */ + public static getDraftStatus(data: { [field: string]: any }) { + const draftField = ContentType.getDraftField(); + if (draftField && data && data[draftField.name]) { + const fieldValue = data[draftField.name]; + if (draftField.type === "boolean") { + return fieldValue ? "Draft" : "Published"; + } else { + return fieldValue; + } + } + return null; + } + /** * Create content based on content types * @returns diff --git a/src/models/DraftField.ts b/src/models/DraftField.ts new file mode 100644 index 00000000..d2edd162 --- /dev/null +++ b/src/models/DraftField.ts @@ -0,0 +1,5 @@ +export interface DraftField { + name: string; + type: "boolean" | "choice"; + choices?: string[]; +} \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index f5e73b59..88254121 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -1,4 +1,5 @@ import { FileType } from "vscode"; +import { DraftField } from "."; import { Choice } from "./Choice"; import { DashboardData } from "./DashboardData"; @@ -17,6 +18,7 @@ export interface PanelSettings { preview: PreviewSettings; contentTypes: ContentType[]; dashboardViewData: DashboardData | undefined; + draftField: DraftField; } export interface ContentType { @@ -29,7 +31,7 @@ export interface ContentType { export interface Field { title?: string; name: string; - type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories"; + type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft"; choices?: string[] | Choice[]; single?: boolean; multiple?: boolean; diff --git a/src/models/index.ts b/src/models/index.ts index ef7e31d9..7f2c2f0a 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,6 +1,7 @@ export * from './Choice'; export * from './ContentFolder'; export * from './DashboardData'; +export * from './DraftField'; export * from './Framework'; export * from './MediaPaths'; export * from './PanelSettings'; diff --git a/src/panelWebView/components/Fields/DraftField.tsx b/src/panelWebView/components/Fields/DraftField.tsx new file mode 100644 index 00000000..ddc5456f --- /dev/null +++ b/src/panelWebView/components/Fields/DraftField.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { RocketIcon } from '../Icons/RocketIcon'; +import { VsLabel } from '../VscodeComponents'; +import { ChoiceField } from './ChoiceField'; +import { Toggle } from './Toggle'; + +export interface IDraftFieldProps { + label: string; + type: "boolean" | "choice"; + value: boolean | string | null | undefined; + + choices?: string[]; + + onChanged: (value: string | boolean) => void; +} + +export const DraftField: React.FunctionComponent = ({ label, type, value, choices, onChanged }: React.PropsWithChildren) => { + + if (type === "boolean") { + return ( + onChanged(checked)} /> + ); + } + + if (type === "choice") { + return ( + onChanged(value as string)} /> + ); + } + + return null; +}; \ No newline at end of file diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index eff704c1..102232a7 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -18,6 +18,7 @@ import { ChoiceField } from './Fields/ChoiceField'; import useContentType from '../../hooks/useContentType'; import { DateHelper } from '../../helpers/DateHelper'; import FieldBoundary from './ErrorBoundary/FieldBoundary'; +import { DraftField } from './Fields/DraftField'; export interface IMetadataProps { settings: PanelSettings | undefined; @@ -172,6 +173,20 @@ const Metadata: React.FunctionComponent = ({settings, metadata, unsetFocus={unsetFocus} /> ); + } else if (field.type === 'draft') { + const draftField = settings?.draftField; + const value = metadata[field.name]; + + return ( + + sendUpdate(field.name, value)} /> + + ); } else { return null; } From cbf434f741622636110920834f9ab0497e8ea533 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 25 Oct 2021 12:54:19 +0200 Subject: [PATCH 05/11] Updated changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 344bcc8c..1131ede9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log -## [5.2.1] - 2021-10-25 +## [5.3.0] - 2021-10-XX + +### 🎨 Enhancements + +- [#158](https://github.com/estruyf/vscode-front-matter/issues/158): Add support for non-boolean draft/publish status fields ### 🐞 Fixes From b473431eaedcadeb0357e6a07a70036ac00220df Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 26 Oct 2021 14:42:01 +0200 Subject: [PATCH 06/11] #159 - SEO enhancements --- CHANGELOG.md | 1 + assets/media/styles.css | 21 +++++- package.json | 12 ++++ src/commands/StatusListener.ts | 1 - src/constants/settings.ts | 3 + src/explorerView/ExplorerView.ts | 51 ++++++++++++-- src/extension.ts | 2 +- src/helpers/CustomScript.ts | 2 - src/models/PanelSettings.ts | 1 + .../components/ArticleDetails.tsx | 30 ++++++++ src/panelWebView/components/SeoFieldInfo.tsx | 2 +- .../components/SeoKeywordInfo.tsx | 70 +++++++++++++++---- src/panelWebView/components/SeoKeywords.tsx | 19 +++-- src/panelWebView/components/SeoStatus.tsx | 9 ++- src/panelWebView/components/ValidInfo.tsx | 4 +- 15 files changed, 197 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1131ede9..90a58029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 🎨 Enhancements - [#158](https://github.com/estruyf/vscode-front-matter/issues/158): Add support for non-boolean draft/publish status fields +- [#159](https://github.com/estruyf/vscode-front-matter/issues/159): Enhancements to SEO checks: Slug check, keyword details, more article information ### 🐞 Fixes diff --git a/assets/media/styles.css b/assets/media/styles.css index 40ec8a0f..d9cb4f9f 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -356,8 +356,18 @@ text-transform: capitalize; } +.table__cell__seo_details { + padding: 10px; +} + .table__cell__validation { - text-align: center; + text-align: left; +} + +.table__cell__validation div { + display: flex; + align-items: center; + padding: 2px 0; } .table__cell__validation .valid { @@ -368,6 +378,15 @@ color: #E6AF2E; } +.table__cell__validation div span + span { + margin-left: .5rem; +} + +.seo__status__note { + font-size: 10px; + padding: 3px 0; +} + /* Fields */ .field__toggle { position: relative; diff --git a/package.json b/package.json index 8ae47c11..1783d177 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,12 @@ "configuration": { "title": "Front Matter: use frontmatter.json for shared team settings", "properties": { + "frontMatter.site.baseURL": { + "type": "string", + "default": "", + "markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)", + "scope": "Site" + }, "frontMatter.content.autoUpdateDate": { "type": "boolean", "default": false, @@ -494,6 +500,12 @@ "markdownDescription": "Specifies the optimal description length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seodescriptionlength)", "scope": "Taxonomy" }, + "frontMatter.taxonomy.seoSlugLength": { + "type": "number", + "default": 75, + "markdownDescription": "Specifies the optimal slug length for SEO (set to `-1` to turn it off). [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.seoSlugLength)", + "scope": "Taxonomy" + }, "frontMatter.taxonomy.seoTitleLength": { "type": "number", "default": 60, diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index 7b11e678..e77b783e 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -20,7 +20,6 @@ export class StatusListener { const draft = ContentType.getDraftField(); if (!draft || draft.type !== "boolean") { frontMatterSB.hide(); - return; } let editor = vscode.window.activeTextEditor; diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 55a4dd12..63259245 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -20,6 +20,7 @@ export const SETTING_REMOVE_QUOTES = "taxonomy.noPropertyValueQuotes"; export const SETTING_FRONTMATTER_TYPE = "taxonomy.frontMatterType"; export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength"; +export const SETTING_SEO_SLUG_LENGTH = "taxonomy.seoSlugLength"; export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength"; export const SETTING_SEO_CONTENT_MIN_LENGTH = "taxonomy.seoContentLengh"; export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField"; @@ -45,6 +46,8 @@ export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; export const SETTINGS_FRAMEWORK_ID = "framework.id"; +export const SETTING_SITE_BASEURL = "site.baseURL"; + /** * @deprecated */ diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 07033d73..ff073a22 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,6 +1,6 @@ import { DashboardData } from '../models/DashboardData'; import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL } from '../constants'; import * as os from 'os'; import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; @@ -12,7 +12,7 @@ import { TagType } from '../panelWebView/TagType'; import { DraftField, TaxonomyType } from '../models'; import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; -import { Content } from 'mdast'; +import { Content, Root } from 'mdast'; import { COMMAND_NAME } from '../constants/Extension'; import { Folders } from '../commands/Folders'; import { Preview } from '../commands/Preview'; @@ -22,6 +22,7 @@ import { Extension } from '../helpers/Extension'; import { Dashboard } from '../commands/Dashboard'; import { ImageHelper } from '../helpers/ImageHelper'; import { CustomScript } from '../helpers/CustomScript'; +import { Link, Parent, Text } from 'mdast-util-from-markdown/lib'; const FILE_LIMIT = 10; @@ -380,6 +381,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { data: { seo: { title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1, + slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1, description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1, content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1, descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description @@ -476,6 +478,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * Get article details */ private getArticleDetails() { + const baseUrl = Settings.get(SETTING_SITE_BASEURL); const editor = window.activeTextEditor; if (!editor) { return null; @@ -492,13 +495,36 @@ export class ExplorerView implements WebviewViewProvider, Disposable { content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes const mdTree = fromMarkdown(content); - const headings = mdTree.children.filter(node => node.type === 'heading').length; - const paragraphs = mdTree.children.filter(node => node.type === 'paragraph').length; + const elms: Parent[] | Link[] = this.getAllElms(mdTree); + + const headings = elms.filter(node => node.type === 'heading'); + const paragraphs = elms.filter(node => node.type === 'paragraph').length; + const images = elms.filter(node => node.type === 'image').length; + const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url); + + const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length; + let externalLinks = links.filter(link => link.startsWith('http')); + if (baseUrl) { + externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase())); + } + + const headers = []; + for (const header of headings) { + const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" "); + if (text) { + headers.push(text); + } + } + const wordCount = this.wordCount(0, mdTree); return { - headings, + headings: headings.length, + headingsText: headers, paragraphs, + images, + internalLinks, + externalLinks: externalLinks.length, wordCount, content: article.content }; @@ -507,6 +533,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable { return null; } + private getAllElms(node: Content | any, allElms?: any[]): any[] { + if (!allElms) { + allElms = []; + } + + if (node.children?.length > 0) { + for (const child of node.children) { + allElms.push(Object.assign({}, child)); + this.getAllElms(child, allElms); + } + } + + return allElms; + } + private counts(acc: any, node: any) { // add 1 to an initial or existing value acc[node.type] = (acc[node.type] || 0) + 1; diff --git a/src/extension.ts b/src/extension.ts index ad613b9b..6d416d32 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -126,7 +126,7 @@ export async function activate(context: vscode.ExtensionContext) { }); // Settings promotion command - subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, () => { console.log('promote'); SettingsHelper.promote(); })); + subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote )); // Collapse all sections in the webview const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => { diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index 477add62..f12912e7 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -89,8 +89,6 @@ export class CustomScript { articleData = `'${articleData}'`; } - console.log(articleData); - exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${contentPath}" ${articleData}`, (error, stdout) => { if (error) { Notifications.error(`${script.title}: ${error.message}`); diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 88254121..86c52ac3 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -45,6 +45,7 @@ export interface DateInfo { export interface SEO { title: number; + slug: number; description: number; content: number; descriptionField: string; diff --git a/src/panelWebView/components/ArticleDetails.tsx b/src/panelWebView/components/ArticleDetails.tsx index 3969cc69..12499096 100644 --- a/src/panelWebView/components/ArticleDetails.tsx +++ b/src/panelWebView/components/ArticleDetails.tsx @@ -6,6 +6,9 @@ export interface IArticleDetailsProps { headings: number; paragraphs: number; wordCount: number; + internalLinks: number; + externalLinks: number; + images: number; } } @@ -42,6 +45,33 @@ const ArticleDetails: React.FunctionComponent = ({details} ) } + + { + details?.internalLinks !== undefined && ( + + Internal links + {details.internalLinks} + + ) + } + + { + details?.externalLinks !== undefined && ( + + External links + {details.externalLinks} + + ) + } + + { + details?.images !== undefined && ( + + Images + {details.images} + + ) + } diff --git a/src/panelWebView/components/SeoFieldInfo.tsx b/src/panelWebView/components/SeoFieldInfo.tsx index d61cf4f5..28d95fe6 100644 --- a/src/panelWebView/components/SeoFieldInfo.tsx +++ b/src/panelWebView/components/SeoFieldInfo.tsx @@ -15,7 +15,7 @@ const SeoFieldInfo: React.FunctionComponent = ({ title, valu {title} {value}/{recommendation} - { isValid !== undefined ? : - } + { isValid !== undefined ? : - } ); diff --git a/src/panelWebView/components/SeoKeywordInfo.tsx b/src/panelWebView/components/SeoKeywordInfo.tsx index bdf0b451..1b27b641 100644 --- a/src/panelWebView/components/SeoKeywordInfo.tsx +++ b/src/panelWebView/components/SeoKeywordInfo.tsx @@ -8,9 +8,39 @@ export interface ISeoKeywordInfoProps { description: string; slug: string; content: string; + wordCount?: number; + headings?: string[]; } -const SeoKeywordInfo: React.FunctionComponent = ({keyword, title, description, slug, content}: React.PropsWithChildren) => { +const SeoKeywordInfo: React.FunctionComponent = ({keyword, title, description, slug, content, wordCount, headings}: React.PropsWithChildren) => { + + const density = () => { + if (!wordCount) { + return null; + } + + const pattern = new RegExp('\\b' + keyword.toLowerCase() + '\\b', 'ig'); + const count = (content.match(pattern) || []).length; + const density = (count / wordCount) * 100; + const densityTitle = `Keyword usage ${density.toFixed(2)}% *`; + + if (density < 0.75) { + return + } else if (density >= 0.75 && density < 1.5) { + return + } else { + return + } + }; + + const checkHeadings = () => { + if (!headings || headings.length === 0) { + return null; + } + + const exists = headings.filter(heading => heading.split(' ').findIndex(word => word.toLowerCase() === keyword.toLowerCase()) !== -1); + return 0} />; + }; if (!keyword) { return null; @@ -19,17 +49,33 @@ const SeoKeywordInfo: React.FunctionComponent = ({keyword, return ( {keyword} - - - - - - - - - - - + +
+ +
+
+ +
+
+ +
+
+ +
+ { + headings && headings.length > 0 && ( +
+ {checkHeadings()} +
+ ) + } + { + wordCount && ( +
+ {density()} +
+ ) + }
); diff --git a/src/panelWebView/components/SeoKeywords.tsx b/src/panelWebView/components/SeoKeywords.tsx index 177804d5..282e9a83 100644 --- a/src/panelWebView/components/SeoKeywords.tsx +++ b/src/panelWebView/components/SeoKeywords.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { SeoKeywordInfo } from './SeoKeywordInfo'; -import { VsTable, VsTableBody, VsTableCell, VsTableHeader, VsTableHeaderCell, VsTableRow } from './VscodeComponents'; +import { VsTable, VsTableBody, VsTableHeader, VsTableHeaderCell } from './VscodeComponents'; export interface ISeoKeywordsProps { keywords: string[] | null; @@ -9,6 +9,8 @@ export interface ISeoKeywordsProps { description: string; slug: string; content: string; + headings?: string[]; + wordCount?: number; } const SeoKeywords: React.FunctionComponent = ({keywords, ...data}: React.PropsWithChildren) => { @@ -37,13 +39,10 @@ const SeoKeywords: React.FunctionComponent = ({keywords, ...d

Keywords

- + Keyword - Title - Description - Slug - Content + Details { @@ -55,6 +54,14 @@ const SeoKeywords: React.FunctionComponent = ({keywords, ...d } + + { + data.wordCount && ( +
+ * A keyword density of 1-1.5% is sufficient in most cases. +
+ ) + }
); }; diff --git a/src/panelWebView/components/SeoStatus.tsx b/src/panelWebView/components/SeoStatus.tsx index 357a8568..dea3200b 100644 --- a/src/panelWebView/components/SeoStatus.tsx +++ b/src/panelWebView/components/SeoStatus.tsx @@ -13,7 +13,7 @@ export interface ISeoStatusProps { const SeoStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { const { data, seo } = props; - const { title } = data; + const { title, slug } = data; const [ isOpen, setIsOpen ] = React.useState(true); const tableRef = React.useRef(); const pushUpdate = React.useRef((value: boolean) => { @@ -65,6 +65,11 @@ const SeoStatus: React.FunctionComponent = (props: React.PropsW ) } + { + (slug && seo.slug > 0) && ( + + ) + } { (data[descriptionField] && seo.description > 0) && ( @@ -85,6 +90,8 @@ const SeoStatus: React.FunctionComponent = (props: React.PropsW title={title} description={data[descriptionField]} slug={data.slug} + headings={data?.articleDetails?.headingsText} + wordCount={data?.articleDetails?.wordCount} content={data?.articleDetails?.content} /> diff --git a/src/panelWebView/components/ValidInfo.tsx b/src/panelWebView/components/ValidInfo.tsx index 431f923a..2bc2877b 100644 --- a/src/panelWebView/components/ValidInfo.tsx +++ b/src/panelWebView/components/ValidInfo.tsx @@ -3,10 +3,11 @@ import { CheckIcon } from './Icons/CheckIcon'; import { WarningIcon } from './Icons/WarningIcon'; export interface IValidInfoProps { + label?: string; isValid: boolean; } -const ValidInfo: React.FunctionComponent = ({isValid}: React.PropsWithChildren) => { +const ValidInfo: React.FunctionComponent = ({label, isValid}: React.PropsWithChildren) => { return ( <> { @@ -16,6 +17,7 @@ const ValidInfo: React.FunctionComponent = ({isValid}: React.Pr ) } + { label && {label} } ); }; From c60520c0ff5fdd1fe34209c9ae31d4d497a4aff5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 26 Oct 2021 15:28:01 +0200 Subject: [PATCH 07/11] Fix replace in action button --- src/explorerView/ExplorerView.ts | 4 ++-- src/panelWebView/components/Actions.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index ff073a22..d327a2a1 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -12,7 +12,7 @@ import { TagType } from '../panelWebView/TagType'; import { DraftField, TaxonomyType } from '../models'; import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; -import { Content, Root } from 'mdast'; +import { Content } from 'mdast'; import { COMMAND_NAME } from '../constants/Extension'; import { Folders } from '../commands/Folders'; import { Preview } from '../commands/Preview'; @@ -22,7 +22,7 @@ import { Extension } from '../helpers/Extension'; import { Dashboard } from '../commands/Dashboard'; import { ImageHelper } from '../helpers/ImageHelper'; import { CustomScript } from '../helpers/CustomScript'; -import { Link, Parent, Text } from 'mdast-util-from-markdown/lib'; +import { Link, Parent } from 'mdast-util-from-markdown/lib'; const FILE_LIMIT = 10; diff --git a/src/panelWebView/components/Actions.tsx b/src/panelWebView/components/Actions.tsx index b07ffcc7..ac5454f3 100644 --- a/src/panelWebView/components/Actions.tsx +++ b/src/panelWebView/components/Actions.tsx @@ -27,8 +27,8 @@ const Actions: React.FunctionComponent = (props: React.PropsWithC { (settings && settings.scripts && settings.scripts.length > 0) && ( - settings.scripts.map((value) => ( - + settings.scripts.map((value, idx) => ( + )) ) } From 3571af82c7aa90518d318c9da9debe2a3999c532 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 26 Oct 2021 15:59:31 +0200 Subject: [PATCH 08/11] Updated readme --- README.beta.md | 4 ++++ README.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.beta.md b/README.beta.md index f3b85ef7..96c7932e 100644 --- a/README.beta.md +++ b/README.beta.md @@ -48,6 +48,10 @@ Our main extension features are: > If you see something missing in your article creation flow, please feel free to reach out. +**Version 5** + +The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0). + **Version 4** Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0). diff --git a/README.md b/README.md index 5068fab6..0bbbf7d7 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ Our main extension features are: > If you see something missing in your article creation flow, please feel free to reach out. +**Version 5** + +The new media dashboard redesign got introduced + support for setting metadata on media files [v5.0.0 release notes](https://frontmatter.codes/updates/v5.0.0). + **Version 4** Support for Team level settings, content-types, and image support. Get to know more at: [v4.0.0 release notes](https://frontmatter.codes/updates/v4_0_0). From 263ccab311bfc7cccfc3e099b442d748385e2b4b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 26 Oct 2021 15:59:45 +0200 Subject: [PATCH 09/11] 5.3.0 --- package-lock.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 507bbc3d..523aff44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-front-matter-beta", - "version": "5.2.0", + "version": "5.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1783d177..098df648 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter", "description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "5.2.0", + "version": "5.3.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -783,4 +783,4 @@ "dependencies": { "@docsearch/js": "^3.0.0-alpha.40" } -} \ No newline at end of file +} From b96722dd69b6a4745a1035d5f79a4fb21dd4d1bc Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 27 Oct 2021 11:42:57 +0200 Subject: [PATCH 10/11] #158 - Update boolean field check --- package.json | 16 ++++++++-------- src/helpers/ContentType.ts | 21 ++++++++++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 098df648..c79b04c1 100644 --- a/package.json +++ b/package.json @@ -91,12 +91,6 @@ "configuration": { "title": "Front Matter: use frontmatter.json for shared team settings", "properties": { - "frontMatter.site.baseURL": { - "type": "string", - "default": "", - "markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)", - "scope": "Site" - }, "frontMatter.content.autoUpdateDate": { "type": "boolean", "default": false, @@ -117,7 +111,7 @@ "boolean", "choice" ], - "description": "Define the type of field" + "description": "" }, "name": { "type": "string", @@ -262,6 +256,12 @@ "markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.preview.pathname)", "scope": "Site preview" }, + "frontMatter.site.baseURL": { + "type": "string", + "default": "", + "markdownDescription": "Specify the base URL of your site, this will be used for SEO checks. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.site.baseURL)", + "scope": "Site" + }, "frontMatter.taxonomy.alignFilename": { "type": "boolean", "default": false, @@ -783,4 +783,4 @@ "dependencies": { "@docsearch/js": "^3.0.0-alpha.40" } -} +} \ No newline at end of file diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 9118042a..6a7cc15c 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -7,7 +7,6 @@ import { Questions } from "./Questions"; import { writeFileSync } from "fs"; import { Notifications } from "./Notifications"; import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType"; -import { GrayMatterFile } from "gray-matter"; export class ContentType { @@ -31,15 +30,27 @@ export class ContentType { * @returns */ public static getDraftStatus(data: { [field: string]: any }) { - const draftField = ContentType.getDraftField(); - if (draftField && data && data[draftField.name]) { - const fieldValue = data[draftField.name]; - if (draftField.type === "boolean") { + const contentType = ArticleHelper.getContentType(data); + const draftSetting = ContentType.getDraftField(); + + const draftField = contentType.fields.find(f => f.type === "draft"); + + let fieldValue = null; + + if (draftField) { + fieldValue = data[draftField.name]; + } else if (draftSetting && data && data[draftSetting.name]) { + fieldValue = data[draftSetting.name]; + } + + if (draftSetting && fieldValue) { + if (draftSetting.type === "boolean") { return fieldValue ? "Draft" : "Published"; } else { return fieldValue; } } + return null; } From 5916344092128748e87a12224a3320e62b21d75d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 28 Oct 2021 12:00:24 +0200 Subject: [PATCH 11/11] added release notes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a58029..8ae237a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [5.3.0] - 2021-10-XX +## [5.3.0] - 2021-10-28 - [Release Notes](https://beta.frontmatter.codes/updates/v5.3.0) ### 🎨 Enhancements