mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-03 16:31:32 +02:00
#666 - First steps to implement media content types
This commit is contained in:
+193
-160
@@ -10,8 +10,7 @@
|
||||
"color": "#0e131f",
|
||||
"theme": "dark"
|
||||
},
|
||||
"badges": [
|
||||
{
|
||||
"badges": [{
|
||||
"description": "version",
|
||||
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
|
||||
"href": "https://github.com/estruyf/vscode-front-matter"
|
||||
@@ -71,8 +70,7 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"keybindings": [{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
@@ -90,23 +88,19 @@
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}
|
||||
]
|
||||
"activitybar": [{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}]
|
||||
},
|
||||
"views": {
|
||||
"frontmatter-explorer": [
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}
|
||||
]
|
||||
"frontmatter-explorer": [{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "%settings.configuration.title%",
|
||||
@@ -174,8 +168,7 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -191,8 +184,7 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -500,8 +492,7 @@
|
||||
"categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [
|
||||
{
|
||||
"items": [{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
@@ -569,8 +560,7 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -777,8 +767,7 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -832,8 +821,7 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -1007,6 +995,73 @@
|
||||
"markdownDescription": "%setting.frontMatter.media.defaultSorting.markdownDescription%",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.media.contentTypes": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.media.contentTypes.markdownDescription%",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.description%",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.name.description%"
|
||||
},
|
||||
"fileTypes": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fileTypes.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.description%",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.title.description%"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.name.description%"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.type.description%"
|
||||
},
|
||||
"single": {
|
||||
"type": "boolean",
|
||||
"description": "%setting.frontMatter.media.contentTypes.items.properties.fields.properties.single.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.media.supportedMimeTypes": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
@@ -1234,8 +1289,7 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1429,8 +1483,7 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"allOf": [{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1630,51 +1683,48 @@
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
@@ -1687,8 +1737,7 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1880,8 +1929,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"commands": [{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2198,21 +2246,16 @@
|
||||
"category": "Front Matter"
|
||||
}
|
||||
],
|
||||
"submenus": [
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}
|
||||
],
|
||||
"submenus": [{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}],
|
||||
"menus": {
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"webview/context": [{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}],
|
||||
"editor/title": [{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
@@ -2293,14 +2336,11 @@
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}
|
||||
],
|
||||
"frontmatter.submenu": [
|
||||
{
|
||||
"explorer/context": [{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}],
|
||||
"frontmatter.submenu": [{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "explorerResourceIsFolder",
|
||||
"group": "frontmatter@1"
|
||||
@@ -2316,8 +2356,7 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"commandPalette": [{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2466,8 +2505,7 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"view/title": [{
|
||||
"command": "frontMatter.chatbot",
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2499,57 +2537,52 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"grammars": [
|
||||
{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
}
|
||||
],
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [
|
||||
{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
"grammars": [{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
]
|
||||
}],
|
||||
"walkthroughs": [{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
"scripts": {
|
||||
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
|
||||
@@ -2674,4 +2707,4 @@
|
||||
"vsce": {
|
||||
"dependencies": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ export const SETTING_CONTENT_HIDE_FRONTMATTER = 'content.hideFm';
|
||||
export const SETTING_CONTENT_HIDE_FRONTMATTER_MESSAGE = 'content.hideFmMessage';
|
||||
|
||||
export const SETTING_MEDIA_SUPPORTED_MIMETYPES = 'media.supportedMimeTypes';
|
||||
export const SETTING_MEDIA_CONTENTTYPES = 'media.contentTypes';
|
||||
|
||||
export const SETTING_DASHBOARD_OPENONSTART = 'dashboard.openOnStart';
|
||||
export const SETTING_DASHBOARD_CONTENT_TAGS = 'dashboard.content.cardTags';
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DetailsInput } from './DetailsInput';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DEFAULT_MEDIA_CONTENT_TYPE, MediaInfo, UnmappedMedia } from '../../../models';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { basename } from 'path';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { PageSelector, SelectedMediaFolderSelector, SettingsAtom } from '../../state';
|
||||
|
||||
export interface IDetailsFormProps {
|
||||
media: MediaInfo;
|
||||
isImageFile: boolean;
|
||||
isVideoFile: boolean;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const DetailsForm: React.FunctionComponent<IDetailsFormProps> = ({
|
||||
media,
|
||||
isImageFile,
|
||||
isVideoFile,
|
||||
onDismiss,
|
||||
}: React.PropsWithChildren<IDetailsFormProps>) => {
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const [filename, setFilename] = React.useState<string>(media.filename);
|
||||
const [unmapped, setUnmapped] = React.useState<UnmappedMedia[]>([]);
|
||||
const [metadata, setMetadata] = React.useState<{ [fieldName: string]: string }>({});
|
||||
|
||||
const fileInfo = useMemo(() => {
|
||||
const fileInfo = filename ? basename(filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
const name = fileInfo?.join('.');
|
||||
|
||||
return { name, extension };
|
||||
}, [filename]);
|
||||
|
||||
const fields = useMemo(() => {
|
||||
const contentType = settings?.media.contentTypes.find((c) => c.fileTypes?.map(t => t.toLowerCase()).includes(fileInfo.extension as string)) || DEFAULT_MEDIA_CONTENT_TYPE;
|
||||
return contentType.fields;
|
||||
}, [fileInfo, settings?.media.contentTypes]);
|
||||
|
||||
const updateMetadata = useCallback((fieldName: string, value: string) => {
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata,
|
||||
[fieldName]: value
|
||||
}));
|
||||
}, [metadata]);
|
||||
|
||||
const remapMetadata = useCallback((item: UnmappedMedia) => {
|
||||
Messenger.send(DashboardMessage.remapMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
unmappedItem: item,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onDismiss();
|
||||
}, [media, selectedFolder, page]);
|
||||
|
||||
const onSubmitMetadata = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
filename,
|
||||
page,
|
||||
folder: selectedFolder,
|
||||
metadata,
|
||||
});
|
||||
|
||||
onDismiss();
|
||||
}, [media, filename, metadata, selectedFolder, page, onDismiss]);
|
||||
|
||||
const formFields = useMemo(() => {
|
||||
return fields.map((field) => {
|
||||
if (field.name === "title") {
|
||||
return (
|
||||
<div key="title">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`title`} value={metadata?.title || ""} onChange={(e) => updateMetadata("title", e)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.name === "caption") {
|
||||
if (isImageFile || isVideoFile) {
|
||||
return (
|
||||
<div key="caption">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`caption`} value={metadata?.caption || ""} onChange={(e) => updateMetadata("caption", e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (field.name === "alt") {
|
||||
if (isImageFile) {
|
||||
return (
|
||||
<div key="alt">
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`alt`} value={metadata?.alt || ""} onChange={(e) => updateMetadata("alt", e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.name}>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{field.title || field.name}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={field.name} value={metadata[field.name] || ""} onChange={(e) => updateMetadata(field.name, e)} isTextArea={!field.single} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [fields, metadata, updateMetadata]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fields && media.metadata && fileInfo?.extension) {
|
||||
const metadataFields: { [fieldName: string]: string } = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
metadataFields[field.name] = (media.metadata[field.name] || '') as string;
|
||||
});
|
||||
|
||||
setMetadata(metadataFields);
|
||||
}
|
||||
}, [fileInfo, media.metadata, fields]);
|
||||
|
||||
useEffect(() => {
|
||||
messageHandler.request<UnmappedMedia[]>(DashboardMessage.getUnmappedMedia, media.filename).then((result) => {
|
||||
setUnmapped(result);
|
||||
});
|
||||
}, [media.filename]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className={`text-base text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)}
|
||||
</h3>
|
||||
|
||||
{
|
||||
unmapped && unmapped.length > 0 && (
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)}
|
||||
</p>
|
||||
<ul className='pl-4'>
|
||||
{
|
||||
unmapped.map((item) => (
|
||||
<li className='list-disc'>
|
||||
<button
|
||||
key={item.file}
|
||||
className='text-left hover:text-[var(--frontmatter-link-hover)]'
|
||||
onClick={() => remapMetadata(item)}>
|
||||
{item.file}{item.metadata.title ? ` (${item.metadata.title})` : ''}
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)}
|
||||
</label>
|
||||
<div className="relative mt-1">
|
||||
<DetailsInput name={`filename`} value={fileInfo.name || ""} onChange={(e) => setFilename(`${e}.${fileInfo.extension}`)} />
|
||||
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span className={`sm:text-sm placeholder-[var(--vscode-input-placeholderForeground)]`}>.{fileInfo?.extension}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formFields}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1`}
|
||||
onClick={onSubmitMetadata}
|
||||
disabled={!filename}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]`}
|
||||
onClick={onDismiss}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonCancel)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,17 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { PencilSquareIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { format } from 'date-fns';
|
||||
import { basename } from 'path';
|
||||
import * as React from 'react';
|
||||
import { Fragment, useCallback, useMemo } from 'react';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import { DateHelper } from '../../../helpers/DateHelper';
|
||||
import { MediaInfo, UnmappedMedia } from '../../../models';
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { PageSelector, SelectedMediaFolderSelector } from '../../state';
|
||||
import { DEFAULT_MEDIA_CONTENT_TYPE, MediaInfo } from '../../../models';
|
||||
import { DetailsItem } from './DetailsItem';
|
||||
import { DetailsInput } from './DetailsInput';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { DetailsForm } from './DetailsForm';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsAtom } from '../../state';
|
||||
import { basename } from 'path';
|
||||
|
||||
export interface IDetailsSlideOverProps {
|
||||
imgSrc: string;
|
||||
@@ -42,62 +40,56 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
isImageFile,
|
||||
isVideoFile
|
||||
}: React.PropsWithChildren<IDetailsSlideOverProps>) => {
|
||||
const [filename, setFilename] = React.useState<string>(media.filename);
|
||||
const [caption, setCaption] = React.useState<string | undefined>(media.caption);
|
||||
const [title, setTitle] = React.useState<string | undefined>(media.title);
|
||||
const [unmapped, setUnmapped] = React.useState<UnmappedMedia[]>([]);
|
||||
const [alt, setAlt] = React.useState(media.alt);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const createdDate = useMemo(() => DateHelper.tryParse(media.ctime), [media]);
|
||||
const modifiedDate = useMemo(() => DateHelper.tryParse(media.mtime), [media]);
|
||||
|
||||
const fileInfo = filename ? basename(filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
const name = fileInfo?.join('.');
|
||||
const extension = useMemo(() => {
|
||||
const fileInfo = media.filename ? basename(media.filename).split('.') : null;
|
||||
const extension = fileInfo?.pop();
|
||||
return extension;
|
||||
}, [media.filename]);
|
||||
|
||||
const onSubmitMetadata = useCallback(() => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
filename,
|
||||
caption,
|
||||
alt,
|
||||
title,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onEditClose();
|
||||
}, [media, filename, caption, alt, title, selectedFolder, page]);
|
||||
|
||||
const remapMetadata = useCallback((item: UnmappedMedia) => {
|
||||
Messenger.send(DashboardMessage.remapMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
unmappedItem: item,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
|
||||
onEditClose();
|
||||
}, [media, filename, caption, alt, title, selectedFolder, page]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTitle(media.title);
|
||||
setAlt(media.alt);
|
||||
setCaption(media.caption);
|
||||
setFilename(media.filename);
|
||||
}, [media]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showForm) {
|
||||
messageHandler.request<UnmappedMedia[]>(DashboardMessage.getUnmappedMedia, filename).then((result) => {
|
||||
setUnmapped(result);
|
||||
});
|
||||
} else {
|
||||
setUnmapped([]);
|
||||
const fields = useMemo(() => {
|
||||
if (extension) {
|
||||
const contentType = settings?.media.contentTypes.find((c) => c.fileTypes?.map(t => t.toLowerCase()).includes(extension as string)) || DEFAULT_MEDIA_CONTENT_TYPE;
|
||||
return contentType.fields;
|
||||
}
|
||||
}, [showForm, filename]);
|
||||
}, [extension, settings?.media.contentTypes]);
|
||||
|
||||
const detailItems = useMemo(() => {
|
||||
const items = [];
|
||||
|
||||
items.push(
|
||||
<DetailsItem key="filename" title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)} details={media.filename} />
|
||||
);
|
||||
|
||||
fields?.forEach((field) => {
|
||||
if (field.name === "title") {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.metadata.title || ""} />
|
||||
);
|
||||
} else if (field.name === "caption") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.metadata.caption || ""} />
|
||||
);
|
||||
}
|
||||
} else if (field.name === "alt") {
|
||||
if (isImageFile) {
|
||||
items.push(
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.metadata.alt || ""} />
|
||||
);
|
||||
}
|
||||
} else {
|
||||
items.push(
|
||||
<DetailsItem title={field.title || field.name} details={(media.metadata[field.name] || "") as string} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [fields, media.metadata]);
|
||||
|
||||
return (
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
@@ -168,101 +160,11 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
<div>
|
||||
{/* EDIT METADATA FORM */}
|
||||
{showForm && (
|
||||
<>
|
||||
<h3 className={`text-base text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)}
|
||||
</h3>
|
||||
|
||||
{
|
||||
unmapped && unmapped.length > 0 && (
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)}
|
||||
</p>
|
||||
<ul className='pl-4'>
|
||||
{
|
||||
unmapped.map((item) => (
|
||||
<li className='list-disc'>
|
||||
<button
|
||||
key={item.file}
|
||||
className='text-left hover:text-[var(--frontmatter-link-hover)]'
|
||||
onClick={() => remapMetadata(item)}>
|
||||
{item.file}{item.metadata.title ? ` (${item.metadata.title})` : ''}
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)}
|
||||
</p>
|
||||
<div className="flex flex-col py-3 space-y-3">
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)}
|
||||
</label>
|
||||
<div className="relative mt-1">
|
||||
<DetailsInput name={`filename`} value={name || ""} onChange={(e) => setFilename(`${e}.${extension}`)} />
|
||||
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span className={`sm:text-sm placeholder-[var(--vscode-input-placeholderForeground)]`}>.{extension}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`title`} value={title || ""} onChange={(e) => setTitle(e)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(isImageFile || isVideoFile) && (
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`caption`} value={caption || ""} onChange={(e) => setCaption(e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isImageFile && (
|
||||
<div>
|
||||
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<DetailsInput name={`alt`} value={alt || ""} onChange={(e) => setAlt(e)} isTextArea />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1`}
|
||||
onClick={onSubmitMetadata}
|
||||
disabled={!filename}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonSave)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]`}
|
||||
onClick={onEditClose}
|
||||
>
|
||||
{l10n.t(LocalizationKey.commonCancel)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
<DetailsForm
|
||||
media={media}
|
||||
isImageFile={isImageFile}
|
||||
isVideoFile={isVideoFile}
|
||||
onDismiss={onEditClose} />
|
||||
)}
|
||||
|
||||
{!showForm && (
|
||||
@@ -275,15 +177,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
|
||||
</button>
|
||||
</h3>
|
||||
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)} details={media.filename} />
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.title || ""} />
|
||||
|
||||
{isImageFile && (
|
||||
<>
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonCaption)} details={media.caption || ''} />
|
||||
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonAlt)} details={media.alt || ''} />
|
||||
</>
|
||||
)}
|
||||
{detailItems}
|
||||
</dl>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -55,8 +55,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [showSnippetFormDialog, setShowSnippetFormDialog] = useState(false);
|
||||
const [mediaData, setMediaData] = useState<any | undefined>(undefined);
|
||||
const [caption, setCaption] = useState(media.caption);
|
||||
const [alt, setAlt] = useState(media.alt);
|
||||
const [filename, setFilename] = useState<string | null>(null);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
@@ -150,7 +148,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
position: viewData?.data?.position || null,
|
||||
blockData:
|
||||
typeof viewData?.data?.blockData !== 'undefined' ? viewData?.data?.blockData : undefined,
|
||||
title: media.title
|
||||
title: media.metadata.title
|
||||
});
|
||||
} else {
|
||||
Messenger.send(DashboardMessage.insertMedia, {
|
||||
@@ -163,9 +161,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
position: viewData?.data?.position || null,
|
||||
blockData:
|
||||
typeof viewData?.data?.blockData !== 'undefined' ? viewData?.data?.blockData : undefined,
|
||||
alt: alt || '',
|
||||
caption: caption || '',
|
||||
title: media.title || ''
|
||||
alt: media.metadata.alt || '',
|
||||
caption: media.metadata.caption || '',
|
||||
title: media.metadata.title || ''
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -190,12 +188,10 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
const fieldData = {
|
||||
mediaUrl: (parseWinPath(relPath) || '').replace(/ /g, '%20'),
|
||||
alt: alt || '',
|
||||
caption: caption || '',
|
||||
title: media.title || '',
|
||||
filename: basename(relPath || ''),
|
||||
mediaWidth: media?.dimensions?.width?.toString() || '',
|
||||
mediaHeight: media?.dimensions?.height?.toString() || ''
|
||||
mediaHeight: media?.dimensions?.height?.toString() || '',
|
||||
...media.metadata
|
||||
};
|
||||
|
||||
if (!snippet.fields || snippet.fields.length === 0) {
|
||||
@@ -215,7 +211,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setMediaData(fieldData);
|
||||
}
|
||||
},
|
||||
[alt, caption, media, settings, viewData, mediaSnippets]
|
||||
[media, settings, viewData, mediaSnippets]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -404,18 +400,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
setMediaData(undefined);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (media.alt !== alt) {
|
||||
setAlt(media.alt);
|
||||
}
|
||||
}, [media.alt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (media.caption !== caption) {
|
||||
setCaption(media.caption);
|
||||
}
|
||||
}, [media.caption]);
|
||||
|
||||
useEffect(() => {
|
||||
const name = basename(parseWinPath(media.fsPath) || '');
|
||||
if (name !== filename) {
|
||||
@@ -623,28 +607,28 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<p className={`text-sm font-bold pointer-events-none flex items-center break-all text-[var(--vscode-foreground)]}`}>
|
||||
{basename(parseWinPath(media.fsPath) || '')}
|
||||
</p>
|
||||
{!isImageFile && media.title && (
|
||||
{!isImageFile && media.metadata.title && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.title}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.title}</span>
|
||||
</p>
|
||||
)}
|
||||
{media.caption && (
|
||||
{media.metadata.caption && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.caption}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.caption}</span>
|
||||
</p>
|
||||
)}
|
||||
{!media.caption && media.alt && (
|
||||
{!media.metadata.caption && media.metadata.alt && (
|
||||
<p className={`mt-2 text-xs font-medium pointer-events-none flex flex-col items-start`}>
|
||||
<b className={`mr-2`}>
|
||||
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}:
|
||||
</b>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.alt}</span>
|
||||
<span className={`block mt-1 text-xs text-[var(--vscode-foreground)]`}>{media.metadata.alt}</span>
|
||||
</p>
|
||||
)}
|
||||
{(media?.size || media?.dimensions) && (
|
||||
|
||||
@@ -33,8 +33,8 @@ export const MediaSnippetForm: React.FunctionComponent<IMediaSnippetFormProps> =
|
||||
|
||||
return (
|
||||
<SnippetSlideOver
|
||||
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.title || media.filename)}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.title || media.filename)}
|
||||
title={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogTitle, media.metadata.title || media.filename)}
|
||||
description={l10n.t(LocalizationKey.dashboardMediaMediaSnippetFormFormDialogDescription, media.metadata.title || media.filename)}
|
||||
isSaveDisabled={false}
|
||||
trigger={insertToArticle}
|
||||
dismiss={onDismiss}
|
||||
|
||||
@@ -66,6 +66,10 @@ const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFor
|
||||
if (mediaData[fieldName]) {
|
||||
return mediaData[fieldName];
|
||||
}
|
||||
|
||||
if (mediaData.metadata && mediaData.metadata[fieldName]) {
|
||||
return mediaData.metadata[fieldName];
|
||||
}
|
||||
},
|
||||
[mediaData]
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DraftField,
|
||||
Framework,
|
||||
GitSettings,
|
||||
MediaContentType,
|
||||
Project,
|
||||
Snippets,
|
||||
SortingSetting
|
||||
@@ -47,6 +48,11 @@ export interface Settings {
|
||||
snippetsWrapper: boolean;
|
||||
date: { format: string };
|
||||
lastUpdated: number;
|
||||
media: MediaDashboardSettings;
|
||||
}
|
||||
|
||||
export interface MediaDashboardSettings {
|
||||
contentTypes: MediaContentType[];
|
||||
}
|
||||
|
||||
export interface DashboardState {
|
||||
|
||||
@@ -30,14 +30,23 @@ import {
|
||||
SETTING_DASHBOARD_CONTENT_CARD_TITLE,
|
||||
SETTING_DASHBOARD_CONTENT_CARD_STATE,
|
||||
SETTING_DASHBOARD_CONTENT_CARD_DESCRIPTION,
|
||||
SETTING_WEBSITE_URL
|
||||
SETTING_WEBSITE_URL,
|
||||
SETTING_MEDIA_CONTENTTYPES
|
||||
} from '../constants';
|
||||
import {
|
||||
DashboardViewType,
|
||||
SortingOption,
|
||||
Settings as ISettings
|
||||
} from '../dashboardWebView/models';
|
||||
import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from '../models';
|
||||
import {
|
||||
CustomScript,
|
||||
DEFAULT_MEDIA_CONTENT_TYPE,
|
||||
DraftField,
|
||||
MediaContentType,
|
||||
Snippets,
|
||||
SortingSetting,
|
||||
TaxonomyType
|
||||
} from '../models';
|
||||
import { DataFile } from '../models/DataFile';
|
||||
import { DataFolder } from '../models/DataFolder';
|
||||
import { DataType } from '../models/DataType';
|
||||
@@ -152,6 +161,11 @@ export class DashboardSettings {
|
||||
snippetsWrapper: Settings.get<boolean>(SETTING_SNIPPETS_WRAPPER),
|
||||
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global'),
|
||||
websiteUrl: Settings.get<string>(SETTING_WEBSITE_URL),
|
||||
media: {
|
||||
contentTypes: Settings.get<MediaContentType[]>(SETTING_MEDIA_CONTENTTYPES) || [
|
||||
DEFAULT_MEDIA_CONTENT_TYPE
|
||||
]
|
||||
},
|
||||
lastUpdated: new Date().getTime()
|
||||
} as ISettings;
|
||||
|
||||
|
||||
@@ -161,7 +161,9 @@ export class MediaHelpers {
|
||||
dimensions:
|
||||
mimeType && mimeType.startsWith('image/') ? imageSize(file.fsPath) : undefined,
|
||||
mimeType: lookup(file.fsPath) || '',
|
||||
...metadata
|
||||
metadata: {
|
||||
...metadata
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return { ...file };
|
||||
@@ -478,15 +480,11 @@ export class MediaHelpers {
|
||||
const {
|
||||
file,
|
||||
filename,
|
||||
page,
|
||||
folder,
|
||||
...metadata
|
||||
metadata
|
||||
}: {
|
||||
file: string;
|
||||
filename: string;
|
||||
page: number;
|
||||
folder: string | null;
|
||||
metadata: any;
|
||||
metadata: { [fieldName: string]: string | string[] | Date | number | undefined };
|
||||
} = data;
|
||||
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
@@ -522,7 +520,8 @@ export class MediaHelpers {
|
||||
filename: basename(file.fsPath),
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.getWebview()?.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
stats: undefined,
|
||||
metadata: {}
|
||||
} as MediaInfo)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Field } from '.';
|
||||
|
||||
export interface MediaContentType {
|
||||
name: string;
|
||||
fileTypes: string[] | null | undefined;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
export const DEFAULT_MEDIA_CONTENT_TYPE: MediaContentType = {
|
||||
name: 'default',
|
||||
fileTypes: null,
|
||||
fields: [
|
||||
{
|
||||
title: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
title: 'Caption',
|
||||
name: 'caption',
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
title: 'Alt text',
|
||||
name: 'alt',
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -14,11 +14,15 @@ export interface MediaInfo {
|
||||
fsPath: string;
|
||||
vsPath: string | undefined;
|
||||
dimensions?: ISizeCalculationResult | undefined;
|
||||
title?: string | undefined;
|
||||
caption?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
mimeType?: string | undefined;
|
||||
mtime?: Date;
|
||||
ctime?: Date;
|
||||
size?: number;
|
||||
|
||||
metadata: {
|
||||
title?: string | undefined;
|
||||
caption?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
[fieldName: string]: string | string[] | Date | number | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export * from './DraftField';
|
||||
export * from './Framework';
|
||||
export * from './GitSettings';
|
||||
export * from './LoadingType';
|
||||
export * from './MediaContentType';
|
||||
export * from './MediaPaths';
|
||||
export * from './Mode';
|
||||
export * from './PanelSettings';
|
||||
|
||||
Reference in New Issue
Block a user