#104 - First steps to preview image selection

This commit is contained in:
Elio Struyf
2021-09-16 11:46:37 +02:00
parent 1e26329d38
commit 626cc773f4
19 changed files with 249 additions and 60 deletions
+17
View File
@@ -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);
+21 -4
View File
@@ -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
View File
@@ -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) {
+4
View File
@@ -0,0 +1,4 @@
export interface DashboardData {
type: "contents" | "media";
data?: any;
}
+1
View File
@@ -3,4 +3,5 @@ export enum DashboardCommand {
pages = "pages",
settings = "settings",
media = "media",
viewData = "viewData",
}
+2
View File
@@ -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 -9
View File
@@ -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} />;
};
+41 -14
View File
@@ -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) || "")}
+11 -1
View File
@@ -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 && (
+12 -2
View File
@@ -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
};
}
+7
View File
@@ -0,0 +1,7 @@
import { atom } from 'recoil';
import { DashboardData } from '../../../models/DashboardData';
export const ViewDataAtom = atom<DashboardData | undefined>({
key: 'ViewDataAtom',
default: undefined
});
+1
View File
@@ -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);
}
});
+1
View File
@@ -11,4 +11,5 @@ export * from './SettingsSelector';
export * from './SortingSelector';
export * from './TabSelector';
export * from './TagSelector';
export * from './ViewDataSelector';
export * from './ViewSelector';
+1
View File
@@ -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>
);
};
+17 -5
View File
@@ -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>
);
};
+12 -13
View File
@@ -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}
+39 -7
View File
@@ -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>
`;
}
}
}