mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-05-09 14:54:45 +02:00
194 lines
5.7 KiB
TypeScript
194 lines
5.7 KiB
TypeScript
import { Messenger } from '@estruyf/vscode/dist/client';
|
|
import * as React from 'react';
|
|
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
|
import { useRecoilValue } from 'recoil';
|
|
import { processKnownPlaceholders } from '../../../helpers/PlaceholderHelper';
|
|
import { SnippetParser } from '../../../helpers/SnippetParser';
|
|
import { Snippet, SnippetField, SnippetInfoField, SnippetSpecialPlaceholders } from '../../../models';
|
|
import { DashboardMessage } from '../../DashboardMessage';
|
|
import { SettingsAtom, ViewDataSelector } from '../../state';
|
|
import { SnippetInputField } from './SnippetInputField';
|
|
import { SNIPPET } from '../../../constants/Snippet';
|
|
|
|
export interface ISnippetFormProps {
|
|
snippetKey?: string;
|
|
snippet: Snippet;
|
|
selection: string | undefined;
|
|
fieldInfo?: SnippetInfoField[];
|
|
mediaData?: any;
|
|
onInsert?: (mediaData: any) => void;
|
|
}
|
|
|
|
export interface SnippetFormHandle {
|
|
onSave: () => void;
|
|
}
|
|
|
|
const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFormProps> = (
|
|
{ snippetKey, snippet, selection, fieldInfo, mediaData, onInsert },
|
|
ref
|
|
) => {
|
|
const viewData = useRecoilValue(ViewDataSelector);
|
|
const [fields, setFields] = useState<SnippetField[]>([]);
|
|
const settings = useRecoilValue(SettingsAtom);
|
|
|
|
const onTextChange = useCallback(
|
|
(field: SnippetField, value: string) => {
|
|
setFields((prevFields) =>
|
|
prevFields.map((f) => (f.name === field.name ? { ...f, value } : f))
|
|
);
|
|
},
|
|
[setFields]
|
|
);
|
|
|
|
const insertPlaceholderValues = useCallback(
|
|
(value: SnippetSpecialPlaceholders) => {
|
|
if (value === 'FM_SELECTED_TEXT') {
|
|
return selection || '';
|
|
}
|
|
|
|
value = processKnownPlaceholders(
|
|
value,
|
|
viewData?.data?.fileTitle || '',
|
|
settings?.date.format || ''
|
|
);
|
|
|
|
return value;
|
|
},
|
|
[selection]
|
|
);
|
|
|
|
const insertValueFromMedia = useCallback(
|
|
(fieldName: string) => {
|
|
if (!mediaData) {
|
|
return '';
|
|
}
|
|
|
|
if (mediaData[fieldName]) {
|
|
return mediaData[fieldName];
|
|
}
|
|
},
|
|
[mediaData]
|
|
);
|
|
|
|
const snippetBody = useMemo(() => {
|
|
let body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
|
|
|
const obj: any = {};
|
|
for (const field of fields) {
|
|
obj[field.name] = field.value;
|
|
}
|
|
|
|
return SnippetParser.render(body, obj, snippet.openingTags, snippet.closingTags);
|
|
}, [fields, snippet]);
|
|
|
|
const shouldShowField = (fieldName: string, idx: number, allFields: SnippetField[]) => {
|
|
const crntField = allFields.findIndex((f) => f.name === fieldName);
|
|
if (crntField < idx) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
onSave() {
|
|
if (!snippetBody) {
|
|
return;
|
|
}
|
|
|
|
const snippetInfo = {
|
|
id: snippetKey,
|
|
fields: fields.map(f => ({
|
|
name: f.name,
|
|
value: f.value
|
|
}))
|
|
}
|
|
|
|
if (!onInsert) {
|
|
if (!snippetKey || settings?.snippetsWrapper === false) {
|
|
Messenger.send(DashboardMessage.insertSnippet, {
|
|
file: viewData?.data?.filePath,
|
|
snippet: snippetBody
|
|
});
|
|
} else {
|
|
Messenger.send(DashboardMessage.insertSnippet, {
|
|
file: viewData?.data?.filePath,
|
|
range: viewData?.data?.range,
|
|
snippet: `<!-- ${SNIPPET.wrapper.start} data:${JSON.stringify(snippetInfo)} -->
|
|
${snippetBody}
|
|
<!-- ${SNIPPET.wrapper.end} -->`
|
|
});
|
|
}
|
|
} else {
|
|
onInsert(snippetBody);
|
|
}
|
|
}
|
|
}));
|
|
|
|
useEffect(() => {
|
|
// Get all placeholder variables from the snippet
|
|
const body = typeof snippet.body === 'string' ? snippet.body : snippet.body.join(`\n`);
|
|
|
|
const placeholders = SnippetParser.getPlaceholders(
|
|
body,
|
|
snippet.openingTags,
|
|
snippet.closingTags
|
|
);
|
|
|
|
const allFields: SnippetField[] = [];
|
|
const snippetFields = snippet.fields || [];
|
|
|
|
// Loop over all fields to check if they are present in the snippet
|
|
for (const field of snippetFields) {
|
|
const idx = placeholders.findIndex((fieldName) => fieldName === field.name);
|
|
if (idx > -1) {
|
|
allFields.push({
|
|
...field,
|
|
value: insertPlaceholderValues(field.default || '')
|
|
});
|
|
}
|
|
}
|
|
|
|
// Loop over all placeholders to find the ones that are not present in the snippet fields
|
|
for (const fieldName of placeholders) {
|
|
const idx = snippetFields.findIndex((field) => field.name === fieldName);
|
|
if (idx === -1) {
|
|
allFields.push({
|
|
name: fieldName,
|
|
title: fieldName,
|
|
type: 'string',
|
|
single: true,
|
|
value: insertValueFromMedia(fieldName)
|
|
});
|
|
}
|
|
}
|
|
|
|
setFields(allFields);
|
|
}, [snippet]);
|
|
|
|
return (
|
|
<div>
|
|
<pre className={`border p-2 whitespace-pre-wrap break-words max-h-64 overflow-auto rounded border-[var(--frontmatter-border)] bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
|
{snippetBody}
|
|
</pre>
|
|
|
|
<div className="space-y-4 mt-4">
|
|
{fields.map(
|
|
(field: SnippetField, index: number, allFields: SnippetField[]) =>
|
|
shouldShowField(field.name, index, allFields) && (
|
|
<div key={index}>
|
|
<label htmlFor={field.name} className="block text-sm font-medium capitalize">
|
|
{field.title || field.name}
|
|
</label>
|
|
<div className="mt-1">
|
|
<SnippetInputField field={field} fieldInfo={fieldInfo} onValueChange={onTextChange} />
|
|
</div>
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.forwardRef(SnippetForm);
|