From 460c4964f68d8d9b0958db2f6c76c2001e021153 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 7 Feb 2024 11:49:03 +0100 Subject: [PATCH 1/3] #666 - First steps to implement media content types --- package.json | 353 ++++++++++-------- src/constants/settings.ts | 1 + .../components/Media/DetailsForm.tsx | 227 +++++++++++ .../components/Media/DetailsSlideOver.tsx | 220 +++-------- .../components/Media/Item.tsx | 42 +-- .../components/Media/MediaSnippetForm.tsx | 4 +- .../components/SnippetsView/SnippetForm.tsx | 4 + src/dashboardWebView/models/Settings.ts | 6 + src/helpers/DashboardSettings.ts | 18 +- src/helpers/MediaHelpers.ts | 15 +- src/models/MediaContentType.ts | 32 ++ src/models/MediaPaths.ts | 10 +- src/models/index.ts | 1 + 13 files changed, 566 insertions(+), 367 deletions(-) create mode 100644 src/dashboardWebView/components/Media/DetailsForm.tsx create mode 100644 src/models/MediaContentType.ts diff --git a/package.json b/package.json index 5a5051db..3f45a72c 100644 --- a/package.json +++ b/package.json @@ -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 } -} +} \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 95379f7f..4acb29b4 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -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'; diff --git a/src/dashboardWebView/components/Media/DetailsForm.tsx b/src/dashboardWebView/components/Media/DetailsForm.tsx new file mode 100644 index 00000000..8de191b8 --- /dev/null +++ b/src/dashboardWebView/components/Media/DetailsForm.tsx @@ -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 = ({ + media, + isImageFile, + isVideoFile, + onDismiss, +}: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsAtom); + const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); + const page = useRecoilValue(PageSelector); + + const [filename, setFilename] = React.useState(media.filename); + const [unmapped, setUnmapped] = React.useState([]); + 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 ( +
+ +
+ updateMetadata("title", e)} /> +
+
+ ); + } + + if (field.name === "caption") { + if (isImageFile || isVideoFile) { + return ( +
+ +
+ updateMetadata("caption", e)} isTextArea /> +
+
+ ) + } else { + return null; + } + } + + if (field.name === "alt") { + if (isImageFile) { + return ( +
+ +
+ updateMetadata("alt", e)} isTextArea /> +
+
+ ) + } else { + return null; + } + } + + return ( +
+ +
+ updateMetadata(field.name, e)} isTextArea={!field.single} /> +
+
+ ); + }); + }, [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(DashboardMessage.getUnmappedMedia, media.filename).then((result) => { + setUnmapped(result); + }); + }, [media.filename]); + + return ( + <> +

+ {l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)} +

+ + { + unmapped && unmapped.length > 0 && ( +
+

+ {l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)} +

+
    + { + unmapped.map((item) => ( +
  • + +
  • + )) + } +
+
+ ) + } + +

+ {l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)} +

+ +
+
+ +
+ setFilename(`${e}.${fileInfo.extension}`)} /> + +
+ .{fileInfo?.extension} +
+
+
+ + {formFields} +
+ +
+ + +
+ + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/DetailsSlideOver.tsx b/src/dashboardWebView/components/Media/DetailsSlideOver.tsx index 60097964..b689711b 100644 --- a/src/dashboardWebView/components/Media/DetailsSlideOver.tsx +++ b/src/dashboardWebView/components/Media/DetailsSlideOver.tsx @@ -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 = isImageFile, isVideoFile }: React.PropsWithChildren) => { - const [filename, setFilename] = React.useState(media.filename); - const [caption, setCaption] = React.useState(media.caption); - const [title, setTitle] = React.useState(media.title); - const [unmapped, setUnmapped] = React.useState([]); - 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(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( + + ); + + fields?.forEach((field) => { + if (field.name === "title") { + items.push( + + ); + } else if (field.name === "caption") { + if (isImageFile) { + items.push( + + ); + } + } else if (field.name === "alt") { + if (isImageFile) { + items.push( + + ); + } + } else { + items.push( + + ); + } + }); + + return items; + }, [fields, media.metadata]); return ( @@ -168,101 +160,11 @@ export const DetailsSlideOver: React.FunctionComponent =
{/* EDIT METADATA FORM */} {showForm && ( - <> -

- {l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)} -

- - { - unmapped && unmapped.length > 0 && ( -
-

- {l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)} -

-
    - { - unmapped.map((item) => ( -
  • - -
  • - )) - } -
-
- ) - } - -

- {l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)} -

-
-
- -
- setFilename(`${e}.${extension}`)} /> - -
- .{extension} -
-
-
- -
- -
- setTitle(e)} /> -
-
- - {(isImageFile || isVideoFile) && ( -
- -
- setCaption(e)} isTextArea /> -
-
- )} - {isImageFile && ( -
- -
- setAlt(e)} isTextArea /> -
-
- )} -
- -
- - -
- + )} {!showForm && ( @@ -275,15 +177,7 @@ export const DetailsSlideOver: React.FunctionComponent =
- - - - {isImageFile && ( - <> - - - - )} + {detailItems}
)} diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index dc45b7cc..ca8168be 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -55,8 +55,6 @@ export const Item: React.FunctionComponent = ({ const [showDetails, setShowDetails] = useState(false); const [showSnippetFormDialog, setShowSnippetFormDialog] = useState(false); const [mediaData, setMediaData] = useState(undefined); - const [caption, setCaption] = useState(media.caption); - const [alt, setAlt] = useState(media.alt); const [filename, setFilename] = useState(null); const settings = useRecoilValue(SettingsSelector); const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); @@ -150,7 +148,7 @@ export const Item: React.FunctionComponent = ({ 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 = ({ 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 = ({ 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 = ({ setMediaData(fieldData); } }, - [alt, caption, media, settings, viewData, mediaSnippets] + [media, settings, viewData, mediaSnippets] ); /** @@ -404,18 +400,6 @@ export const Item: React.FunctionComponent = ({ 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 = ({

{basename(parseWinPath(media.fsPath) || '')}

- {!isImageFile && media.title && ( + {!isImageFile && media.metadata.title && (

{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}: - {media.title} + {media.metadata.title}

)} - {media.caption && ( + {media.metadata.caption && (

{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}: - {media.caption} + {media.metadata.caption}

)} - {!media.caption && media.alt && ( + {!media.metadata.caption && media.metadata.alt && (

{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}: - {media.alt} + {media.metadata.alt}

)} {(media?.size || media?.dimensions) && ( diff --git a/src/dashboardWebView/components/Media/MediaSnippetForm.tsx b/src/dashboardWebView/components/Media/MediaSnippetForm.tsx index 1c9def40..2e7c7750 100644 --- a/src/dashboardWebView/components/Media/MediaSnippetForm.tsx +++ b/src/dashboardWebView/components/Media/MediaSnippetForm.tsx @@ -33,8 +33,8 @@ export const MediaSnippetForm: React.FunctionComponent = return ( (SETTING_SNIPPETS_WRAPPER), isBacker: await ext.getState(CONTEXT.backer, 'global'), websiteUrl: Settings.get(SETTING_WEBSITE_URL), + media: { + contentTypes: Settings.get(SETTING_MEDIA_CONTENTTYPES) || [ + DEFAULT_MEDIA_CONTENT_TYPE + ] + }, lastUpdated: new Date().getTime() } as ISettings; diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 40fe953c..d6ab72e4 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -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) ); } diff --git a/src/models/MediaContentType.ts b/src/models/MediaContentType.ts new file mode 100644 index 00000000..e8c94963 --- /dev/null +++ b/src/models/MediaContentType.ts @@ -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 + } + ] +}; diff --git a/src/models/MediaPaths.ts b/src/models/MediaPaths.ts index cf94c1d6..0bd118bd 100644 --- a/src/models/MediaPaths.ts +++ b/src/models/MediaPaths.ts @@ -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; + }; } diff --git a/src/models/index.ts b/src/models/index.ts index 1f4ef199..00cb2674 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -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'; From 06b8a579a87540e963b9b555d744fbc648ef88aa Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 8 Feb 2024 11:45:15 +0100 Subject: [PATCH 2/3] #745 - Fix double date formatting --- src/panelWebView/components/Fields/DateTimeField.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/panelWebView/components/Fields/DateTimeField.tsx b/src/panelWebView/components/Fields/DateTimeField.tsx index d28d190e..e80b32be 100644 --- a/src/panelWebView/components/Fields/DateTimeField.tsx +++ b/src/panelWebView/components/Fields/DateTimeField.tsx @@ -11,7 +11,7 @@ import { LocalizationKey } from '../../../localization'; export interface IDateTimeFieldProps extends BaseFieldProps { format?: string; - onChange: (date: string) => void; + onChange: (date: Date) => void; } type InputProps = JSX.IntrinsicElements['input']; @@ -37,11 +37,7 @@ export const DateTimeField: React.FunctionComponent = ({ const onDateChange = React.useCallback((date: Date) => { setDateValue(date); - - const dateValue = DateHelper.format(date, format); - if (dateValue) { - onChange(dateValue); - } + onChange(date); }, [format, onChange]); const showRequiredState = useMemo(() => { From 183e77b77b75346a6eba62a4d738fdb9bee57381 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 13 Feb 2024 09:28:19 +0100 Subject: [PATCH 3/3] #666 - Support config splitting --- src/helpers/SettingsHelper.ts | 47 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 5090bff4..92ab8d5b 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -1,17 +1,19 @@ -import { - EXTENSION_NAME, - SETTING_CONFIG_DYNAMIC_FILE_PATH, - SETTING_PROJECTS -} from './../constants/settings'; import { parseWinPath } from './parseWinPath'; import { Telemetry } from './Telemetry'; import { Notifications } from './Notifications'; -import { commands, Uri, workspace, window } from 'vscode'; -import * as vscode from 'vscode'; +import { + commands, + Uri, + workspace, + window, + WorkspaceConfiguration, + FileSystemWatcher, + Disposable, + ProgressLocation +} from 'vscode'; import { ContentType, CustomTaxonomy, Project } from '../models'; import { - SETTING_TAXONOMY_TAGS, - SETTING_TAXONOMY_CATEGORIES, + EXTENSION_NAME, CONFIG_KEY, CONTEXT, ExtensionState, @@ -35,8 +37,13 @@ import { SETTING_GLOBAL_NOTIFICATIONS, SETTING_GLOBAL_NOTIFICATIONS_DISABLED, SETTING_MEDIA_SUPPORTED_MIMETYPES, + SETTING_MEDIA_CONTENTTYPES, SETTING_COMMA_SEPARATED_FIELDS, - SETTING_REMOVE_QUOTES + SETTING_REMOVE_QUOTES, + SETTING_CONFIG_DYNAMIC_FILE_PATH, + SETTING_PROJECTS, + SETTING_TAXONOMY_TAGS, + SETTING_TAXONOMY_CATEGORIES } from '../constants'; import { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; @@ -59,13 +66,13 @@ export class Settings { public static globalConfigFolder = '.frontmatter/config'; public static globalConfigPath: string | undefined = undefined; public static globalConfig: any; - private static config: vscode.WorkspaceConfiguration; + private static config: WorkspaceConfiguration; private static isInitialized: boolean = false; private static listeners: { id: string; callback: (global?: any) => void }[] = []; - private static fileCreationWatcher: vscode.FileSystemWatcher | undefined; - private static fileChangeWatcher: vscode.FileSystemWatcher | undefined; - private static fileSaveListener: vscode.Disposable; - private static fileDeleteListener: vscode.Disposable; + private static fileCreationWatcher: FileSystemWatcher | undefined; + private static fileChangeWatcher: FileSystemWatcher | undefined; + private static fileSaveListener: Disposable; + private static fileDeleteListener: Disposable; private static readConfigPromise: Promise | undefined = undefined; private static project: Project | undefined = undefined; private static configDebouncer = debounceCallback(); @@ -109,10 +116,10 @@ export class Settings { commands.registerCommand(COMMAND_NAME.settingsRefresh, Settings.refreshConfig); } - Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); + Settings.config = workspace.getConfiguration(CONFIG_KEY); Settings.attachListener('settings-init', async () => { - Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); + Settings.config = workspace.getConfiguration(CONFIG_KEY); }); Settings.onConfigChange(); @@ -674,7 +681,7 @@ export class Settings { try { await window.withProgress( { - location: vscode.ProgressLocation.Notification, + location: ProgressLocation.Notification, title: l10n.t( LocalizationKey.helpersSettingsHelperReadConfigProgressTitle, EXTENSION_NAME @@ -866,6 +873,10 @@ export class Settings { else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) { Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CONTENT_TYPES, 'name', configJson); } + // Media Content types + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_MEDIA_CONTENTTYPES)) { + Settings.updateGlobalConfigArraySetting(SETTING_MEDIA_CONTENTTYPES, 'name', configJson); + } // Data files else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FILES)) { Settings.updateGlobalConfigArraySetting(SETTING_DATA_FILES, 'id', configJson);