mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 09:23:00 +02:00
#113 - Implemented local database for media
This commit is contained in:
Generated
+32
@@ -359,6 +359,15 @@
|
||||
"integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node-persist": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-persist/-/node-persist-3.1.2.tgz",
|
||||
"integrity": "sha512-aLFUB1951wOfR+tZ4f3TudLPblo9+PfnduMh6feuOTijD0Q6YkMdqPXSgQIjI23FGmCuoZaIZ9x6cvEG/TdiSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
@@ -4106,6 +4115,23 @@
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"node-json-db": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-1.3.0.tgz",
|
||||
"integrity": "sha512-3IK9KuqfKdK12zFKtzmnD6Y5J9qL0TY0gnnZ5XKpzdhCP019zOxPxGCaH6cmIqiho2ymFgcTMKQeJvYkbBFraQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "~1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-libs-browser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||
@@ -4145,6 +4171,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-persist": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.0.tgz",
|
||||
"integrity": "sha512-/j+fd/u71wNgKf3V2bx4tnDm+3GvLnlCuvf2MXbJ3wern+67IAb6zN9Leu1tCWPlPNZ+v1hLSibVukkPK2HqJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.75",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz",
|
||||
|
||||
@@ -626,6 +626,7 @@
|
||||
"@types/lodash.uniqby": "4.7.6",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "10.17.48",
|
||||
"@types/node-persist": "^3.1.2",
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-datepicker": "^4.1.7",
|
||||
"@types/react-dom": "17.0.0",
|
||||
@@ -642,6 +643,8 @@
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"mdast-util-from-markdown": "1.0.0",
|
||||
"node-json-db": "^1.3.0",
|
||||
"node-persist": "^3.1.0",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-loader": "4.3.0",
|
||||
"react": "17.0.1",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { decodeBase64Image } from '../helpers/decodeBase64Image';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
|
||||
|
||||
export class Dashboard {
|
||||
@@ -31,6 +32,7 @@ export class Dashboard {
|
||||
private static media: MediaInfo[] = [];
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
private static _viewData: DashboardData | undefined;
|
||||
private static mediaLib: MediaLibrary;
|
||||
|
||||
public static get viewData(): DashboardData | undefined {
|
||||
return Dashboard._viewData;
|
||||
@@ -40,6 +42,8 @@ export class Dashboard {
|
||||
* Init the dashboard
|
||||
*/
|
||||
public static async init() {
|
||||
this.mediaLib = MediaLibrary.getInstance();
|
||||
|
||||
const openOnStartup = SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART);
|
||||
if (openOnStartup) {
|
||||
Dashboard.open();
|
||||
@@ -160,7 +164,7 @@ export class Dashboard {
|
||||
env.clipboard.writeText(msg.data);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
Dashboard.media = [];
|
||||
Dashboard.resetMedia();
|
||||
Dashboard.getMedia(0, msg?.data?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
@@ -172,9 +176,16 @@ export class Dashboard {
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
Dashboard.insertImage(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
Dashboard.updateMediaMetadata(msg?.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static resetMedia() {
|
||||
Dashboard.media = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an image into the front matter or contents
|
||||
@@ -197,7 +208,7 @@ export class Dashboard {
|
||||
const line = data.position.line;
|
||||
const character = data.position.character;
|
||||
if (line) {
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
} else {
|
||||
@@ -295,14 +306,18 @@ export class Dashboard {
|
||||
files = files.slice(page * 16, ((page + 1) * 16));
|
||||
files = files.map((file) => {
|
||||
try {
|
||||
const metadata = Dashboard.mediaLib.get(file.fsPath);
|
||||
|
||||
return {
|
||||
...file,
|
||||
stats: statSync(file.fsPath)
|
||||
stats: statSync(file.fsPath),
|
||||
...metadata
|
||||
};
|
||||
} catch (e) {
|
||||
return {...file, stats: undefined};
|
||||
}
|
||||
}).filter(f => f.stats !== undefined);
|
||||
});
|
||||
files = files.filter(f => f.stats !== undefined);
|
||||
|
||||
const folders = [...new Set(Dashboard.media.map((file) => {
|
||||
let relFolderPath = wsFolder ? file.fsPath.substring(wsFolder.fsPath.length + 1) : file.fsPath;
|
||||
@@ -475,6 +490,14 @@ export class Dashboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the selected file
|
||||
*/
|
||||
private static async updateMediaMetadata({ file, page, folder, description = null, alt = null }: { file:string; page: number; folder: string | null; description: string | null; alt: string | null; }) {
|
||||
Dashboard.mediaLib.set(file, description, alt);
|
||||
Dashboard.getMedia(page || 0, folder || "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Post data to the dashboard
|
||||
* @param msg
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models";
|
||||
import uniqBy = require("lodash.uniqby");
|
||||
import { Template } from "./Template";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
import { CONTEXT } from "../constants/context";
|
||||
import { Settings } from "../helpers";
|
||||
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
@@ -14,4 +14,5 @@ export enum DashboardMessage {
|
||||
uploadMedia = 'uploadMedia',
|
||||
deleteMedia = 'deleteMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
updateMediaMetadata = 'updateMediaMetadata',
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import useDarkMode from '../../hooks/useDarkMode';
|
||||
import usePages from '../hooks/usePages';
|
||||
import { WelcomeScreen } from './WelcomeScreen';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector, ViewDataAtom } from '../state';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PencilIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { LightboxAtom, PageSelector, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { Metadata } from '../Modals/Metadata';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -17,7 +19,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const [ , setLightbox ] = useRecoilState(LightboxAtom);
|
||||
const [ showAlert, setShowAlert ] = React.useState(false);
|
||||
const [ showForm, setShowForm ] = React.useState(false);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const [ description, setDescription ] = React.useState(media.description);
|
||||
const [ alt, setAlt ] = React.useState(media.alt);
|
||||
const page = useRecoilValue(PageSelector);
|
||||
|
||||
const parseWinPath = (path: string | undefined) => {
|
||||
return path?.split(`\\`).join(`/`);
|
||||
@@ -59,18 +65,25 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null
|
||||
position: viewData?.data?.position || null,
|
||||
alt: alt || "",
|
||||
description: description || ""
|
||||
});
|
||||
};
|
||||
|
||||
const insertSnippet = () => {
|
||||
const relPath = getRelPath();
|
||||
let snippet = settings?.mediaSnippet.join("\n");
|
||||
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
|
||||
snippet = snippet?.replace("{alt}", alt || "");
|
||||
snippet = snippet?.replace("{description}", description || "");
|
||||
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath,
|
||||
fieldName: viewData?.data?.fieldName,
|
||||
position: viewData?.data?.position || null,
|
||||
snippet: settings?.mediaSnippet.join("\n").replace("{mediaUrl}", parseWinPath(relPath) || "")
|
||||
snippet
|
||||
});
|
||||
};
|
||||
|
||||
@@ -100,6 +113,33 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
setLightbox(media.vsPath || "");
|
||||
};
|
||||
|
||||
const updateMetadata = () => {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const submitMetadata = () => {
|
||||
Messenger.send(DashboardMessage.updateMediaMetadata, {
|
||||
file: media.fsPath,
|
||||
description,
|
||||
alt,
|
||||
folder: selectedFolder,
|
||||
page
|
||||
});
|
||||
setShowForm(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (media.alt !== description) {
|
||||
setAlt(media.alt);
|
||||
}
|
||||
}, [media.alt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (media.description !== description) {
|
||||
setDescription(media.description);
|
||||
}
|
||||
}, [media.description]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
|
||||
@@ -112,7 +152,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
</div>
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-10`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<>
|
||||
@@ -137,6 +177,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button title={`Edit metadata`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={updateMetadata}>
|
||||
<PencilIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Edit metadata</span>
|
||||
</button>
|
||||
<button title={`Copy media path`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={copyToClipboard}>
|
||||
@@ -156,19 +202,77 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
|
||||
{basename(parseWinPath(media.fsPath) || "")}
|
||||
</p>
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
<b className={`mr-2`}>Folder:</b> {getFolder()}
|
||||
{
|
||||
media.description && (
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Description:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.description}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
media.alt && (
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Alt:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.alt}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-2`}>Folder:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getFolder()}</span>
|
||||
</p>
|
||||
{
|
||||
media?.stats?.size && (
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
<b className={`mr-1`}>Size:</b> {calculateSize()}
|
||||
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
|
||||
<b className={`mr-1`}>Size:</b>
|
||||
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{calculateSize()}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{
|
||||
showForm && (
|
||||
<Metadata
|
||||
title={`Set metadata for: ${basename(parseWinPath(media.fsPath) || "")}`}
|
||||
description={`Please specify the metadata you want to set for the file.`}
|
||||
okBtnText={`Save`}
|
||||
cancelBtnText={`Cancel`}
|
||||
dismiss={() => setShowForm(false)}
|
||||
trigger={submitMetadata}>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
|
||||
Description
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
rows={3}
|
||||
className="py-1 px-2 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none w-full"
|
||||
value={description || ''}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
|
||||
Alt tag value
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
className="py-1 px-2 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none w-full"
|
||||
value={alt || ''}
|
||||
onChange={(e) => setAlt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Metadata>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showAlert && (
|
||||
<Alert
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LoadingAtom, MediaTotalSelector, SelectedMediaFolderSelector } from '../../state';
|
||||
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector } from '../../state';
|
||||
import { FolderSelection } from './FolderSelection';
|
||||
import { LIMIT } from './Media';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
@@ -14,7 +14,7 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const [ , setLoading ] = useRecoilState(LoadingAtom);
|
||||
const [ page, setPage ] = React.useState<number>(0);
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
|
||||
const totalPages = Math.ceil(totalMedia / LIMIT) - 1;
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { Fragment, useRef } from 'react';
|
||||
|
||||
export interface IMetadataProps {
|
||||
title: string;
|
||||
description: string;
|
||||
okBtnText: string;
|
||||
cancelBtnText: string;
|
||||
|
||||
dismiss: () => void;
|
||||
trigger: () => void;
|
||||
}
|
||||
|
||||
export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, description, cancelBtnText, okBtnText, dismiss, trigger, children}: React.PropsWithChildren<IMetadataProps>) => {
|
||||
|
||||
const cancelButtonRef = useRef(null);
|
||||
|
||||
|
||||
return (
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
<Dialog className="fixed z-10 inset-0 overflow-y-auto" initialFocus={cancelButtonRef} onClose={() => dismiss()}>
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-vulcan-500 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className="inline-block align-bottom bg-white dark:bg-vulcan-500 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 border-whisper-900">
|
||||
<div>
|
||||
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-vulcan-300 dark:text-whisper-900">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-vulcan-500 dark:text-whisper-500">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
{children}
|
||||
</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-md border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
onClick={() => trigger()}
|
||||
>
|
||||
{okBtnText}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
|
||||
onClick={() => dismiss()}
|
||||
ref={cancelButtonRef}
|
||||
>
|
||||
{cancelBtnText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { PageAtom } from '..';
|
||||
|
||||
export const PageSelector = selector({
|
||||
key: 'PageSelector',
|
||||
get: ({get}) => {
|
||||
return get(PageAtom);
|
||||
}
|
||||
});
|
||||
@@ -5,6 +5,7 @@ export * from './GroupingSelector';
|
||||
export * from './LoadingSelector';
|
||||
export * from './MediaFoldersSelector';
|
||||
export * from './MediaTotalSelector';
|
||||
export * from './PageSelector';
|
||||
export * from './SearchSelector';
|
||||
export * from './SelectedMediaFolderSelector';
|
||||
export * from './SettingsSelector';
|
||||
|
||||
@@ -167,12 +167,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Inserting an image in Markdown
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertImage, Article.insertImage));
|
||||
|
||||
vscode.workspace.onDidRenameFiles(async (e) => {
|
||||
if (e.files.length > 0) {
|
||||
console.log('Renamed files: ', e.files);
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe all commands
|
||||
subscriptions.push(
|
||||
insertTags,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { EXTENSION_NAME } from '../constants';
|
||||
import { Notifications } from './Notifications';
|
||||
|
||||
export class FilesHelper {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { workspace } from 'vscode';
|
||||
import { JsonDB } from 'node-json-db/dist/JsonDB';
|
||||
import { join } from 'path';
|
||||
import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders';
|
||||
|
||||
interface MediaRecord {
|
||||
description: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export class MediaLibrary {
|
||||
private db: JsonDB;
|
||||
private static instance: MediaLibrary;
|
||||
|
||||
private constructor() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
this.db = new JsonDB(join(wsFolder?.fsPath || "", '.frontmatter/mediaDb.json'), true, false, '/');
|
||||
|
||||
workspace.onDidRenameFiles(e => {
|
||||
e.files.forEach(f => {
|
||||
// Check if file is an image
|
||||
if (f.oldUri.path.endsWith('.jpeg') ||
|
||||
f.oldUri.path.endsWith('.jpg') ||
|
||||
f.oldUri.path.endsWith('.png') ||
|
||||
f.oldUri.path.endsWith('.gif')) {
|
||||
this.rename(f.oldUri.fsPath, f.newUri.fsPath);
|
||||
Dashboard.resetMedia();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static getInstance(): MediaLibrary {
|
||||
if (!MediaLibrary.instance) {
|
||||
MediaLibrary.instance = new MediaLibrary();
|
||||
}
|
||||
|
||||
return MediaLibrary.instance;
|
||||
}
|
||||
|
||||
public get(id: string): MediaRecord | undefined {
|
||||
try {
|
||||
const fileId = this.parsePath(id);
|
||||
if (this.db.exists(fileId)) {
|
||||
return this.db.getData(fileId);
|
||||
}
|
||||
return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public set(id: string, description: string | null = null, alt: string | null = null): void {
|
||||
const fileId = this.parsePath(id);
|
||||
this.db.push(fileId, { description, alt }, true);
|
||||
}
|
||||
|
||||
public rename(oldId: string, newId: string): void {
|
||||
const fileId = this.parsePath(oldId);
|
||||
const newFileId = this.parsePath(newId);
|
||||
const data = this.db.getData(fileId);
|
||||
if (data) {
|
||||
this.db.delete(fileId);
|
||||
this.db.push(newFileId, data, true);
|
||||
}
|
||||
}
|
||||
|
||||
private parsePath(path: string) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = path.replace(wsFolder?.fsPath || "", WORKSPACE_PLACEHOLDER);
|
||||
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath.toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,6 @@ export interface MediaInfo {
|
||||
fsPath: string;
|
||||
vsPath: string | undefined;
|
||||
stats: Stats | undefined;
|
||||
description?: string | undefined;
|
||||
alt?: string | undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user