mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-08 09:53:20 +02:00
#104 - First steps to preview image selection
This commit is contained in:
@@ -454,6 +454,23 @@ input:checked + .field__toggle__slider:before {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-height: 16rem;
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__remove {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
}
|
||||
|
||||
.metadata_field__preview_image__remove:hover {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
/* File list */
|
||||
.file_list vscode-label {
|
||||
border-bottom: 1px solid var(--vscode-foreground);
|
||||
|
||||
@@ -17,10 +17,12 @@ import { Settings } from '../pagesView/models/Settings';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { ViewType } from '../pagesView/state';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { EditorHelper, WebviewHelper } from '@estruyf/vscode';
|
||||
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
|
||||
import { decodeBase64Image } from '../helpers/decodeBase64Image';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../webview/ExplorerView';
|
||||
|
||||
|
||||
export class Dashboard {
|
||||
@@ -28,6 +30,7 @@ export class Dashboard {
|
||||
private static isDisposed: boolean = true;
|
||||
private static media: MediaInfo[] = [];
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
private static viewData: DashboardData | undefined;
|
||||
|
||||
/**
|
||||
* Init the dashboard
|
||||
@@ -43,7 +46,9 @@ export class Dashboard {
|
||||
/**
|
||||
* Open or reveal the dashboard
|
||||
*/
|
||||
public static async open() {
|
||||
public static async open(data?: DashboardData) {
|
||||
Dashboard.viewData = data;
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.reveal();
|
||||
} else {
|
||||
@@ -93,8 +98,8 @@ export class Dashboard {
|
||||
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
|
||||
|
||||
Dashboard.webview.onDidChangeViewState(() => {
|
||||
if (this.webview?.visible) {
|
||||
console.log(`Dashboard opened`);
|
||||
if (!this.webview?.visible) {
|
||||
Dashboard.viewData = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,6 +113,11 @@ export class Dashboard {
|
||||
|
||||
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getViewType:
|
||||
if (Dashboard.viewData) {
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: Dashboard.viewData });
|
||||
}
|
||||
break;
|
||||
case DashboardMessage.getData:
|
||||
Dashboard.getSettings();
|
||||
Dashboard.getPages();
|
||||
@@ -151,6 +161,13 @@ export class Dashboard {
|
||||
case DashboardMessage.deleteMedia:
|
||||
Dashboard.deleteFile(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
if (msg.data?.file && msg.data?.image) {
|
||||
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
|
||||
await EditorHelper.showFile(msg.data.file);
|
||||
ExplorerView.getInstance(extensionUri).updateMetadata({field: `preview`, value: msg.data.image});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+4
-5
@@ -5,14 +5,13 @@ import { Folders } from './commands/Folders';
|
||||
import { Preview } from './commands/Preview';
|
||||
import { Project } from './commands/Project';
|
||||
import { Template } from './commands/Template';
|
||||
import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from './constants/Extension';
|
||||
import { COMMAND_NAME } from './constants/Extension';
|
||||
import { TaxonomyType } from './models';
|
||||
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
|
||||
import { TagType } from './viewpanel/TagType';
|
||||
import { ExplorerView } from './webview/ExplorerView';
|
||||
import { Extension } from './helpers/Extension';
|
||||
import { basename } from 'path';
|
||||
import { Notifications } from './helpers/Notifications';
|
||||
import { DashboardData } from './models/DashboardData';
|
||||
|
||||
let frontMatterStatusBar: vscode.StatusBarItem;
|
||||
let statusDebouncer: { (fnc: any, time: number): void; };
|
||||
@@ -36,8 +35,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Pages dashboard
|
||||
Dashboard.init();
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, () => {
|
||||
Dashboard.open();
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Dashboard.open(data);
|
||||
}));
|
||||
|
||||
if (!extension.getVersion().usedVersion) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DashboardData {
|
||||
type: "contents" | "media";
|
||||
data?: any;
|
||||
}
|
||||
@@ -3,4 +3,5 @@ export enum DashboardCommand {
|
||||
pages = "pages",
|
||||
settings = "settings",
|
||||
media = "media",
|
||||
viewData = "viewData",
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum DashboardMessage {
|
||||
getViewType = 'getViewType',
|
||||
getData = 'getData',
|
||||
openFile = 'openFile',
|
||||
getTheme = 'getTheme',
|
||||
@@ -12,4 +13,5 @@ export enum DashboardMessage {
|
||||
refreshMedia = 'refreshMedia',
|
||||
uploadMedia = 'uploadMedia',
|
||||
deleteMedia = 'deleteMedia',
|
||||
insertPreviewImage = 'insertPreviewImage',
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import useDarkMode from '../../hooks/useDarkMode';
|
||||
import usePages from '../hooks/usePages';
|
||||
import { WelcomeScreen } from './WelcomeScreen';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { DashboardViewSelector, ViewDataAtom } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
|
||||
@@ -31,13 +31,9 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
|
||||
return <WelcomeScreen settings={settings} />;
|
||||
}
|
||||
|
||||
if (view === "contents") {
|
||||
return (
|
||||
<Contents pages={pageItems} loading={loading} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Media />
|
||||
);
|
||||
if (view === 'media') {
|
||||
return <Media />;
|
||||
}
|
||||
|
||||
return <Contents pages={pageItems} loading={loading} />;
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClipboardCopyIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { CheckCircleIcon, ClipboardCopyIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
|
||||
export interface IItemProps {
|
||||
@@ -17,6 +17,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const [ , setLightbox ] = useRecoilState(LightboxAtom);
|
||||
const [ showAlert, setShowAlert ] = React.useState(false);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const parseWinPath = (path: string | undefined) => {
|
||||
return path?.split(`\\`).join(`/`);
|
||||
@@ -35,7 +36,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
return "";
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const getRelPath = () => {
|
||||
let relPath: string | undefined = "";
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
@@ -44,10 +45,22 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
}
|
||||
return relPath;
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || "");
|
||||
};
|
||||
|
||||
const insertToArticle = () => {
|
||||
const relPath = getRelPath();
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
file: viewData?.data?.filePath
|
||||
});
|
||||
};
|
||||
|
||||
const deleteMedia = () => {
|
||||
setShowAlert(true);
|
||||
};
|
||||
@@ -87,18 +100,32 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-10`}>
|
||||
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
|
||||
<button title={`Copy media path`}
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<button
|
||||
title={`Insert into your article`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardCopyIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Copy media path</span>
|
||||
</button>
|
||||
<button title={`Delete media`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Delete media</span>
|
||||
</button>
|
||||
onClick={insertToArticle}>
|
||||
<CheckCircleIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Insert into your article</span>
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<button title={`Copy media path`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardCopyIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Copy media path</span>
|
||||
</button>
|
||||
<button title={`Delete media`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={deleteMedia}>
|
||||
<TrashIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Delete media</span>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
|
||||
{basename(parseWinPath(media.fsPath) || "")}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo, MediaPaths } from '../../../models/MediaPaths';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { Header } from '../Header';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
@@ -27,6 +27,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
const [ , setTotal ] = useRecoilState(MediaTotalAtom);
|
||||
const [ , setFolders ] = useRecoilState(MediaFoldersAtom);
|
||||
const [ loading, setLoading ] = useRecoilState(LoadingAtom);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
acceptedFiles.forEach((file) => {
|
||||
@@ -73,6 +74,15 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
<Header settings={settings} />
|
||||
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4" {...getRootProps()}>
|
||||
|
||||
{
|
||||
viewData?.data?.filePath && (
|
||||
<div className={`text-lg text-center mb-6`}>
|
||||
<p>Select the image you want to use for your article.</p>
|
||||
<p className={`opacity-80 text-base`}>You can also drag and drop images from your desktop and select that once uploaded.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
isDragActive && (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
|
||||
import { DashboardCommand } from '../DashboardCommand';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import { Page } from '../models/Page';
|
||||
import { SettingsAtom } from '../state';
|
||||
import { DashboardViewAtom, SettingsAtom, ViewDataAtom } from '../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
|
||||
@@ -11,12 +11,20 @@ export default function useMessages() {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [pages, setPages] = useState<Page[]>([]);
|
||||
const [settings, setSettings] = useRecoilState(SettingsAtom);
|
||||
const [viewData, setViewData] = useRecoilState(ViewDataAtom);
|
||||
const [, setView] = useRecoilState(DashboardViewAtom);
|
||||
|
||||
Messenger.listen((message: MessageEvent<EventData<any>>) => {
|
||||
switch (message.data.command) {
|
||||
case DashboardCommand.loading:
|
||||
setLoading(message.data.data);
|
||||
break;
|
||||
case DashboardCommand.viewData:
|
||||
setViewData(message.data.data);
|
||||
if (message.data.data?.type === 'media') {
|
||||
setView('media');
|
||||
}
|
||||
break;
|
||||
case DashboardCommand.settings:
|
||||
setSettings(message.data.data);
|
||||
break;
|
||||
@@ -27,8 +35,9 @@ export default function useMessages() {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Messenger.send(DashboardMessage.getViewType);
|
||||
Messenger.send(DashboardMessage.getTheme);
|
||||
Messenger.send(DashboardMessage.getData);
|
||||
}, ['']);
|
||||
@@ -36,6 +45,7 @@ export default function useMessages() {
|
||||
return {
|
||||
loading,
|
||||
pages,
|
||||
viewData,
|
||||
settings
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
import { DashboardData } from '../../../models/DashboardData';
|
||||
|
||||
export const ViewDataAtom = atom<DashboardData | undefined>({
|
||||
key: 'ViewDataAtom',
|
||||
default: undefined
|
||||
});
|
||||
@@ -14,3 +14,4 @@ export * from './SortingAtom';
|
||||
export * from './TabAtom';
|
||||
export * from './TagAtom';
|
||||
export * from './ViewAtom';
|
||||
export * from './ViewDataAtom';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { ViewDataAtom } from '..';
|
||||
|
||||
export const ViewDataSelector = selector({
|
||||
key: 'ViewDataSelector',
|
||||
get: ({get}) => {
|
||||
return get(ViewDataAtom);
|
||||
}
|
||||
});
|
||||
@@ -11,4 +11,5 @@ export * from './SettingsSelector';
|
||||
export * from './SortingSelector';
|
||||
export * from './TabSelector';
|
||||
export * from './TagSelector';
|
||||
export * from './ViewDataSelector';
|
||||
export * from './ViewSelector';
|
||||
|
||||
@@ -25,4 +25,5 @@ export enum CommandToCode {
|
||||
openInEditor = "open-in-editor",
|
||||
updateMetadata = "update-metadata",
|
||||
openDashboard = "open-dashboard",
|
||||
selectImage = "select-image",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { PhotographIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { PanelSettings } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IPreviewImageFieldProps {
|
||||
label: string;
|
||||
value: string | null;
|
||||
filePath: string | null;
|
||||
onChange: (value: string | null) => void;
|
||||
}
|
||||
|
||||
export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps> = ({label, onChange, value, filePath}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
|
||||
|
||||
const selectImage = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.selectImage, { filePath });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<PhotographIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
<div className={`metadata_field__preview_image`}>
|
||||
{
|
||||
value ? (
|
||||
<div>
|
||||
<img src={value} />
|
||||
|
||||
<button onClick={() => onChange(null)} className={`metadata_field__preview_image__remove`}>Remove image</button>
|
||||
</div>
|
||||
) : (
|
||||
<button onClick={selectImage}>Select image</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { RocketIcon } from '../Icons/RocketIcon';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IToggleProps {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChanged: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export const Toggle: React.FunctionComponent<IToggleProps> = ({checked, onChanged}: React.PropsWithChildren<IToggleProps>) => {
|
||||
export const Toggle: React.FunctionComponent<IToggleProps> = ({label, checked, onChanged}: React.PropsWithChildren<IToggleProps>) => {
|
||||
const [ isChecked, setIsChecked ] = React.useState(checked);
|
||||
|
||||
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -20,9 +23,18 @@ export const Toggle: React.FunctionComponent<IToggleProps> = ({checked, onChange
|
||||
}, [ checked ]);
|
||||
|
||||
return (
|
||||
<label className="field__toggle">
|
||||
<input type="checkbox" checked={isChecked} onChange={onChange} />
|
||||
<span className="field__toggle__slider"></span>
|
||||
</label>
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<RocketIcon /> <span style={{ lineHeight: "16px"}}>{label}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
|
||||
<label className="field__toggle">
|
||||
<input type="checkbox" checked={isChecked} onChange={onChange} />
|
||||
<span className="field__toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -10,13 +10,13 @@ import { RocketIcon } from './Icons/RocketIcon';
|
||||
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { TagIcon } from './Icons/TagIcon';
|
||||
import { TagPicker } from './TagPicker';
|
||||
import { VsLabel } from './VscodeComponents';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { DateTimeField } from './Fields/DateTimeField';
|
||||
import { TextField } from './Fields/TextField';
|
||||
import { DefaultFields } from '../../constants';
|
||||
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { PreviewImageField } from './Fields/PreviewImageField';
|
||||
export interface IMetadataProps {
|
||||
settings: PanelSettings | undefined;
|
||||
metadata: { [prop: string]: string[] | string | null };
|
||||
@@ -74,7 +74,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
<DateTimeField
|
||||
label={`Article date`}
|
||||
date={publishing}
|
||||
dateFormat={settings?.date?.format}
|
||||
format={settings?.date?.format}
|
||||
onChange={(date => sendUpdate(settings?.date?.pubDate, date))} />
|
||||
|
||||
{
|
||||
@@ -82,22 +82,21 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
|
||||
<DateTimeField
|
||||
label={`Modified date`}
|
||||
date={modifying}
|
||||
dateFormat={settings?.date?.format}
|
||||
format={settings?.date?.format}
|
||||
onChange={(date => sendUpdate(settings?.date?.modDate, date))} />
|
||||
)
|
||||
}
|
||||
|
||||
<div className={`metadata_field`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label`}>
|
||||
<RocketIcon /> <span style={{ lineHeight: "16px"}}>Published</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
<Toggle
|
||||
label={`Published`}
|
||||
checked={!metadata.draft as any}
|
||||
onChanged={(checked) => sendUpdate("draft", !checked)} />
|
||||
|
||||
<Toggle
|
||||
checked={!metadata.draft as any}
|
||||
onChanged={(checked) => sendUpdate("draft", !checked)} />
|
||||
</div>
|
||||
<PreviewImageField
|
||||
label={`Preview`}
|
||||
filePath={metadata.filePath as string}
|
||||
value={metadata.preview as string}
|
||||
onChange={(value => sendUpdate('preview', value))} />
|
||||
|
||||
{
|
||||
<TagPicker type={TagType.keywords}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DashboardData } from './../models/DashboardData';
|
||||
import { Template } from './../commands/Template';
|
||||
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_COMMA_SEPARATED_FIELDS } from './../constants/settings';
|
||||
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_COMMA_SEPARATED_FIELDS, SETTINGS_CONTENT_STATIC_FOLDERS } from './../constants/settings';
|
||||
import * as os from 'os';
|
||||
import { PanelSettings, CustomScript } from './../models/PanelSettings';
|
||||
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
|
||||
@@ -21,6 +22,8 @@ import { Preview } from '../commands/Preview';
|
||||
import { openFileInEditor } from '../helpers/openFileInEditor';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { dirname, join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
@@ -73,8 +76,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
enableCommandUris: true,
|
||||
localResourceRoots: [this.extPath]
|
||||
enableCommandUris: true
|
||||
};
|
||||
|
||||
webviewView.webview.html = this.getWebviewContent(webviewView.webview);
|
||||
@@ -182,6 +184,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
case CommandToCode.updateMetadata:
|
||||
this.updateMetadata(msg.data);
|
||||
break;
|
||||
case CommandToCode.selectImage:
|
||||
await commands.executeCommand(`frontMatter.dashboard`, {
|
||||
type: "media",
|
||||
data: msg.data
|
||||
} as DashboardData);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -208,8 +216,11 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
* @param metadata
|
||||
*/
|
||||
public pushMetadata(metadata: any) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const filePath = window.activeTextEditor?.document.uri.fsPath;
|
||||
const config = SettingsHelper.getConfig();
|
||||
const commaSeparated = config.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
const staticFolder = config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
|
||||
|
||||
const articleDetails = this.getArticleDetails();
|
||||
|
||||
@@ -225,8 +236,28 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedMetadata.preview && wsFolder) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", updatedMetadata.preview);
|
||||
const contentFolderPath = filePath ? join(dirname(filePath), updatedMetadata.preview) : null;
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (contentFolderPath && existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = this.panel?.webview.asWebviewUri(previewUri);
|
||||
updatedMetadata.preview = preview?.toString() || "";
|
||||
} else {
|
||||
updatedMetadata.preview = "";
|
||||
}
|
||||
}
|
||||
|
||||
this.postWebviewMessage({ command: Command.metadata, data: {
|
||||
filePath,
|
||||
...updatedMetadata
|
||||
}});
|
||||
}
|
||||
@@ -253,7 +284,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
/**
|
||||
* Update the metadata of the article
|
||||
*/
|
||||
private updateMetadata({field, value}: { field: string, value: string }) {
|
||||
public async updateMetadata({field, value}: { field: string, value: string }) {
|
||||
const config = SettingsHelper.getConfig();
|
||||
const pubDate = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
|
||||
const modDate = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified;
|
||||
@@ -277,7 +308,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
} else {
|
||||
article.data[field] = value;
|
||||
}
|
||||
ArticleHelper.update(editor, article);
|
||||
ArticleHelper.update(editor, article);
|
||||
this.pushMetadata(article.data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,7 +599,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; style-src ${webView.cspSource} 'self' 'unsafe-inline'; font-src ${webView.cspSource}">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'; script-src 'nonce-${nonce}'; style-src ${webView.cspSource} 'self' 'unsafe-inline'; font-src ${webView.cspSource}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="${styleResetUri}" rel="stylesheet">
|
||||
<link href="${styleVSCodeUri}" rel="stylesheet">
|
||||
@@ -585,4 +617,4 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user