mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-06 17:02:54 +02:00
#308 - Implementation of the file field
This commit is contained in:
@@ -639,6 +639,7 @@ input:checked + .field__toggle__slider:before {
|
||||
max-height: 16rem;
|
||||
}
|
||||
|
||||
.metadata_field__file__button,
|
||||
.metadata_field__preview_image__button {
|
||||
background-color: transparent;
|
||||
border: 1px dashed var(--vscode-button-background);
|
||||
@@ -646,11 +647,13 @@ input:checked + .field__toggle__slider:before {
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
.metadata_field__file__button:hover,
|
||||
.metadata_field__preview_image__button:hover {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
.metadata_field__file__button svg,
|
||||
.metadata_field__preview_image__button svg {
|
||||
color: var(--vscode-foreground);
|
||||
display: block;
|
||||
@@ -659,6 +662,7 @@ input:checked + .field__toggle__slider:before {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metadata_field__file__button span,
|
||||
.metadata_field__preview_image__button span {
|
||||
color: var(--vscode-foreground);
|
||||
display: inline-block;
|
||||
|
||||
+26
-4
@@ -744,6 +744,7 @@
|
||||
"datetime",
|
||||
"boolean",
|
||||
"image",
|
||||
"file",
|
||||
"choice",
|
||||
"taxonomy",
|
||||
"tags",
|
||||
@@ -827,6 +828,13 @@
|
||||
"default": "",
|
||||
"description": "The ID of your taxonomy field"
|
||||
},
|
||||
"fileExtensions": {
|
||||
"type": "array",
|
||||
"description": "Specify the file extensions to allow for the file picker",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"$ref": "#contenttypefield"
|
||||
},
|
||||
@@ -874,6 +882,20 @@
|
||||
"name"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"fileExtensions"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@@ -1305,8 +1327,8 @@
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
"title": "Insert image into your content",
|
||||
"command": "frontMatter.insertMedia",
|
||||
"title": "Insert media into your content",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/media-dark.svg",
|
||||
@@ -1468,7 +1490,7 @@
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
"command": "frontMatter.insertMedia",
|
||||
"group": "navigation@-128",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
@@ -1625,7 +1647,7 @@
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:dashboard:snippets:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.insertImage",
|
||||
"command": "frontMatter.insertMedia",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -324,7 +324,7 @@ export class Article {
|
||||
/**
|
||||
* Insert an image from the media dashboard into the article
|
||||
*/
|
||||
public static async insertImage() {
|
||||
public static async insertMedia() {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
|
||||
@@ -38,7 +38,7 @@ export const COMMAND_NAME = {
|
||||
modeSwitch: getCommandName("mode.switch"),
|
||||
|
||||
// Insert dashboards
|
||||
insertImage: getCommandName("insertImage"),
|
||||
insertMedia: getCommandName("insertMedia"),
|
||||
insertSnippet: getCommandName("insertSnippet"),
|
||||
|
||||
// WYSIWYG
|
||||
|
||||
@@ -22,6 +22,7 @@ export const TelemetryEvent = {
|
||||
refreshMedia: 'refreshMedia',
|
||||
deleteMedia: 'deleteMedia',
|
||||
insertMediaToContent: 'insertMediaToContent',
|
||||
insertFileToContent: 'insertFileToContent',
|
||||
updateMediaMetadata: 'updateMediaMetadata',
|
||||
openExplorerView: 'openExplorerView',
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export enum DashboardMessage {
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
createMediaFolder = 'createMediaFolder',
|
||||
insertFile = 'insertFile',
|
||||
|
||||
// Data dashboard
|
||||
getDataEntries = 'getDataEntries',
|
||||
|
||||
@@ -9,7 +9,6 @@ import { CustomScript } from '../../../helpers/CustomScript';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { ScriptType } from '../../../models';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { FileIcon } from '../../../panelWebView/components/Icons/FileIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { MenuItem, MenuItems } from '../Menu';
|
||||
@@ -74,18 +73,32 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
|
||||
const insertToArticle = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
parents: viewData?.data?.parents,
|
||||
multiple: viewData?.data?.multiple,
|
||||
value: viewData?.data?.value,
|
||||
position: viewData?.data?.position || null,
|
||||
blockData: typeof viewData?.data?.blockData !== "undefined" ? viewData?.data?.blockData : undefined,
|
||||
alt: alt || "",
|
||||
caption: caption || ""
|
||||
});
|
||||
|
||||
if (viewData?.data?.type === "file") {
|
||||
Messenger.send(DashboardMessage.insertFile, {
|
||||
relPath: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
parents: viewData?.data?.parents,
|
||||
multiple: viewData?.data?.multiple,
|
||||
value: viewData?.data?.value,
|
||||
position: viewData?.data?.position || null,
|
||||
blockData: typeof viewData?.data?.blockData !== "undefined" ? viewData?.data?.blockData : undefined
|
||||
});
|
||||
} else {
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
relPath: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
parents: viewData?.data?.parents,
|
||||
multiple: viewData?.data?.multiple,
|
||||
value: viewData?.data?.value,
|
||||
position: viewData?.data?.position || null,
|
||||
blockData: typeof viewData?.data?.blockData !== "undefined" ? viewData?.data?.blockData : undefined,
|
||||
alt: alt || "",
|
||||
caption: caption || ""
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const insertSnippet = () => {
|
||||
@@ -100,7 +113,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
snippet = snippet?.replace("{mediaHeight}", media?.dimensions?.height?.toString() || "");
|
||||
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
relPath: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null,
|
||||
|
||||
@@ -17,7 +17,7 @@ import useMedia from '../../hooks/useMedia';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { join } from 'path';
|
||||
import { extname, join } from 'path';
|
||||
|
||||
export interface IMediaProps {}
|
||||
|
||||
@@ -40,12 +40,23 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
}, [folders, viewData, settings?.staticFolder]);
|
||||
|
||||
const allMedia = React.useMemo(() => {
|
||||
let mediaFiles = media;
|
||||
// Check if content allows page bundle
|
||||
if (viewData && viewData.data && typeof viewData.data.pageBundle !== "undefined" && !viewData.data.pageBundle) {
|
||||
return media.filter(m => parseWinPath(m.fsPath).includes(join('/', settings?.staticFolder || '', '/')));
|
||||
mediaFiles = media.filter(m => parseWinPath(m.fsPath).includes(join('/', settings?.staticFolder || '', '/')));
|
||||
}
|
||||
|
||||
return media;
|
||||
if (viewData && viewData.data && viewData.data.type === "file" && viewData.data.fileExtensions && viewData.data.fileExtensions.length > 0) {
|
||||
const supportedExtensions = viewData.data.fileExtensions;
|
||||
mediaFiles = mediaFiles.filter(m => {
|
||||
const ext = extname(m.fsPath);
|
||||
// Remove the dot from the extension
|
||||
const extWithoutDot = ext.substring(1);
|
||||
return supportedExtensions.includes(extWithoutDot);
|
||||
});
|
||||
}
|
||||
|
||||
return mediaFiles;
|
||||
}, [media, viewData, settings?.staticFolder]);
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.preview, () => Preview.open(extensionPath) ));
|
||||
|
||||
// Inserting an image in Markdown
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertImage, Article.insertImage));
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertMedia, Article.insertMedia));
|
||||
|
||||
// Inserting a snippet in Markdown
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet));
|
||||
|
||||
@@ -270,7 +270,7 @@ export class MediaHelpers {
|
||||
* @param data
|
||||
*/
|
||||
public static async insertMediaToMarkdown(data: any) {
|
||||
if (data?.file && data?.image) {
|
||||
if (data?.file && data?.relPath) {
|
||||
if (!data?.position) {
|
||||
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
|
||||
}
|
||||
@@ -281,12 +281,12 @@ export class MediaHelpers {
|
||||
const editor = window.activeTextEditor;
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const filePath = data.file;
|
||||
let imgPath = data.image;
|
||||
let relPath = data.relPath;
|
||||
|
||||
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
|
||||
const articleCt = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), relPath);
|
||||
const fileDir = parseWinPath(dirname(filePath));
|
||||
const imgDir = parseWinPath(dirname(absImgPath));
|
||||
const contentFolders = Folders.get();
|
||||
@@ -303,11 +303,11 @@ export class MediaHelpers {
|
||||
if (existsInContent) {
|
||||
const relImgPath = relative(fileDir, imgDir);
|
||||
|
||||
imgPath = join(relImgPath, basename(imgPath));
|
||||
relPath = join(relImgPath, basename(relPath));
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
data.snippet = data.snippet.replace(data.relPath, relPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,14 @@ export class MediaHelpers {
|
||||
if (line) {
|
||||
const selection = editor?.selection;
|
||||
await editor?.edit(builder => {
|
||||
const snippet = data.snippet || ``;
|
||||
const mimeType = lookup(relPath)
|
||||
|
||||
let isFile = true;
|
||||
if (mimeType) {
|
||||
isFile = !mimeType.startsWith('image');
|
||||
}
|
||||
|
||||
const snippet = data.snippet || `${isFile ? "" : "!"}[${data.alt || data.caption || ""}](${relPath})`;
|
||||
if (selection !== undefined) {
|
||||
builder.replace(selection, snippet);
|
||||
} else {
|
||||
@@ -333,7 +340,7 @@ export class MediaHelpers {
|
||||
|
||||
DataListener.updateMetadata({
|
||||
field: data.fieldName,
|
||||
value: imgPath,
|
||||
value: relPath,
|
||||
parents: data.parents,
|
||||
blockData: data.blockData
|
||||
});
|
||||
|
||||
@@ -40,6 +40,10 @@ export class MediaListener extends BaseListener {
|
||||
Telemetry.send(TelemetryEvent.insertMediaToContent);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.insertFile:
|
||||
Telemetry.send(TelemetryEvent.insertFileToContent);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
Telemetry.send(TelemetryEvent.updateMediaMetadata);
|
||||
this.update(msg.data);
|
||||
|
||||
@@ -147,12 +147,14 @@ export class DataListener extends BaseListener {
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const dateFields = contentType.fields.filter((f) => f.type === "datetime");
|
||||
const imageFields = contentType.fields.filter((f) => f.type === "image" && f.multiple);
|
||||
const fileFields = contentType.fields.filter((f) => f.type === "file" && f.multiple);
|
||||
|
||||
// Support multi-level fields
|
||||
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
|
||||
|
||||
const isDateField = dateFields.some(f => f.name === field);
|
||||
const isMultiImageField = imageFields.some(f => f.name === field);
|
||||
const isMultiFileField = fileFields.some(f => f.name === field);
|
||||
|
||||
if (isDateField) {
|
||||
for (const dateField of dateFields) {
|
||||
@@ -160,9 +162,11 @@ export class DataListener extends BaseListener {
|
||||
parentObj[field] = Article.formatDate(new Date(value));
|
||||
}
|
||||
}
|
||||
} else if (isMultiImageField) {
|
||||
for (const imageField of imageFields) {
|
||||
if (field === imageField.name) {
|
||||
} else if (isMultiImageField || isMultiFileField) {
|
||||
const fields = isMultiImageField ? imageFields : fileFields;
|
||||
|
||||
for (const crntField of fields) {
|
||||
if (field === crntField.name) {
|
||||
// If value is an array, it means it comes from the explorer view itself (deletion)
|
||||
if (Array.isArray(value)) {
|
||||
parentObj[field] = value || [];
|
||||
|
||||
@@ -22,6 +22,9 @@ export class MediaListener extends BaseListener {
|
||||
case CommandToCode.selectImage:
|
||||
this.selectMedia(msg);
|
||||
break;
|
||||
case CommandToCode.selectFile:
|
||||
this.selectMedia(msg);
|
||||
break;
|
||||
case CommandToCode.getImageUrl:
|
||||
this.generateUrl(msg.data);
|
||||
break;
|
||||
|
||||
@@ -19,4 +19,8 @@ export interface ViewData {
|
||||
parents?: string[];
|
||||
multiple?: string[];
|
||||
value?: string;
|
||||
|
||||
// File fields
|
||||
type: "file" | "media";
|
||||
fileExtensions?: string[];
|
||||
}
|
||||
@@ -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";
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file";
|
||||
|
||||
export interface Field {
|
||||
title?: string;
|
||||
@@ -66,6 +66,7 @@ export interface Field {
|
||||
fieldGroup?: string | string[];
|
||||
dataType?: string | string[];
|
||||
taxonomyLimit?: number;
|
||||
fileExtensions?: string[];
|
||||
|
||||
// Date fields
|
||||
isPublishDate?: boolean;
|
||||
|
||||
@@ -26,6 +26,7 @@ export enum CommandToCode {
|
||||
updateMetadata = "update-metadata",
|
||||
openDashboard = "open-dashboard",
|
||||
selectImage = "select-image",
|
||||
selectFile = "select-file",
|
||||
updateCustomTaxonomy = "updateCustomTaxonomy",
|
||||
addToCustomTaxonomy = "addToCustomTaxonomy",
|
||||
frameworkCommand = "framework-command",
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { DocumentIcon, PaperClipIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { BlockFieldData } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IFileFieldProps {
|
||||
label: string;
|
||||
fieldName: string;
|
||||
filePath: string;
|
||||
multiple?: boolean;
|
||||
value: string | string[] | null;
|
||||
fileExtensions?: string[];
|
||||
parents?: string[];
|
||||
blockData?: BlockFieldData;
|
||||
onChange: (value: string | string[] | null) => void;
|
||||
}
|
||||
|
||||
const File = ({ value, onRemove }: { value: string, onRemove: (value: string) => void }) => {
|
||||
return (
|
||||
<div className='metadata_field__file__list__item' title={value}>
|
||||
<div className='metadata_field__file__item__icon'>
|
||||
<PaperClipIcon style={{ width: "16px", height: "16px" }} />
|
||||
</div>
|
||||
<span className='metadata_field__file__item__text' style={{ lineHeight: "16px" }}>{basename(value)}</span>
|
||||
|
||||
<button className='metadata_field__file__item__remove' type='button' onClick={() => onRemove(value)}>
|
||||
<TrashIcon style={{ width: "16px", height: "16px" }} />
|
||||
<span className='sr-only'>Delete file</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const FileField: React.FunctionComponent<IFileFieldProps> = ({ label, multiple, filePath, fileExtensions, fieldName, value, parents, blockData, onChange }: React.PropsWithChildren<IFileFieldProps>) => {
|
||||
|
||||
const selectFile = useCallback(() => {
|
||||
MessageHelper.sendMessage(CommandToCode.selectFile, {
|
||||
filePath,
|
||||
fieldName,
|
||||
value,
|
||||
multiple,
|
||||
parents,
|
||||
blockData,
|
||||
fileExtensions,
|
||||
type: "file"
|
||||
});
|
||||
}, [filePath, fieldName, value, multiple, parents]);
|
||||
|
||||
const onRemove = useCallback((crntValue) => {
|
||||
const newValue = value && Array.isArray(value) ? value.filter(v => v !== crntValue) : null;
|
||||
onChange(newValue);
|
||||
} , [value]);
|
||||
|
||||
const isEmpty = useMemo(() => {
|
||||
return !value || (Array.isArray(value) && value.length === 0);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<DocumentIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
<div className={`metadata_field__file`}>
|
||||
{
|
||||
(isEmpty || multiple) && (
|
||||
<button className={`metadata_field__file__button ${isEmpty ? "" : "not_empty"}`} title={`Add your ${label?.toLowerCase() || "file"}`} type="button" onClick={selectFile}>
|
||||
<DocumentIcon />
|
||||
<span>Add your {label?.toLowerCase() || "file"}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
value && !Array.isArray(value) && (
|
||||
<div className='metadata_field__file__list'>
|
||||
<File value={value} onRemove={onRemove} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
multiple && value && Array.isArray(value) && (
|
||||
<div className='metadata_field__file__list multiple'>
|
||||
{
|
||||
value.map((v, idx) => (
|
||||
<File key={idx} value={v} onRemove={onRemove} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -42,7 +42,8 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
|
||||
multiple,
|
||||
metadataInsert: true,
|
||||
parents,
|
||||
blockData
|
||||
blockData,
|
||||
type: "media"
|
||||
});
|
||||
}, [filePath, fieldName, value, multiple, parents]);
|
||||
|
||||
@@ -64,7 +65,7 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
|
||||
(!value || multiple) && (
|
||||
<button className={`metadata_field__preview_image__button`} title={`Add your ${label?.toLowerCase() || "image"}`} type="button" onClick={selectImage}>
|
||||
<PhotographIcon />
|
||||
<span className="mt-2 block text-sm font-medium text-gray-900">Add your {label?.toLowerCase() || "image"}</span>
|
||||
<span>Add your {label?.toLowerCase() || "image"}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { VsLabel } from '../VscodeComponents';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import { DateTimeField } from './DateTimeField';
|
||||
import { DraftField } from './DraftField';
|
||||
import { FileField } from './FileField';
|
||||
import { NumberField } from './NumberField';
|
||||
import { PreviewImageField, PreviewImageValue } from './PreviewImageField';
|
||||
import { TextField } from './TextField';
|
||||
@@ -188,6 +189,21 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
onChange={(value) => onSendUpdate(field.name, value, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'file') {
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<FileField
|
||||
label={field.title || field.name}
|
||||
fieldName={field.name}
|
||||
multiple={field.multiple}
|
||||
fileExtensions={field.fileExtensions}
|
||||
filePath={metadata.filePath as string}
|
||||
value={fieldValue as string | string[] | null}
|
||||
parents={parentFields}
|
||||
blockData={blockData}
|
||||
onChange={(value) => onSendUpdate(field.name, value, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'choice') {
|
||||
const choices = field.choices || [];
|
||||
|
||||
|
||||
@@ -253,6 +253,80 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* File field */
|
||||
.metadata_field__file__button.not_empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.metadata_field__file__button.not_empty svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
margin: inherit;
|
||||
}
|
||||
|
||||
.metadata_field__file__button.not_empty span {
|
||||
margin-top: 0;
|
||||
display: inline-block;
|
||||
margin: inherit;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.metadata_field__file__list.multiple {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.metadata_field__file__list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem .25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.metadata_field__file__list__item:nth-child(odd) {
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
.metadata_field__file__list__item:hover {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
.metadata_field__file__item__icon {
|
||||
margin-right: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metadata_field__file__item__text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.metadata_field__file__item__remove {
|
||||
display: none;
|
||||
background: none;
|
||||
width: auto;
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metadata_field__file__item__remove:hover {
|
||||
background: var(--vscode-inputValidation-errorBackground);
|
||||
}
|
||||
|
||||
.metadata_field__file__list__item:hover .metadata_field__file__item__remove {
|
||||
display: flex;
|
||||
margin: -.5rem -.25rem -.5rem 0;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
/* Quill changes */
|
||||
.ql-toolbar.ql-snow,
|
||||
.ql-container.ql-snow {
|
||||
|
||||
Reference in New Issue
Block a user