import { ModeListener } from './../listeners/general/ModeListener'; import { PagesListener } from './../listeners/dashboard'; import { ArticleHelper, CustomScript, Extension, Logger, Settings, processArticlePlaceholdersFromData, processTimePlaceholders } from '.'; import { COMMAND_NAME, DefaultFieldValues, DefaultFields, EXTENSION_NAME, FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_FRAMEWORK_ID, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_FIELD_GROUPS } from '../constants'; import { ContentType as IContentType, DraftField, Field, FieldGroup, FieldType, ScriptType } from '../models'; import { Uri, commands, window, ProgressLocation, workspace } from 'vscode'; import { Folders } from '../commands/Folders'; import { Questions } from './Questions'; import { Notifications } from './Notifications'; import { DEFAULT_CONTENT_TYPE_NAME } from '../constants/ContentType'; import { basename } from 'path'; import { ParsedFrontMatter } from '../parsers'; import { encodeEmoji, existsAsync, fieldWhenClause, getTitleField, writeFileAsync } from '../utils'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; export class ContentType { /** * Registers the commands related to content types. * * @param subscriptions - The array of subscriptions to which the commands will be added. */ public static async registerCommands() { const ext = Extension.getInstance(); const subscriptions = ext.subscriptions; subscriptions.push( commands.registerCommand(COMMAND_NAME.createByContentType, ContentType.createContent) ); subscriptions.push( commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate) ); subscriptions.push( commands.registerCommand(COMMAND_NAME.addMissingFields, ContentType.addMissingFields) ); subscriptions.push( commands.registerCommand(COMMAND_NAME.setContentType, ContentType.setContentType) ); } /** * Retrieve the draft field * @returns */ public static getDraftField() { const draftField = Settings.get(SETTING_CONTENT_DRAFT_FIELD); if (draftField) { return draftField; } return null; } /** * Retrieve the field its status * @param data * @returns */ public static async getDraftStatus(article: ParsedFrontMatter) { const contentType = await ArticleHelper.getContentType(article); const draftSetting = ContentType.getDraftField(); const draftField = contentType.fields.find((f) => f.type === 'draft'); let fieldValue = null; if (draftField && article?.data) { fieldValue = article?.data[draftField.name]; } else if (draftSetting && article?.data && article?.data[draftSetting.name]) { fieldValue = article?.data[draftSetting.name]; } if (draftSetting && fieldValue !== null) { if (draftSetting.type === 'boolean') { return fieldValue ? 'Draft' : 'Published'; } else { return fieldValue; } } return null; } /** * Create content based on content types * @returns */ public static async createContent() { const selectedFolder = await Questions.SelectContentFolder(); if (!selectedFolder) { return; } const contentTypes = ContentType.getAll(); let folders = await Folders.get(); folders = folders.filter((f) => !f.disableCreation); const folder = folders.find((f) => f.path === selectedFolder.path); if (!folder) { return; } const selectedContentType = await Questions.SelectContentType(folder.contentTypes || []); if (!selectedContentType) { return; } if (contentTypes && folder) { const folderPath = Folders.getFolderPath(Uri.file(folder.path)); const contentType = contentTypes.find((ct) => ct.name === selectedContentType); if (folderPath && contentType) { ContentType.create(contentType, folderPath); } } } /** * Retrieve all content types * @returns */ public static getAll() { const cts = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES); for (const ct of cts || []) { ct.fields = ContentType.mergeFields(ct.fields || []); } return cts; } /** * Merge the collection fields * @param contentType * @returns */ public static mergeFields(fields: Field[]) { if (!fields) { return []; } // Check if there is a field collection const fcFields = ContentType.findAllFieldsByType(fields || [], 'fieldCollection'); if (fcFields.length > 0) { const fieldGroups = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS); if (fieldGroups && fieldGroups.length > 0) { for (const cField of fcFields) { for (const fieldName of cField) { const field = fields.find((f) => f.name === fieldName); if (field && field.type === 'fieldCollection') { const fieldGroup = fieldGroups.find((fg) => fg.id === field.fieldGroup); if (fieldGroup) { const fieldIdx = fields.findIndex((f) => f.name === field.name); fields.splice(fieldIdx, 1, ...fieldGroup.fields); } } else if (field && field.type === 'fields') { field.fields = field.fields || []; } } } } } return fields; } /** * Generate a content type */ public static async generate() { if (!(await ContentType.verify())) { return; } const content = ArticleHelper.getCurrent(); const editor = window.activeTextEditor; const filePath = editor?.document.uri.fsPath; if (!content || !content.data) { Notifications.warning(l10n.t(LocalizationKey.helpersContentTypeGenerateNoFrontMatterError)); return; } const override = await window.showQuickPick( [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], { title: l10n.t(LocalizationKey.helpersContentTypeGenerateOverrideQuickPickTitle), placeHolder: l10n.t(LocalizationKey.helpersContentTypeGenerateOverrideQuickPickPlaceholder), ignoreFocusOut: true } ); const overrideBool = override === l10n.t(LocalizationKey.commonYes); let contentTypeName: string | undefined = `default`; // Ask for the new content type name if (!overrideBool) { contentTypeName = await window.showInputBox({ title: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputTitle), placeHolder: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputPrompt), prompt: l10n.t(LocalizationKey.helpersContentTypeGenerateContentTypeInputPrompt), ignoreFocusOut: true, validateInput: (value: string) => { if (!value) { return l10n.t( LocalizationKey.helpersContentTypeGenerateContentTypeInputValidationEnterName ); } const contentTypes = ContentType.getAll(); if ( contentTypes && contentTypes.find((ct) => ct.name.toLowerCase() === value.toLowerCase()) ) { return l10n.t( LocalizationKey.helpersContentTypeGenerateContentTypeInputValidationNameExists ); } return null; } }); if (!contentTypeName) { Notifications.warning( l10n.t(LocalizationKey.helpersContentTypeGenerateNoContentTypeNameWarning) ); return; } } // Ask if the content type needs to be used as a page bundle let pageBundle = false; const fileName = filePath ? basename(filePath) : undefined; if (fileName?.startsWith(`index.`)) { const pageBundleAnswer = await window.showQuickPick( [l10n.t(LocalizationKey.commonYes), l10n.t(LocalizationKey.commonNo)], { title: l10n.t(LocalizationKey.helpersContentTypeGeneratePageBundleQuickPickTitle), placeHolder: l10n.t( LocalizationKey.helpersContentTypeGeneratePageBundleQuickPickPlaceHolder ), ignoreFocusOut: true } ); pageBundle = pageBundleAnswer === l10n.t(LocalizationKey.commonYes); } const fields = await ContentType.generateFields(content.data); // Update the type field in the page if (!overrideBool && editor) { content.data[DefaultFields.ContentType] = contentTypeName; ArticleHelper.update(editor, content); } const newContentType: IContentType = { name: contentTypeName, pageBundle, fields }; const contentTypes = ContentType.getAll() || []; if (overrideBool) { const index = contentTypes.findIndex((ct) => ct.name === contentTypeName); contentTypes[index].fields = fields; } else { contentTypes.push(newContentType); } await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( overrideBool ? l10n.t(LocalizationKey.helpersContentTypeGenerateUpdatedSuccess, contentTypeName) : l10n.t(LocalizationKey.helpersContentTypeGenerateGeneratedSuccess, contentTypeName), configPath && (await existsAsync(configPath)) ? l10n.t(LocalizationKey.commonOpenSettings) : undefined ); if ( notificationAction === l10n.t(LocalizationKey.commonOpenSettings) && configPath && (await existsAsync(configPath)) ) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } /** * Add missing fields to the content type */ public static async addMissingFields() { if (!(await ContentType.verify())) { return; } const article = ArticleHelper.getCurrent(); if (!article || !article.data) { Notifications.warning( l10n.t(LocalizationKey.helpersContentTypeAddMissingFieldsNoFrontMatterWarning) ); return; } const contentType = await ArticleHelper.getContentType(article); const updatedFields = await ContentType.generateFields(article.data, contentType.fields); const contentTypes = ContentType.getAll() || []; const index = contentTypes.findIndex((ct) => ct.name === contentType.name); contentTypes[index].fields = updatedFields; await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( l10n.t(LocalizationKey.helpersContentTypeAddMissingFieldsUpdatedSuccess, contentType.name), configPath && (await existsAsync(configPath)) ? l10n.t(LocalizationKey.commonOpenSettings) : undefined ); if ( notificationAction === l10n.t(LocalizationKey.commonOpenSettings) && configPath && (await existsAsync(configPath)) ) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } /** * Set the content type to be used for the current file */ public static async setContentType() { if (!(await ContentType.verify())) { return; } const content = ArticleHelper.getCurrent(); const contentTypes = ContentType.getAll() || []; if (!content || !content.data) { Notifications.warning( l10n.t(LocalizationKey.helpersContentTypeSetContentTypeNoFrontMatterWarning) ); return; } const ctAnswer = await window.showQuickPick( contentTypes.map((ct) => ct.name), { title: l10n.t(LocalizationKey.helpersContentTypeSetContentTypeQuickPickTitle), placeHolder: l10n.t(LocalizationKey.helpersContentTypeSetContentTypeQuickPickPlaceholder), ignoreFocusOut: true } ); if (!ctAnswer) { return; } content.data[DefaultFields.ContentType] = ctAnswer; const editor = window.activeTextEditor; ArticleHelper.update(editor!, content); } /** * Retrieve the field value * @param data * @param parents * @returns */ public static getFieldValue(data: any, parents: string[]): any { let fieldValue = []; let crntPageData = data; for (let i = 0; i < parents.length; i++) { const crntField = parents[i]; if (i === parents.length - 1) { fieldValue = crntPageData[crntField]; } else { if (!crntPageData[crntField]) { continue; } crntPageData = crntPageData[crntField]; } } return fieldValue; } /** * Set the field value * @param data * @param parents * @returns */ public static setFieldValue(data: any, parents: string[], value: any) { let crntPageData = data; for (let i = 0; i < parents.length; i++) { const crntField = parents[i]; if (i === parents.length - 1) { crntPageData[crntField] = value; } else { if (!crntPageData[crntField]) { continue; } crntPageData = crntPageData[crntField]; } } return data; } /** * Find the field by its name * @param fields * @param name * @returns */ public static findFieldByName(fields: Field[], name: string): Field | undefined { for (const field of fields) { if (field.name === name) { return field; } else if (field.type === 'fields' && field.fields) { const subField = this.findFieldByName(field.fields, name); if (subField) { return subField; } } } } /** * Find the field by its type * @param fields * @param type * @param parents * @returns */ public static findFieldByType( fields: Field[], type: FieldType, parents: string[] = [] ): string[] { for (const field of fields) { if (field.type === type) { parents = [...parents, field.name]; return parents; } else if (field.type === 'fields' && field.fields) { const subFields = this.findFieldByType(field.fields, type, parents); if (subFields.length > 0) { return [...parents, field.name, ...subFields]; } } } return parents; } /** * Find all the fields by type * @param fields * @param type * @returns */ public static findAllFieldsByType(fields: Field[], type: FieldType): string[][] { let parents: string[][] = []; for (const field of fields) { if (field.type === type) { parents.push([field.name]); } else if (field.type === 'fields' && field.fields) { const subFields = this.findAllFieldsByType(field.fields, type); if (subFields.length > 0) { parents = [...parents, ...subFields.map((f) => [field.name, ...f])]; } } } return parents; } /** * Find the preview field in the fields * @param ctFields * @param parents * @returns */ public static findPreviewField(ctFields: Field[], parents: string[] = []): string[] { for (const field of ctFields) { if (field.isPreviewImage && field.type === 'image') { parents = [...parents, field.name]; return parents; } else if (field.type === 'fields' && field.fields) { const subFields = this.findPreviewField(field.fields); if (subFields.length > 0) { return [...parents, field.name, ...subFields]; } } else if (field.type === 'block') { const subFields = this.findPreviewInBlockField(field); if (subFields.length > 0) { return [...parents, field.name, ...subFields]; } } } return parents; } /** * Find the required fields */ public static async findEmptyRequiredFields( article: ParsedFrontMatter ): Promise { const contentType = await ArticleHelper.getContentType(article); if (!contentType) { return; } const allRequiredFields = ContentType.findRequiredFieldsDeep(contentType.fields); const emptyFields: Field[][] = []; for (const fields of allRequiredFields) { const fieldValue = this.getFieldValue( article.data, fields.map((f) => f.name) ); if ( fieldValue === null || fieldValue === undefined || fieldValue === '' || (Array.isArray(fieldValue) && fieldValue.length === 0) || (typeof fieldValue === 'string' && fieldValue.length === 0) || fieldValue === DefaultFieldValues.faultyCustomPlaceholder ) { emptyFields.push(fields); } } return emptyFields || []; } /** * Find fields by type (deep search) * @param fields * @param type * @param parents * @param parentFields * @returns */ public static findFieldsByTypeDeep( fields: Field[], type: FieldType, parents: Field[][] = [], parentFields: Field[] = [] ): Field[][] { for (const field of fields) { if (field.type === type) { parents.push([...parentFields, field]); } else if (field.type === 'fields' && field.fields) { this.findFieldsByTypeDeep(field.fields, type, parents, [...parentFields, field]); } else if (field.type === 'block') { const groups = field.fieldGroup && Array.isArray(field.fieldGroup) ? field.fieldGroup : [field.fieldGroup]; const blocks = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS); if (groups && blocks) { let found = false; for (const group of groups) { const block = blocks.find((block) => block.id === group); if (!block) { continue; } let newParents: Field[][] = []; if (!found) { newParents = this.findFieldsByTypeDeep(block?.fields, type, parents, [ ...parentFields, field ]); } if (newParents.length > 0) { found = true; } } } } } return parents; } /** * Retrieve the block field groups * @param field * @returns */ public static getBlockFieldGroups(field: Field): FieldGroup[] { const groups = field.fieldGroup && Array.isArray(field.fieldGroup) ? field.fieldGroup : [field.fieldGroup]; if (!groups) { return []; } const blocks = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS); if (!blocks) { return []; } const foundBlocks = []; for (const group of groups) { const block = blocks.find((block) => block.id === group); if (!block) { continue; } foundBlocks.push(block); } return foundBlocks; } /** * Find all the required fields in the content type * @param fields * @param parents * @returns */ private static findRequiredFieldsDeep( fields: Field[], parents: Field[][] = [], parentFields: Field[] = [] ): Field[][] { for (const field of fields) { if (field.required) { parents.push([...parentFields, field]); } if (field.type === 'fields' && field.fields) { this.findRequiredFieldsDeep(field.fields, parents, [...parentFields, field]); } } return parents; } /** * Look for the preview image in the block field * @param field * @param parents * @returns */ private static findPreviewInBlockField(field: Field) { const groups = field.fieldGroup && Array.isArray(field.fieldGroup) ? field.fieldGroup : [field.fieldGroup]; if (!groups) { return []; } const blocks = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS); if (!blocks) { return []; } let found = false; for (const group of groups) { const block = blocks.find((block) => block.id === group); if (!block) { continue; } let newParents: string[] = []; if (!found) { newParents = this.findPreviewField(block?.fields, []); } if (newParents.length > 0) { found = true; return newParents; } } return []; } /** * Generate the fields from the data * @param data * @param fields * @returns */ private static async generateFields(data: any, fields: any[] = []) { for (const field in data) { const fieldData = data[field]; if (fields.some((f) => f.name === field)) { continue; } if (field.toLowerCase() === 'tag' || field.toLowerCase() === 'tags') { fields.push({ title: field, name: field, type: 'tags' } as Field); } else if (field.toLowerCase() === 'category' || field.toLowerCase() === 'categories') { fields.push({ title: field, name: field, type: 'categories' } as Field); } else if ( fieldData && fieldData instanceof Array && fieldData.length > 0 && typeof fieldData[0] === 'string' ) { fields.push({ title: field, name: field, type: 'choice', choices: fieldData } as Field); } else if ( fieldData && fieldData instanceof Array && fieldData.length > 0 && typeof fieldData[0] === 'object' ) { const newFields = await ContentType.generateFields(fieldData); // Combine all the fields for the field group const blockFields: Field[] = []; for (const newField of newFields) { for (const field of newField.fields) { if (blockFields.some((f) => f.name === field.name)) { continue; } blockFields.push(field); } } // Generate a new field group const fieldGroups = Settings.get(SETTING_TAXONOMY_FIELD_GROUPS) || []; const fieldGroupName = `${field}_group`; const newFieldGroup: FieldGroup = { id: fieldGroupName, fields: blockFields }; fieldGroups.push(newFieldGroup); await Settings.safeUpdate(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true); fields.push({ title: field, name: field, type: 'block', fieldGroup: [fieldGroupName] } as Field); } else if (fieldData && fieldData instanceof Object) { const newFields = await ContentType.generateFields(fieldData); fields.push({ title: field, name: field, type: 'fields', fields: newFields } as Field); } else { if (field.toLowerCase().includes('image')) { fields.push({ title: field, name: field, type: 'image' } as Field); } else if (typeof fieldData === 'number') { fields.push({ title: field, name: field, type: 'number' } as Field); } else if (typeof fieldData === 'boolean') { if (field.toLowerCase() === 'draft') { fields.push({ title: field, name: field, type: 'draft' } as Field); } else { fields.push({ title: field, name: field, type: 'boolean' } as Field); } } else if (!isNaN(new Date(fieldData).getDate())) { fields.push({ title: field, name: field, type: 'datetime' } as Field); } else if (field.toLowerCase() === 'draft') { fields.push({ title: field, name: field, type: 'draft' } as Field); } else if (field.toLowerCase() === 'slug') { // Do nothing } else { fields.push({ title: field, name: field, type: typeof fieldData } as Field); } } } return fields; } /** * Create a new file with the specified content type * @param contentType * @param folderPath * @returns */ private static async create(contentType: IContentType, folderPath: string) { window.withProgress( { location: ProgressLocation.Notification, title: l10n.t(LocalizationKey.helpersContentTypeCreateProgressTitle, EXTENSION_NAME), cancellable: false }, async () => { if (contentType.isSubContent || contentType.allowAsSubContent) { let showDialog = true; if (contentType.allowAsSubContent) { const subContentAnswer = await window.showQuickPick( [l10n.t(LocalizationKey.commonNo), l10n.t(LocalizationKey.commonYes)], { title: l10n.t(LocalizationKey.helpersContentTypeCreateAllowSubContentTitle), placeHolder: l10n.t( LocalizationKey.helpersContentTypeCreateAllowSubContentPlaceHolder ), ignoreFocusOut: true } ); showDialog = subContentAnswer === l10n.t(LocalizationKey.commonYes); } if (showDialog) { const folderLocation = await window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: Uri.file(folderPath), openLabel: l10n.t( LocalizationKey.helpersContentTypeCreateAllowSubContentShowOpenDialogOpenLabel ), title: l10n.t( LocalizationKey.helpersContentTypeCreateAllowSubContentShowOpenDialogTitle ) }); if (!folderLocation || folderLocation.length === 0) { return; } folderPath = folderLocation[0].fsPath; if (contentType.pageBundle) { const createAsPageBundle = await window.showQuickPick( [l10n.t(LocalizationKey.commonNo), l10n.t(LocalizationKey.commonYes)], { title: l10n.t(LocalizationKey.helpersContentTypeCreatePageBundleTitle), placeHolder: l10n.t( LocalizationKey.helpersContentTypeCreatePageBundlePlaceHolder ), ignoreFocusOut: true } ); if (createAsPageBundle === l10n.t(LocalizationKey.commonNo)) { contentType.pageBundle = false; } } } } let titleValue = await Questions.ContentTitle(); if (!titleValue) { return; } 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 === titleFieldName); if (titleField && titleField.encodeEmoji) { titleValue = encodeEmoji(titleValue); } let templatePath = contentType.template; let templateData: ParsedFrontMatter | null | undefined = null; if (templatePath) { try { templatePath = Folders.getAbsFilePath(templatePath); templateData = await ArticleHelper.getFrontMatterByPath(templatePath); if (!templateData) { Logger.warning( `ContentType.create: Template file not found or could not be parsed: ${templatePath}` ); Notifications.warning( l10n.t(LocalizationKey.commonError) + ` Template not found: ${templatePath}` ); } } catch (error) { Logger.error( `ContentType.create: Error loading template from ${templatePath}: ${error}` ); Notifications.error( l10n.t(LocalizationKey.commonError) + ` Template loading failed: ${templatePath}` ); } } const newFilePath: string | undefined = await ArticleHelper.createContent( contentType, folderPath, titleValue ); if (!newFilePath) { return; } if (contentType.name === 'default') { const crntFramework = Settings.get(SETTING_FRAMEWORK_ID); if (crntFramework?.toLowerCase() === 'jekyll') { const idx = contentType.fields.findIndex((f) => f.name === 'draft'); if (idx > -1) { contentType.fields.splice(idx, 1); } } } let data: any = await this.processFields( contentType, titleValue, templateData?.data || {}, newFilePath, !!contentType.clearEmpty, contentType ); // Set the content type data[DefaultFields.ContentType] = contentType.name; const article: ParsedFrontMatter = { content: '', data: Object.assign({}, data), path: newFilePath }; data = await ArticleHelper.updateDates(article); if (contentType.name !== DEFAULT_CONTENT_TYPE_NAME) { data[DefaultFields.ContentType] = contentType.name; } else { // Default content type, remove the content type field delete data[DefaultFields.ContentType]; } const content = ArticleHelper.stringifyFrontMatter(templateData?.content || ``, data); await writeFileAsync(newFilePath, content, { encoding: 'utf8' }); // Check if the content type has a post script to execute if (contentType.postScript) { const scripts = await CustomScript.getScripts(); const script = scripts.find((s) => s.id === contentType.postScript); if (script && (script.type === ScriptType.Content || !script?.type)) { await CustomScript.run(script, newFilePath); const doc = await workspace.openTextDocument(Uri.file(newFilePath)); await doc.save(); } } await commands.executeCommand('vscode.open', Uri.file(newFilePath)); Notifications.info(l10n.t(LocalizationKey.helpersContentTypeCreateSuccess)); // Trigger a refresh for the dashboard PagesListener.refresh(); } ); } /** * Process all content type fields * @param contentType * @param data */ private static async processFields( obj: IContentType | Field, titleValue: string, data: any, filePath: string, clearEmpty: boolean, contentType: IContentType, isRoot = true ): Promise { if (obj.fields) { const titleField = getTitleField(); const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string; for (const field of obj.fields) { if (!fieldWhenClause(field, data, obj.fields)) { Logger.info(`Field ${field.name} not added because of when clause`); continue; } if (field.name === titleField) { if (field.default) { data[field.name] = processArticlePlaceholdersFromData( field.default as string, data, contentType, filePath ); data[field.name] = processTimePlaceholders( data[field.name], field.dateFormat || dateFormat ); data[field.name] = await ArticleHelper.processCustomPlaceholders( data[field.name], titleValue, filePath ); } else if (isRoot) { data[field.name] = titleValue; } else if (!clearEmpty) { data[field.name] = ''; } } else { if (field.type === 'fields') { data[field.name] = await this.processFields( field, titleValue, {}, filePath, clearEmpty, contentType, false ); if (clearEmpty && Object.keys(data[field.name]).length === 0) { delete data[field.name]; } } else { const defaultValue = field.default; if (typeof defaultValue === 'string') { data[field.name] = await ContentType.processFieldPlaceholders( defaultValue, data, contentType, field.dateFormat || dateFormat, titleValue, filePath ); } else if (defaultValue && Array.isArray(defaultValue)) { const defaultValues = []; for (let value of defaultValue as string[]) { if (typeof value === 'string') { value = await ContentType.processFieldPlaceholders( value, data, contentType, field.dateFormat || dateFormat, titleValue, filePath ); } defaultValues.push(value); } data[field.name] = defaultValues; } else if (typeof defaultValue !== 'undefined') { data[field.name] = defaultValue; } else { const draftField = ContentType.getDraftField(); if ( field.type === 'draft' && (draftField?.type === 'boolean' || draftField?.type === undefined) ) { data[field.name] = true; } else { // Check the field types switch (field.type) { case 'choice': if (field.multiple) { data[field.name] = []; } else if (!clearEmpty) { data[field.name] = ''; } break; case 'boolean': if (!clearEmpty) { data[field.name] = false; } break; case 'number': if (!clearEmpty) { data[field.name] = 0; } break; case 'datetime': if (!clearEmpty) { data[field.name] = null; } break; case 'list': case 'tags': case 'categories': case 'taxonomy': if (!clearEmpty) { data[field.name] = []; } break; case 'string': case 'slug': case 'image': case 'file': default: if (!clearEmpty) { data[field.name] = ''; } break; } } } } } } } return data; } /** * Processes the field placeholders in the given value. * * @param defaultValue - The default value for the field. * @param data - The data object containing the field values. * @param contentType - The content type object. * @param dateFormat - The date format string. * @param title - The title string. * @param filePath - The file path string. * @returns The processed value with field placeholders replaced. */ private static async processFieldPlaceholders( defaultValue: string, data: any, contentType: IContentType, dateFormat: string, title: string, filePath: string ) { let value = processArticlePlaceholdersFromData(defaultValue, data, contentType); value = processTimePlaceholders(value, dateFormat); value = await ArticleHelper.processCustomPlaceholders(value, title, filePath); return value; } /** * Verify if the content type feature is enabled * @returns */ private static async verify() { const hasFeature = await ModeListener.hasFeature(FEATURE_FLAG.panel.contentType); if (!hasFeature) { Notifications.warning(l10n.t(LocalizationKey.helpersContentTypeVerifyWarning)); return false; } return true; } }