mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Issue: slug placeholder not working if there is no title field #830
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#827](https://github.com/estruyf/vscode-front-matter/issues/827): Fix for `frontmatter.json` file which gets created when already present in a sub-folder
|
||||
- [#830](https://github.com/estruyf/vscode-front-matter/issues/830): Fix for using the SEO title field setting to change the title field reference
|
||||
|
||||
## [10.2.0] - 2024-06-12 - [Release notes](https://beta.frontmatter.codes/updates/v10.2.0)
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import { NavigationType } from '../dashboardWebView/models';
|
||||
import { SNIPPET } from '../constants/Snippet';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { getTitleField } from '../utils';
|
||||
|
||||
export class Article {
|
||||
/**
|
||||
@@ -213,7 +214,7 @@ export class Article {
|
||||
contentType
|
||||
);
|
||||
|
||||
const titleField = 'title';
|
||||
const titleField = getTitleField();
|
||||
const articleTitle: string = article.data[titleField];
|
||||
const slugInfo = Article.generateSlug(articleTitle, article, contentType.slugTemplate);
|
||||
|
||||
@@ -307,17 +308,18 @@ export class Article {
|
||||
|
||||
const parsedFile = parse(file);
|
||||
|
||||
const titleField = getTitleField();
|
||||
const slugTemplate = Settings.get<string>(SETTING_SLUG_TEMPLATE);
|
||||
if (slugTemplate) {
|
||||
if (slugTemplate === '{{title}}') {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data?.title) {
|
||||
return article.data.title.toLowerCase().replace(/\s/g, '-');
|
||||
if (article?.data && article.data[titleField]) {
|
||||
return article.data[titleField].toLowerCase().replace(/\s/g, '-');
|
||||
}
|
||||
} else {
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
return SlugHelper.createSlug(article.data.title, article.data, slugTemplate);
|
||||
return SlugHelper.createSlug(article.data[titleField], article.data, slugTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,11 +492,12 @@ export class Article {
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType = article ? await ArticleHelper.getContentType(article) : undefined;
|
||||
const tileField = getTitleField();
|
||||
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: NavigationType.Snippets,
|
||||
data: {
|
||||
fileTitle: article?.data.title || '',
|
||||
fileTitle: article?.data[tileField] || '',
|
||||
filePath: editor.document.uri.fsPath,
|
||||
fieldName: basename(editor.document.uri.fsPath),
|
||||
contentType,
|
||||
|
||||
@@ -31,7 +31,7 @@ import { ParsedFrontMatter } from '../parsers';
|
||||
import { getLocalizationFile } from '../utils/getLocalizationFile';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { joinUrl } from '../utils';
|
||||
import { getTitleField, joinUrl } from '../utils';
|
||||
import { i18n } from './i18n';
|
||||
|
||||
export class Preview {
|
||||
@@ -77,11 +77,13 @@ export class Preview {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = getTitleField();
|
||||
|
||||
// Create the preview webview
|
||||
const webView = window.createWebviewPanel(
|
||||
'frontMatterPreview',
|
||||
article?.data?.title
|
||||
? l10n.t(LocalizationKey.commandsPreviewPanelTitle, article?.data.title)
|
||||
article?.data && article?.data[titleField]
|
||||
? l10n.t(LocalizationKey.commandsPreviewPanelTitle, article?.data[titleField])
|
||||
: 'Front Matter Preview',
|
||||
{
|
||||
viewColumn: ViewColumn.Beside,
|
||||
|
||||
@@ -3,15 +3,12 @@ import {
|
||||
CONTEXT,
|
||||
EXTENSION_NAME,
|
||||
NOTIFICATION_TYPE,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_DESCRIPTION_LENGTH,
|
||||
SETTING_SEO_TITLE_FIELD,
|
||||
SETTING_SEO_TITLE_LENGTH
|
||||
} from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { ArticleHelper, Notifications, SeoHelper, Settings } from '../helpers';
|
||||
import { PanelProvider } from '../panelWebView/PanelProvider';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { DataListener } from '../listeners/panel';
|
||||
import { commands } from 'vscode';
|
||||
@@ -20,6 +17,7 @@ import { Preview } from './Preview';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { i18n } from './i18n';
|
||||
import { getDescriptionField, getTitleField } from '../utils';
|
||||
|
||||
export class StatusListener {
|
||||
/**
|
||||
@@ -56,12 +54,10 @@ export class StatusListener {
|
||||
collection.clear();
|
||||
|
||||
// Retrieve the SEO config properties
|
||||
const titleLength = (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1;
|
||||
const descLength = (Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number) || -1;
|
||||
const titleField =
|
||||
(Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField =
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
const titleLength = Settings.get<number>(SETTING_SEO_TITLE_LENGTH) || -1;
|
||||
const descLength = Settings.get<number>(SETTING_SEO_DESCRIPTION_LENGTH) || -1;
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
if (editor && article.data[titleField] && titleLength > -1) {
|
||||
SeoHelper.checkLength(editor, collection, article, titleField, titleLength);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { COMMAND_NAME, SETTING_CONTENT_I18N } from '../constants';
|
||||
import { ContentFolder, Field, I18nConfig, ContentType as IContentType } from '../models';
|
||||
import { join, parse } from 'path';
|
||||
import { existsAsync } from '../utils';
|
||||
import { existsAsync, getDescriptionField, getTitleField } from '../utils';
|
||||
import { Folders } from '.';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
@@ -412,8 +412,11 @@ export class i18n {
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const title = article.data.title || '';
|
||||
const description = article.data.description || '';
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
const title = article.data[titleField] || '';
|
||||
const description = article.data[descriptionField] || '';
|
||||
const content = article.content || '';
|
||||
|
||||
const text = [title, description, content];
|
||||
@@ -428,8 +431,8 @@ export class i18n {
|
||||
return;
|
||||
}
|
||||
|
||||
article.data.title = article.data.title ? translations[0] : '';
|
||||
article.data.description = article.data.description ? translations[1] : '';
|
||||
article.data[titleField] = article.data[titleField] ? translations[0] : '';
|
||||
article.data[descriptionField] = article.data[descriptionField] ? translations[1] : '';
|
||||
article.content = article.content ? translations[2] : '';
|
||||
} catch (error) {
|
||||
Notifications.error(`${(error as Error).message}`);
|
||||
|
||||
@@ -48,7 +48,7 @@ import { Link, Parent } from 'mdast-util-from-markdown/lib';
|
||||
import { Content } from 'mdast';
|
||||
import { CustomScript } from './CustomScript';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { existsAsync } from '../utils';
|
||||
import { existsAsync, getTitleField } from '../utils';
|
||||
import { mkdirAsync } from '../utils/mkdirAsync';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
@@ -635,11 +635,12 @@ export class ArticleHelper {
|
||||
) {
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
const fmData = Object.assign({}, data);
|
||||
const titleField = getTitleField();
|
||||
|
||||
for (const fieldName of Object.keys(fmData)) {
|
||||
const fieldValue = fmData[fieldName];
|
||||
|
||||
if (fieldName === 'title' && (fieldValue === null || fieldValue === '')) {
|
||||
if (fieldName === titleField && (fieldValue === null || fieldValue === '')) {
|
||||
fmData[fieldName] = title;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import {
|
||||
COMMAND_NAME,
|
||||
DefaultFieldValues,
|
||||
DefaultFields,
|
||||
EXTENSION_NAME,
|
||||
FEATURE_FLAG,
|
||||
SETTING_CONTENT_DRAFT_FIELD,
|
||||
@@ -37,7 +38,7 @@ import { DEFAULT_CONTENT_TYPE_NAME } from '../constants/ContentType';
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { basename } from 'path';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { encodeEmoji, existsAsync, fieldWhenClause, writeFileAsync } from '../utils';
|
||||
import { encodeEmoji, existsAsync, fieldWhenClause, getTitleField, writeFileAsync } from '../utils';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
|
||||
@@ -950,8 +951,11 @@ export class ContentType {
|
||||
}
|
||||
|
||||
titleValue = titleValue.trim();
|
||||
|
||||
const titleFieldName = getTitleField();
|
||||
|
||||
// Check if the title needs to encode the emoji's used in it
|
||||
const titleField = contentType.fields.find((f) => f.name === 'title');
|
||||
const titleField = contentType.fields.find((f) => f.name === titleFieldName);
|
||||
if (titleField && titleField.encodeEmoji) {
|
||||
titleValue = encodeEmoji(titleValue);
|
||||
}
|
||||
@@ -1058,6 +1062,7 @@ export class ContentType {
|
||||
isRoot: boolean = true
|
||||
): Promise<any> {
|
||||
if (obj.fields) {
|
||||
const titleField = getTitleField();
|
||||
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
|
||||
|
||||
for (const field of obj.fields) {
|
||||
@@ -1066,7 +1071,7 @@ export class ContentType {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.name === 'title') {
|
||||
if (field.name === titleField) {
|
||||
if (field.default) {
|
||||
data[field.name] = processArticlePlaceholdersFromData(
|
||||
field.default as string,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Preview } from '../commands/Preview';
|
||||
import { Project } from '../commands/Project';
|
||||
import {
|
||||
CONTEXT,
|
||||
DefaultFields,
|
||||
SETTING_CONTENT_DRAFT_FIELD,
|
||||
SETTING_CONTENT_FRONTMATTER_HIGHLIGHT,
|
||||
SETTING_DATA_TYPES,
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
SETTING_DATE_FORMAT,
|
||||
SETTING_PANEL_FREEFORM,
|
||||
SETTING_SEO_CONTENT_MIN_LENGTH,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_DESCRIPTION_LENGTH,
|
||||
SETTING_SEO_SLUG_LENGTH,
|
||||
SETTING_SEO_TITLE_LENGTH,
|
||||
@@ -30,8 +28,7 @@ import {
|
||||
SETTING_SLUG_SUFFIX,
|
||||
SETTING_SLUG_UPDATE_FILE_NAME,
|
||||
SETTING_TAXONOMY_CUSTOM,
|
||||
SETTING_TAXONOMY_FIELD_GROUPS,
|
||||
SETTING_SEO_TITLE_FIELD
|
||||
SETTING_TAXONOMY_FIELD_GROUPS
|
||||
} from '../constants';
|
||||
import { GitListener } from '../listeners/general';
|
||||
import {
|
||||
@@ -46,6 +43,7 @@ import {
|
||||
} from '../models';
|
||||
import { Folders } from '../commands';
|
||||
import { Copilot } from '../services/Copilot';
|
||||
import { getDescriptionField, getTitleField } from '../utils';
|
||||
|
||||
export class PanelSettings {
|
||||
public static async get(): Promise<IPanelSettings> {
|
||||
@@ -61,9 +59,8 @@ export class PanelSettings {
|
||||
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,
|
||||
titleField: (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title,
|
||||
descriptionField:
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description
|
||||
titleField: getTitleField(),
|
||||
descriptionField: getDescriptionField()
|
||||
},
|
||||
slug: {
|
||||
prefix: Settings.get(SETTING_SLUG_PREFIX) || '',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ContentType } from '../models';
|
||||
import { getTitleField } from '../utils';
|
||||
import { ArticleHelper } from './ArticleHelper';
|
||||
import { SlugHelper } from './SlugHelper';
|
||||
|
||||
@@ -7,16 +8,17 @@ export const processArticlePlaceholdersFromData = (
|
||||
data: { [key: string]: any },
|
||||
contentType: ContentType
|
||||
): string => {
|
||||
if (value.includes('{{title}}') && data.title) {
|
||||
const titleField = getTitleField();
|
||||
if (value.includes('{{title}}') && data[titleField]) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
value = value.replace(regex, data.title || '');
|
||||
value = value.replace(regex, data[titleField] || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{slug}}')) {
|
||||
const regex = new RegExp('{{slug}}', 'g');
|
||||
value = value.replace(
|
||||
regex,
|
||||
SlugHelper.createSlug(data.title || '', data, contentType.slugTemplate) || ''
|
||||
SlugHelper.createSlug(data[titleField] || '', data, contentType.slugTemplate) || ''
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,9 +34,11 @@ export const processArticlePlaceholdersFromPath = async (
|
||||
return value;
|
||||
}
|
||||
|
||||
const titleField = getTitleField();
|
||||
|
||||
if (value.includes('{{title}}')) {
|
||||
const regex = new RegExp('{{title}}', 'g');
|
||||
value = value.replace(regex, article.data.title || '');
|
||||
value = value.replace(regex, article.data[titleField] || '');
|
||||
}
|
||||
|
||||
if (value.includes('{{slug}}') && filePath) {
|
||||
@@ -43,8 +47,11 @@ export const processArticlePlaceholdersFromPath = async (
|
||||
const regex = new RegExp('{{slug}}', 'g');
|
||||
value = value.replace(
|
||||
regex,
|
||||
SlugHelper.createSlug(article.data.title || '', article.data, contentType.slugTemplate) ||
|
||||
''
|
||||
SlugHelper.createSlug(
|
||||
article.data[titleField] || '',
|
||||
article.data,
|
||||
contentType.slugTemplate
|
||||
) || ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,7 @@ import { Folders } from '../../commands/Folders';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import {
|
||||
Uri,
|
||||
authentication,
|
||||
commands,
|
||||
window
|
||||
} from 'vscode';
|
||||
import { Uri, authentication, commands, window } from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
@@ -30,7 +25,6 @@ import {
|
||||
SETTING_DATE_FORMAT,
|
||||
SETTING_GLOBAL_ACTIVE_MODE,
|
||||
SETTING_GLOBAL_MODES,
|
||||
SETTING_SEO_TITLE_FIELD,
|
||||
SETTING_TAXONOMY_CONTENT_TYPES
|
||||
} from '../../constants';
|
||||
import { Article, Preview } from '../../commands';
|
||||
@@ -42,7 +36,7 @@ import {
|
||||
ContentType as IContentType,
|
||||
FolderInfo
|
||||
} from '../../models';
|
||||
import { encodeEmoji, fieldWhenClause } from '../../utils';
|
||||
import { encodeEmoji, fieldWhenClause, getTitleField } from '../../utils';
|
||||
import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
import { SponsorAi } from '../../services/SponsorAI';
|
||||
@@ -140,7 +134,7 @@ export class DataListener extends BaseListener {
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = PanelProvider.getInstance(extPath);
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const titleField = getTitleField();
|
||||
const description = await Copilot.suggestDescription(
|
||||
articleDetails.data[titleField],
|
||||
articleDetails.content
|
||||
@@ -199,7 +193,7 @@ export class DataListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const titleField = getTitleField();
|
||||
|
||||
const suggestion = await SponsorAi.getDescription(
|
||||
githubAuth.accessToken,
|
||||
@@ -417,7 +411,7 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
|
||||
let beforeValue: any;
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const titleField = getTitleField();
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
@@ -774,9 +768,11 @@ export class DataListener extends BaseListener {
|
||||
data && contentType ? processArticlePlaceholdersFromData(value, data, contentType) : value;
|
||||
value = processTimePlaceholders(value, dateFormat);
|
||||
value = processFmPlaceholders(value, data);
|
||||
|
||||
const titleField = getTitleField();
|
||||
value = await ArticleHelper.processCustomPlaceholders(
|
||||
value,
|
||||
data.title || '',
|
||||
data[titleField] || '',
|
||||
crntFile?.uri.fsPath || ''
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,13 @@ import { authentication, window } from 'vscode';
|
||||
import { ArticleHelper, Extension, Settings, TaxonomyHelper } from '../../helpers';
|
||||
import { BlockFieldData, CustomTaxonomyData, PostMessageData, TaxonomyType } from '../../models';
|
||||
import { DataListener } from '.';
|
||||
import {
|
||||
DefaultFields,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_TITLE_FIELD
|
||||
} from '../../constants';
|
||||
import { SponsorAi } from '../../services/SponsorAI';
|
||||
import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { Copilot } from '../../services/Copilot';
|
||||
import { getDescriptionField, getTitleField } from '../../utils';
|
||||
|
||||
export class TaxonomyListener extends BaseListener {
|
||||
/**
|
||||
@@ -73,10 +69,9 @@ export class TaxonomyListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Suggests a taxonomy for a given command, request ID, and tag type.
|
||||
*
|
||||
*
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The ID of the request.
|
||||
* @param type - The type of the tag.
|
||||
@@ -100,8 +95,8 @@ export class TaxonomyListener extends BaseListener {
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = PanelProvider.getInstance(extPath);
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField = (Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
const tags = await Copilot.suggestTaxonomy(
|
||||
articleDetails.data[titleField],
|
||||
@@ -127,7 +122,7 @@ export class TaxonomyListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Suggests taxonomy based on the provided command, request ID, and tag type.
|
||||
*
|
||||
*
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The ID of the request.
|
||||
* @param type - The type of tag.
|
||||
@@ -166,9 +161,8 @@ export class TaxonomyListener extends BaseListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField =
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
const suggestions = await SponsorAi.getTaxonomySuggestions(
|
||||
githubAuth.accessToken,
|
||||
|
||||
@@ -137,7 +137,8 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (
|
||||
<FeatureFlag features={mode?.features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.seo}>
|
||||
<SeoStatus
|
||||
seo={settings.seo}
|
||||
data={metadata}
|
||||
metadata={metadata}
|
||||
settings={settings}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { SEO } from '../../models/PanelSettings';
|
||||
import { PanelSettings, SEO } from '../../models/PanelSettings';
|
||||
import { TagType } from '../TagType';
|
||||
import { ArticleDetails } from './ArticleDetails';
|
||||
import { Collapsible } from './Collapsible';
|
||||
@@ -8,49 +8,55 @@ import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { SeoFieldInfo } from './SeoFieldInfo';
|
||||
import { SeoKeywords } from './SeoKeywords';
|
||||
import { TagPicker } from './Fields/TagPicker';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { LocalizationKey, localize } from '../../localization';
|
||||
import { VSCodeTable, VSCodeTableBody, VSCodeTableHead, VSCodeTableHeader, VSCodeTableRow } from './VSCode/VSCodeTable';
|
||||
import useContentType from '../../hooks/useContentType';
|
||||
|
||||
export interface ISeoStatusProps {
|
||||
seo: SEO;
|
||||
data: any;
|
||||
metadata: any;
|
||||
settings: PanelSettings | undefined;
|
||||
focusElm: TagType | null;
|
||||
unsetFocus: () => void;
|
||||
}
|
||||
|
||||
const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
data,
|
||||
metadata,
|
||||
seo,
|
||||
settings,
|
||||
focusElm,
|
||||
unsetFocus
|
||||
}: React.PropsWithChildren<ISeoStatusProps>) => {
|
||||
const { title, slug } = data;
|
||||
const contentType = useContentType(settings, metadata);
|
||||
const { slug } = metadata;
|
||||
|
||||
const { descriptionField, titleField } = seo;
|
||||
|
||||
const tableContent = React.useMemo(() => {
|
||||
const titleFieldName = contentType?.fields.find(f => f.name === titleField)?.title || titleField;
|
||||
const descriptionFieldName = contentType?.fields.find(f => f.name === descriptionField)?.title || descriptionField;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`seo__status__details`}>
|
||||
<h4>{l10n.t(LocalizationKey.panelSeoStatusTitle)}</h4>
|
||||
<h4>{localize(LocalizationKey.panelSeoStatusTitle)}</h4>
|
||||
|
||||
<VSCodeTable>
|
||||
<VSCodeTableHeader>
|
||||
<VSCodeTableRow>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderProperty)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderLength)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{l10n.t(LocalizationKey.panelSeoStatusHeaderValid)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderProperty)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderLength)}</VSCodeTableHead>
|
||||
<VSCodeTableHead>{localize(LocalizationKey.panelSeoStatusHeaderValid)}</VSCodeTableHead>
|
||||
</VSCodeTableRow>
|
||||
</VSCodeTableHeader>
|
||||
|
||||
<VSCodeTableBody>
|
||||
{data[titleField] && seo.title > 0 ? (
|
||||
{metadata[titleField] && seo.title > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={titleField}
|
||||
value={data[titleField].length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.title)}
|
||||
isValid={data[titleField].length <= seo.title}
|
||||
title={titleFieldName}
|
||||
value={metadata[titleField].length}
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.title)}
|
||||
isValid={metadata[titleField].length <= seo.title}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -58,25 +64,25 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
<SeoFieldInfo
|
||||
title={`slug`}
|
||||
value={slug.length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.slug)}
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.slug)}
|
||||
isValid={slug.length <= seo.slug}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{data[descriptionField] && seo.description > 0 ? (
|
||||
{metadata[descriptionField] && seo.description > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={descriptionField}
|
||||
value={data[descriptionField].length}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.description)}
|
||||
isValid={data[descriptionField].length <= seo.description}
|
||||
title={descriptionFieldName}
|
||||
value={metadata[descriptionField].length}
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoCharacters, seo.description)}
|
||||
isValid={metadata[descriptionField].length <= seo.description}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{seo.content > 0 && data?.articleDetails?.wordCount > 0 ? (
|
||||
{seo.content > 0 && metadata?.articleDetails?.wordCount > 0 ? (
|
||||
<SeoFieldInfo
|
||||
title={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoArticle)}
|
||||
value={data?.articleDetails?.wordCount}
|
||||
recommendation={l10n.t(LocalizationKey.panelSeoStatusSeoFieldInfoWords, seo.content)}
|
||||
title={localize(LocalizationKey.panelSeoStatusSeoFieldInfoArticle)}
|
||||
value={metadata?.articleDetails?.wordCount}
|
||||
recommendation={localize(LocalizationKey.panelSeoStatusSeoFieldInfoWords, seo.content)}
|
||||
/>
|
||||
) : null}
|
||||
</VSCodeTableBody>
|
||||
@@ -84,20 +90,20 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
</div>
|
||||
|
||||
<SeoKeywords
|
||||
keywords={data?.keywords}
|
||||
title={title}
|
||||
description={data[descriptionField]}
|
||||
slug={data.slug}
|
||||
headings={data?.articleDetails?.headingsText}
|
||||
wordCount={data?.articleDetails?.wordCount}
|
||||
content={data?.articleDetails?.content}
|
||||
keywords={metadata?.keywords}
|
||||
title={metadata[titleField]}
|
||||
description={metadata[descriptionField]}
|
||||
slug={metadata.slug}
|
||||
headings={metadata?.articleDetails?.headingsText}
|
||||
wordCount={metadata?.articleDetails?.wordCount}
|
||||
content={metadata?.articleDetails?.content}
|
||||
/>
|
||||
|
||||
<FieldBoundary fieldName={`Keywords`}>
|
||||
<TagPicker
|
||||
type={TagType.keywords}
|
||||
icon={<SymbolKeywordIcon />}
|
||||
crntSelected={(data.keywords as string[]) || []}
|
||||
crntSelected={(metadata.keywords as string[]) || []}
|
||||
options={[]}
|
||||
freeform={true}
|
||||
focussed={focusElm === TagType.keywords}
|
||||
@@ -106,17 +112,17 @@ const SeoStatus: React.FunctionComponent<ISeoStatusProps> = ({
|
||||
/>
|
||||
</FieldBoundary>
|
||||
|
||||
<ArticleDetails details={data.articleDetails} />
|
||||
<ArticleDetails details={metadata.articleDetails} />
|
||||
</div>
|
||||
);
|
||||
}, [data, seo, focusElm, unsetFocus]);
|
||||
}, [contentType, metadata, seo, focusElm, unsetFocus]);
|
||||
|
||||
return (
|
||||
<Collapsible id={`seo`} title={l10n.t(LocalizationKey.panelSeoStatusCollapsibleTitle)}>
|
||||
{!title && !data[descriptionField] ? (
|
||||
<Collapsible id={`seo`} title={localize(LocalizationKey.panelSeoStatusCollapsibleTitle)}>
|
||||
{!metadata[titleField] && !metadata[descriptionField] ? (
|
||||
<div className={`seo__status__empty`}>
|
||||
<p>
|
||||
{l10n.t(LocalizationKey.panelSeoStatusRequired, "Title", descriptionField)}
|
||||
{localize(LocalizationKey.panelSeoStatusRequired, titleField, descriptionField)}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
DefaultFields,
|
||||
DEFAULT_CONTENT_TYPE_NAME,
|
||||
ExtensionState,
|
||||
SETTING_SEO_DESCRIPTION_FIELD,
|
||||
SETTING_SEO_TITLE_FIELD,
|
||||
SETTING_DATE_FORMAT
|
||||
} from '../constants';
|
||||
import { Page } from '../dashboardWebView/models';
|
||||
@@ -25,7 +23,7 @@ import {
|
||||
Notifications,
|
||||
Settings
|
||||
} from '../helpers';
|
||||
import { existsAsync } from '../utils';
|
||||
import { existsAsync, getDescriptionField, getTitleField } from '../utils';
|
||||
import { Article, Cache } from '../commands';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
@@ -183,10 +181,8 @@ export class PagesParser {
|
||||
if (article?.data) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
|
||||
const descriptionField =
|
||||
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
const titleField = getTitleField();
|
||||
const descriptionField = getDescriptionField();
|
||||
|
||||
const dateField =
|
||||
(await ArticleHelper.getPublishDateField(article)) || DefaultFields.PublishingDate;
|
||||
|
||||
5
src/utils/getDescriptionField.ts
Normal file
5
src/utils/getDescriptionField.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DefaultFields, SETTING_SEO_DESCRIPTION_FIELD } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
|
||||
export const getDescriptionField = () =>
|
||||
Settings.get<string>(SETTING_SEO_DESCRIPTION_FIELD) || DefaultFields.Description;
|
||||
5
src/utils/getTitleField.ts
Normal file
5
src/utils/getTitleField.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DefaultFields, SETTING_SEO_TITLE_FIELD } from '../constants';
|
||||
import { Settings } from '../helpers';
|
||||
|
||||
export const getTitleField = () =>
|
||||
Settings.get<string>(SETTING_SEO_TITLE_FIELD) || DefaultFields.Title;
|
||||
@@ -5,8 +5,10 @@ export * from './existsAsync';
|
||||
export * from './fetchWithTimeout';
|
||||
export * from './fieldWhenClause';
|
||||
export * from './flattenObjectKeys';
|
||||
export * from './getDescriptionField';
|
||||
export * from './getExtensibilityScripts';
|
||||
export * from './getLocalizationFile';
|
||||
export * from './getTitleField';
|
||||
export * from './ignoreMsgCommand';
|
||||
export * from './isWindows';
|
||||
export * from './joinUrl';
|
||||
|
||||
Reference in New Issue
Block a user