diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e986b5..4343061a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#388](https://github.com/estruyf/vscode-front-matter/issues/388): New stop server action has been added to the panel - [#390](https://github.com/estruyf/vscode-front-matter/issues/390): Implement another JSON parser in order to be able to parse the `frontmatter.json` file better - [#394](https://github.com/estruyf/vscode-front-matter/issues/394): Ordering of snippet fields is based on their field definition +- [#395](https://github.com/estruyf/vscode-front-matter/issues/395): Added support for custom snippet fields on media snippets - [#402](https://github.com/estruyf/vscode-front-matter/issues/402): Custom sorting of content now supports `number` fields ### ⚡️ Optimizations diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 97ff9240..fc82963d 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -364,6 +364,7 @@ export class Article { const contentType = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE; const position = editor.selection.active; + const selectionText = editor.document.getText(editor.selection); await vscode.commands.executeCommand(COMMAND_NAME.dashboard, { type: "media", @@ -371,7 +372,8 @@ export class Article { pageBundle: !!contentType.pageBundle, filePath: editor.document.uri.fsPath, fieldName: basename(editor.document.uri.fsPath), - position + position, + selection: selectionText } } as DashboardData); diff --git a/src/constants/TelemetryEvent.ts b/src/constants/TelemetryEvent.ts index 302a1327..a477a4f4 100644 --- a/src/constants/TelemetryEvent.ts +++ b/src/constants/TelemetryEvent.ts @@ -22,6 +22,7 @@ export const TelemetryEvent = { uploadMedia: 'uploadMedia', refreshMedia: 'refreshMedia', deleteMedia: 'deleteMedia', + insertContentSnippet: 'insertContentSnippet', insertMediaToContent: 'insertMediaToContent', insertFileToContent: 'insertFileToContent', updateMediaMetadata: 'updateMediaMetadata', diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 54c6b619..46e7bc38 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -19,6 +19,7 @@ import { Alert } from '../Modals/Alert'; import { InfoDialog } from '../Modals/InfoDialog'; import { DetailsSlideOver } from './DetailsSlideOver'; import { usePopper } from 'react-popper'; +import { MediaSnippetForm } from './MediaSnippetForm'; export interface IItemProps { media: MediaInfo; @@ -29,7 +30,10 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi const [ showAlert, setShowAlert ] = useState(false); const [ showForm, setShowForm ] = useState(false); const [ showSnippetSelection, setShowSnippetSelection ] = useState(false); + const [ snippet, setSnippet ] = useState(undefined); 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); @@ -136,6 +140,9 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi } }, [mediaSnippets]); + /** + * Process the snippet + */ const processSnippet = useCallback((snippet: Snippet) => { setShowSnippetSelection(false); @@ -151,7 +158,24 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi mediaHeight: media?.dimensions?.height?.toString() || "", }; - const output = SnippetParser.render(snippet.body, fieldData, snippet?.openingTags, snippet?.closingTags); + if (snippet.fields.length === 0) { + setShowSnippetFormDialog(false); + setMediaData(undefined); + + const output = SnippetParser.render(snippet.body, fieldData, snippet?.openingTags, snippet?.closingTags); + insertMediaSnippetToArticle(output); + } else { + setSnippet(snippet); + setShowSnippetFormDialog(true); + setMediaData(fieldData); + } + }, [alt, caption, media, settings, viewData, mediaSnippets]); + + /** + * Insert the media snippet + */ + const insertMediaSnippetToArticle = useCallback((output: string) => { + const relPath = getRelPath(); Messenger.send(DashboardMessage.insertMedia, { relPath: parseWinPath(relPath) || "", @@ -160,7 +184,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi position: viewData?.data?.position || null, snippet: output }); - }, [alt, caption, media, settings, viewData, mediaSnippets]); + }, [viewData]); const deleteMedia = () => { setShowAlert(true); @@ -300,6 +324,12 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi return null; }, [media]); + const clearFormData = () => { + setShowSnippetFormDialog(false); + setSnippet(undefined); + setMediaData(undefined); + } + useEffect(() => { if (media.alt !== alt) { setAlt(media.alt); @@ -319,6 +349,12 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi } }, [media.fsPath]); + useEffect(() => { + if (!viewData?.data?.filePath) { + clearFormData(); + } + }, [viewData]); + return ( <>
  • @@ -540,6 +576,18 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi trigger={confirmDeletion} /> ) } + + { + (showSnippetFormDialog && snippet && mediaData) && ( + + ) + } ); }; diff --git a/src/dashboardWebView/components/Media/MediaSnippetForm.tsx b/src/dashboardWebView/components/Media/MediaSnippetForm.tsx new file mode 100644 index 00000000..2c345a28 --- /dev/null +++ b/src/dashboardWebView/components/Media/MediaSnippetForm.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { useRef } from 'react'; +import { useRecoilValue } from 'recoil'; +import { MediaInfo, Snippet } from '../../../models'; +import { ViewDataSelector } from '../../state'; +import { FormDialog } from '../Modals/FormDialog'; +import SnippetForm, { SnippetFormHandle } from '../SnippetsView/SnippetForm'; + +export interface IMediaSnippetFormProps { + media: MediaInfo; + snippet: Snippet; + mediaData: any; + onDismiss: () => void; + onInsert: (output: string) => void; +} + +export const MediaSnippetForm: React.FunctionComponent = ({ media, snippet, mediaData, onDismiss, onInsert }: React.PropsWithChildren) => { + const viewData = useRecoilValue(ViewDataSelector); + const formRef = useRef(null); + + const insertToArticle = () => { + formRef.current?.onSave(); + onDismiss(); + }; + + return ( + + + + + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx index 1c5a0b3c..498c1651 100644 --- a/src/dashboardWebView/components/SnippetsView/Item.tsx +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -7,7 +7,6 @@ import { FeatureFlag } from '../../../components/features/FeatureFlag'; import { FEATURE_FLAG } from '../../../constants'; import { SnippetParser } from '../../../helpers/SnippetParser'; import { Snippet, Snippets } from '../../../models'; -import { FileIcon } from '../../../panelWebView/components/Icons/FileIcon'; import { DashboardMessage } from '../../DashboardMessage'; import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state'; import { QuickAction } from '../Menu'; diff --git a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx index 2695977f..a87ad55a 100644 --- a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx +++ b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx @@ -13,13 +13,15 @@ import { SnippetInputField } from './SnippetInputField'; export interface ISnippetFormProps { snippet: Snippet; selection: string | undefined; + mediaData?: any; + onInsert?: (mediaData: any) => void; } export interface SnippetFormHandle { onSave: () => void; } -const SnippetForm: React.ForwardRefRenderFunction = ({ snippet, selection }, ref) => { +const SnippetForm: React.ForwardRefRenderFunction = ({ snippet, selection, mediaData, onInsert }, ref) => { const viewData = useRecoilValue(ViewDataSelector); const [ fields, setFields ] = useState([]); const settings = useRecoilValue(SettingsAtom); @@ -38,6 +40,16 @@ const SnippetForm: React.ForwardRefRenderFunction { + if (!mediaData) { + return ""; + } + + if (mediaData[fieldName]) { + return mediaData[fieldName]; + } + }, [mediaData]); + const snippetBody = useMemo(() => { let body = typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`); @@ -63,10 +75,14 @@ const SnippetForm: React.ForwardRefRenderFunction