mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-06 18:01:24 +02:00
#332 - Adding the new fileData field
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { DataFile } from "../models";
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Logger } from "./Logger";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { commands } from "vscode";
|
||||
import { COMMAND_NAME, SETTING_DATA_FILES } from "../constants";
|
||||
import { Settings } from "./SettingsHelper";
|
||||
|
||||
|
||||
export class DataFileHelper {
|
||||
|
||||
/**
|
||||
* Retrieve the file data
|
||||
* @param filePath
|
||||
* @returns
|
||||
*/
|
||||
public static get(filePath: string) {
|
||||
const absPath = Folders.getAbsFilePath(filePath);
|
||||
if (existsSync(absPath)) {
|
||||
return readFileSync(absPath, 'utf8');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get by the id of the data file
|
||||
* @param id
|
||||
*/
|
||||
public static getById(id: string) {
|
||||
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files.find(f => f.id === id);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
return DataFileHelper.process(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the data file
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public static async process(data: DataFile) {
|
||||
try {
|
||||
const { file, fileType } = data;
|
||||
const dataFile = DataFileHelper.get(file);
|
||||
|
||||
if (fileType === "yaml") {
|
||||
return yaml.safeLoad(dataFile || "");
|
||||
} else {
|
||||
return dataFile ? JSON.parse(dataFile) : [];
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger.error(`DataFileHelper::process: ${(ex as Error).message}`);
|
||||
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
|
||||
|
||||
if (btnClick && btnClick === 'Open output') {
|
||||
commands.executeCommand(COMMAND_NAME.showOutputChannel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export * from './ArticleHelper';
|
||||
export * from './ContentType';
|
||||
export * from './CustomScript';
|
||||
export * from './DashboardSettings';
|
||||
export * from './DataFileHelper';
|
||||
export * from './DateHelper';
|
||||
export * from './Extension';
|
||||
export * from './FilesHelper';
|
||||
@@ -11,13 +12,15 @@ export * from './ImageHelper';
|
||||
export * from './Logger';
|
||||
export * from './MediaHelpers';
|
||||
export * from './MediaLibrary';
|
||||
export * from './MessageHelper';
|
||||
export * from './Notifications';
|
||||
export * from './PanelSettings';
|
||||
export * from './PlaceholderHelper';
|
||||
export * from './Questions';
|
||||
export * from './Sanitize';
|
||||
export * from './SeoHelper';
|
||||
export * from './SettingsHelper';
|
||||
export * from './SlugHelper';
|
||||
export * from './SnippetParser';
|
||||
export * from './Sorting';
|
||||
export * from './StringHelpers';
|
||||
export * from './Telemetry';
|
||||
|
||||
@@ -3,11 +3,10 @@ import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { Folders } from '../../commands/Folders';
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Logger, Notifications } from '../../helpers';
|
||||
import { commands } from 'vscode';
|
||||
import { DataFileHelper } from '../../helpers';
|
||||
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
@@ -57,38 +56,7 @@ export class DataListener extends BaseListener {
|
||||
* @param msgData
|
||||
*/
|
||||
private static async processDataFile(msgData: DataFile) {
|
||||
try {
|
||||
const { file } = msgData;
|
||||
const dataFile = this.getDataFile(file);
|
||||
|
||||
if (msgData.fileType === "yaml") {
|
||||
const entries = yaml.safeLoad(dataFile || "");
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, entries);
|
||||
} else {
|
||||
const jsonData = dataFile ? JSON.parse(dataFile) : [];
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, jsonData);
|
||||
}
|
||||
} catch (ex) {
|
||||
Logger.error(`DataListener::processDataFile: ${(ex as Error).message}`);
|
||||
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
|
||||
|
||||
if (btnClick && btnClick === 'Open output') {
|
||||
commands.executeCommand(`workbench.panel.output.focus`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file data
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
private static getDataFile(file: string) {
|
||||
const absPath = Folders.getAbsFilePath(file);
|
||||
if (existsSync(absPath)) {
|
||||
return readFileSync(absPath, 'utf8');
|
||||
}
|
||||
|
||||
return null;
|
||||
const entries = DataFileHelper.process(msgData);
|
||||
this.sendMsg(DashboardCommand.dataFileEntries, entries);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DataFileHelper } from './../../helpers/DataFileHelper';
|
||||
import { BlockFieldData } from './../../models/BlockFieldData';
|
||||
import { ImageHelper } from './../../helpers/ImageHelper';
|
||||
import { Folders } from "../../commands/Folders";
|
||||
@@ -45,10 +46,16 @@ export class DataListener extends BaseListener {
|
||||
break;
|
||||
case CommandToCode.generateContentType:
|
||||
commands.executeCommand(COMMAND_NAME.generateContentType);
|
||||
break;
|
||||
case CommandToCode.addMissingFields:
|
||||
commands.executeCommand(COMMAND_NAME.addMissingFields);
|
||||
break;
|
||||
case CommandToCode.setContentType:
|
||||
commands.executeCommand(COMMAND_NAME.setContentType);
|
||||
break;
|
||||
case CommandToCode.getDataEntries:
|
||||
this.getDataFileEntries(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +108,7 @@ export class DataListener extends BaseListener {
|
||||
// Get the current content type
|
||||
const contentType = ArticleHelper.getContentType(updatedMetadata);
|
||||
if (contentType) {
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields)
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +285,17 @@ export class DataListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data entries from local data files
|
||||
* @param data
|
||||
*/
|
||||
private static async getDataFileEntries(data: any) {
|
||||
const entries = await DataFileHelper.getById(data);
|
||||
if (entries) {
|
||||
this.sendMsg(Command.dataFileEntries, entries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a terminal and run the passed command
|
||||
* @param command
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface ContentType {
|
||||
pageBundle?: boolean;
|
||||
}
|
||||
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file";
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile";
|
||||
|
||||
export interface Field {
|
||||
title?: string;
|
||||
@@ -71,6 +71,11 @@ export interface Field {
|
||||
// Date fields
|
||||
isPublishDate?: boolean;
|
||||
isModifiedDate?: boolean;
|
||||
|
||||
// Data file
|
||||
dataFileId?: string;
|
||||
dataFileKey?: string;
|
||||
dataFileValue?: string;
|
||||
}
|
||||
|
||||
export interface DateInfo {
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum Command {
|
||||
mediaSelectionData = "mediaSelectionData",
|
||||
sendMediaUrl = "sendMediaUrl",
|
||||
updatePlaceholder = "updatePlaceholder",
|
||||
dataFileEntries = "dataFileEntries",
|
||||
}
|
||||
@@ -37,4 +37,5 @@ export enum CommandToCode {
|
||||
generateContentType = "generate-content-type",
|
||||
addMissingFields = "add-missing-fields",
|
||||
setContentType = "set-content-type",
|
||||
getDataEntries = "get-data-entries",
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import { ChevronDownIcon, DatabaseIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Command } from '../../Command';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import Downshift from 'downshift';
|
||||
import { ChoiceButton } from './ChoiceButton';
|
||||
|
||||
export interface IDataFileFieldProps {
|
||||
label: string;
|
||||
dataFileId?: string;
|
||||
dataFileKey?: string;
|
||||
dataFileValue?: string;
|
||||
selected: string | string[];
|
||||
multiSelect?: boolean;
|
||||
onChange: (value: string | string[]) => void;
|
||||
}
|
||||
|
||||
export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({ label, dataFileId, dataFileKey, dataFileValue, selected, multiSelect, onChange }: React.PropsWithChildren<IDataFileFieldProps>) => {
|
||||
const [ dataEntries, setDataEntries ] = useState<string[] | null>(null);
|
||||
const [ crntSelected, setCrntSelected ] = React.useState<string | string[] | null>();
|
||||
const dsRef = React.useRef<Downshift<string> | null>(null);
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<any>>) => {
|
||||
const { command, data } = message.data;
|
||||
|
||||
if (command === Command.dataFileEntries) {
|
||||
setDataEntries(data || null);
|
||||
}
|
||||
};
|
||||
|
||||
const onValueChange = useCallback((txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || []) as string[], txtValue];
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected(txtValue);
|
||||
onChange(txtValue);
|
||||
}
|
||||
}, [crntSelected, multiSelect, onChange]);
|
||||
|
||||
const removeSelected = useCallback((txtValue: string) => {
|
||||
if (multiSelect) {
|
||||
const newValue = [...(crntSelected || [])].filter(v => v !== txtValue);
|
||||
setCrntSelected(newValue);
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setCrntSelected("");
|
||||
onChange("");
|
||||
}
|
||||
}, [crntSelected, multiSelect, onChange]);
|
||||
|
||||
const allChoices = useMemo(() => {
|
||||
if (dataEntries && dataFileKey) {
|
||||
return dataEntries.map((r: any) => ({
|
||||
id: r[dataFileKey],
|
||||
title: r[dataFileValue || dataFileKey] || r[dataFileKey]
|
||||
})).filter(r => r.id);
|
||||
}
|
||||
return [];
|
||||
}, [crntSelected, dataEntries, dataFileKey, dataFileValue]);
|
||||
|
||||
const availableChoices = useMemo(() => {
|
||||
if (allChoices) {
|
||||
return allChoices.filter(choice => {
|
||||
if (choice) {
|
||||
if (typeof crntSelected === 'string') {
|
||||
return crntSelected !== choice.id;
|
||||
} else if (crntSelected instanceof Array) {
|
||||
return crntSelected.indexOf(choice.id) === -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}, [allChoices]);
|
||||
|
||||
const getChoiceValue = useCallback((id: string) => {
|
||||
const choice = allChoices.find(r => r.id === id);
|
||||
if (choice) {
|
||||
return choice.title;
|
||||
}
|
||||
return "";
|
||||
}, [allChoices]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
if (multiSelect) {
|
||||
setCrntSelected(typeof selected === 'string' ? [selected] : selected);
|
||||
return;
|
||||
} else {
|
||||
setCrntSelected(selected instanceof Array ? selected[0] : selected);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setCrntSelected(multiSelect ? [] : "");
|
||||
}, [selected, multiSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataFileId) {
|
||||
Messenger.send(CommandToCode.getDataEntries, dataFileId);
|
||||
}
|
||||
}, [dataFileId]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(messageListener);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<DatabaseIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
<Downshift
|
||||
ref={dsRef}
|
||||
onSelect={(selected) => onValueChange(selected || "")}
|
||||
itemToString={item => (item ? item : '')}>
|
||||
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
|
||||
<div {...getRootProps(undefined, {suppressRefError: true})} className={`metadata_field__choice`}>
|
||||
<button
|
||||
{...getToggleButtonProps({
|
||||
className: `metadata_field__choice__toggle`,
|
||||
disabled: availableChoices.length === 0
|
||||
})}>
|
||||
<span>{`Select ${label}`}</span>
|
||||
<ChevronDownIcon className="icon" />
|
||||
</button>
|
||||
|
||||
<ul className={`metadata_field__choice_list ${isOpen ? "open" : "closed" }`} {...getMenuProps()}>
|
||||
{
|
||||
isOpen ? availableChoices.map((choice, index) => (
|
||||
<li {...getItemProps({
|
||||
key: choice.id,
|
||||
index,
|
||||
item: choice.id,
|
||||
})}>
|
||||
{ choice.title || <span className={`metadata_field__choice_list__item`}>Clear value</span> }
|
||||
</li>
|
||||
)) : null
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</Downshift>
|
||||
|
||||
{
|
||||
crntSelected instanceof Array ? crntSelected.map((value: string) => (
|
||||
<ChoiceButton
|
||||
key={value}
|
||||
value={value}
|
||||
title={getChoiceValue(value)}
|
||||
onClick={removeSelected} />
|
||||
)) : (
|
||||
crntSelected && (
|
||||
<ChoiceButton
|
||||
key={crntSelected}
|
||||
value={crntSelected}
|
||||
title={getChoiceValue(crntSelected)}
|
||||
onClick={removeSelected} />
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DateHelper } from '../../../helpers/DateHelper';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { BlockFieldData, Field, PanelSettings } from '../../../models';
|
||||
import { Command } from '../../Command';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
@@ -17,6 +17,7 @@ import { IMetadata } from '../Metadata';
|
||||
import { TagPicker } from '../TagPicker';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import { DataFileField } from './DataFileField';
|
||||
import { DateTimeField } from './DateTimeField';
|
||||
import { DraftField } from './DraftField';
|
||||
import { FileField } from './FileField';
|
||||
@@ -98,7 +99,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
// Check if the field value contains a placeholder
|
||||
if (value && typeof value === "string" && value.includes(`{{`) && value.includes(`}}`)) {
|
||||
window.addEventListener('message', listener);
|
||||
MessageHelper.sendMessage(CommandToCode.updatePlaceholder, {
|
||||
Messenger.send(CommandToCode.updatePlaceholder, {
|
||||
field: field.name,
|
||||
title: metadata["title"],
|
||||
value
|
||||
@@ -346,6 +347,19 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
onSubmit={(value) => onSendUpdate(field.name, value, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'dataFile') {
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<DataFileField
|
||||
label={field.title || field.name}
|
||||
dataFileId={field.dataFileId}
|
||||
dataFileKey={field.dataFileKey}
|
||||
dataFileValue={field.dataFileValue}
|
||||
selected={fieldValue as string}
|
||||
multiSelect={field.multiple}
|
||||
onChange={(value => onSendUpdate(field.name, value, parentFields))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else {
|
||||
console.warn(`Unknown field type: ${field.type}`);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user