mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-08 09:53:20 +02:00
@@ -13,6 +13,8 @@
|
||||
- Light color theme enhancements to folder cards
|
||||
- [#272](https://github.com/estruyf/vscode-front-matter/issues/272): New slide over panel for showing details of media files
|
||||
- [#276](https://github.com/estruyf/vscode-front-matter/issues/276): Add a Front Matter walkthrough for VS Code
|
||||
- [#270](https://github.com/estruyf/vscode-front-matter/issues/270): Only show media files from public folder if `pageBundle` is not enabled on any of the content types
|
||||
- [#282](https://github.com/estruyf/vscode-front-matter/issues/282): Insert relative paths for media files located in a page bundle (also sub-folders)
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
|
||||
import { isValidFile } from './../helpers/isValidFile';
|
||||
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -326,11 +327,15 @@ export class Article {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
const contentType = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
const position = editor.selection.active;
|
||||
|
||||
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: "media",
|
||||
data: {
|
||||
pageBundle: !!contentType.pageBundle,
|
||||
filePath: editor.document.uri.fsPath,
|
||||
fieldName: basename(editor.document.uri.fsPath),
|
||||
position
|
||||
|
||||
@@ -20,7 +20,9 @@ export const DEFAULT_CONTENT_TYPE: ContentType = {
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime"
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {FolderIcon} from '@heroicons/react/solid';
|
||||
import { basename } from 'path';
|
||||
import {DocumentIcon, FolderIcon} from '@heroicons/react/solid';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedMediaFolderAtom } from '../../state';
|
||||
@@ -14,12 +14,19 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({ folder,
|
||||
const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
|
||||
const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;
|
||||
|
||||
const isContentFolder = React.useMemo(() => !relFolderPath.includes(join('/', staticFolder || '', '/')), [relFolderPath, staticFolder]);
|
||||
|
||||
return (
|
||||
<li className={`group relative hover:bg-gray-200 dark:hover:bg-vulcan-100 text-gray-600 hover:text-gray-700 dark:text-whisper-900 dark:hover:text-whisper-800 p-4`}>
|
||||
<button className={`w-full flex flex-row items-center h-full`} onClick={() => setSelectedFolder(folder)}>
|
||||
<div>
|
||||
<FolderIcon className={`h-12 w-12 mr-4`} />
|
||||
<button title={isContentFolder ? 'Content directory folder' : 'Public directory folder'} className={`w-full flex flex-row items-center h-full`} onClick={() => setSelectedFolder(folder)}>
|
||||
<div className='relative mr-4'>
|
||||
<FolderIcon className={`h-12 w-12`} />
|
||||
{
|
||||
isContentFolder && (
|
||||
<span className='text-whisper-800 dark:text-vulcan-500 font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2'>C</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
|
||||
|
||||
@@ -16,6 +16,8 @@ import { FolderItem } from './FolderItem';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { join } from 'path';
|
||||
|
||||
export interface IMediaProps {}
|
||||
|
||||
@@ -27,6 +29,25 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
const folders = useRecoilValue(MediaFoldersAtom);
|
||||
const loading = useRecoilValue(LoadingAtom);
|
||||
|
||||
|
||||
const allFolders = React.useMemo(() => {
|
||||
// Check if content allows page bundle
|
||||
if (viewData && viewData.data && typeof viewData.data.pageBundle !== "undefined" && !viewData.data.pageBundle) {
|
||||
return folders.filter(f => parseWinPath(f).includes(join('/', settings?.staticFolder || '', '/')));
|
||||
}
|
||||
|
||||
return folders;
|
||||
}, [folders, viewData, settings?.staticFolder]);
|
||||
|
||||
const allMedia = React.useMemo(() => {
|
||||
// Check if content allows page bundle
|
||||
if (viewData && viewData.data && typeof viewData.data.pageBundle !== "undefined" && !viewData.data.pageBundle) {
|
||||
return media.filter(m => parseWinPath(m.fsPath).includes(join('/', settings?.staticFolder || '', '/')));
|
||||
}
|
||||
|
||||
return media;
|
||||
}, [media, viewData, settings?.staticFolder]);
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
acceptedFiles.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
@@ -79,7 +100,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
}
|
||||
|
||||
{
|
||||
(media.length === 0 && folders.length === 0 && !loading) && (
|
||||
(allMedia.length === 0 && folders.length === 0 && !loading) && (
|
||||
<div className={`flex items-center justify-center h-full`}>
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon className={`text-vulcan-300 dark:text-whisper-800 h-32 mx-auto opacity-90 mb-8`} />
|
||||
@@ -91,11 +112,11 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
}
|
||||
|
||||
{
|
||||
folders && folders.length > 0 && (
|
||||
allFolders && allFolders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
<List gap={0}>
|
||||
{
|
||||
folders && folders.map((folder) => (
|
||||
allFolders.map((folder) => (
|
||||
<FolderItem key={folder} folder={folder} staticFolder={settings?.staticFolder} wsFolder={settings?.wsFolder} />
|
||||
))
|
||||
}
|
||||
@@ -106,7 +127,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
|
||||
<List>
|
||||
{
|
||||
media.map((file) => (
|
||||
allMedia.map((file) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))
|
||||
}
|
||||
|
||||
@@ -218,12 +218,20 @@ export class ArticleHelper {
|
||||
return modDateField?.name || Settings.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.PublishingDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the content types
|
||||
* @returns
|
||||
*/
|
||||
public static getContentTypes() {
|
||||
return Settings.get<ContentType[]>(SETTING_TAXONOMY_CONTENT_TYPES) || [DEFAULT_CONTENT_TYPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the content type of the current file
|
||||
* @param updatedMetadata
|
||||
*/
|
||||
public static getContentType(metadata: { [field: string]: string; }): ContentType {
|
||||
const contentTypes = Settings.get<ContentType[]>(SETTING_TAXONOMY_CONTENT_TYPES);
|
||||
const contentTypes = ArticleHelper.getContentTypes();
|
||||
|
||||
if (!contentTypes || !metadata) {
|
||||
return DEFAULT_CONTENT_TYPE;
|
||||
|
||||
+44
-23
@@ -1,10 +1,10 @@
|
||||
import { decodeBase64Image, Extension, MediaLibrary, Notifications, parseWinPath, Settings, Sorting } from ".";
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER } from "../constants";
|
||||
import { DEFAULT_CONTENT_TYPE, ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER } from "../constants";
|
||||
import { SortingOption } from "../dashboardWebView/models";
|
||||
import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models";
|
||||
import { basename, extname, join, parse, dirname } from "path";
|
||||
import { basename, extname, join, parse, dirname, relative } from "path";
|
||||
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, workspace, window, Position } from "vscode";
|
||||
import imageSize from "image-size";
|
||||
@@ -12,6 +12,7 @@ import { EditorHelper } from "@estruyf/vscode";
|
||||
import { ExplorerView } from "../explorerView/ExplorerView";
|
||||
import { SortOption } from "../dashboardWebView/constants/SortOption";
|
||||
import { DataListener, MediaListener } from "../listeners/panel";
|
||||
import { ArticleHelper } from "./ArticleHelper";
|
||||
|
||||
|
||||
export class MediaHelpers {
|
||||
@@ -31,6 +32,10 @@ export class MediaHelpers {
|
||||
const viewData = Dashboard.viewData;
|
||||
let selectedFolder = requestedFolder;
|
||||
|
||||
// Check if there are any content types that are set to use page bundles
|
||||
const contentTypes = ArticleHelper.getContentTypes();
|
||||
const pageBundleContentTypes = contentTypes.filter(ct => ct.pageBundle);
|
||||
|
||||
const ext = Extension.getInstance();
|
||||
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
|
||||
|
||||
@@ -80,15 +85,17 @@ export class MediaHelpers {
|
||||
allMedia = [...media];
|
||||
}
|
||||
|
||||
if (contentFolders && wsFolder) {
|
||||
for (let i = 0; i < contentFolders.length; i++) {
|
||||
const contentFolder = contentFolders[i];
|
||||
const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1);
|
||||
const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*');
|
||||
const files = await workspace.findFiles(folderSearch);
|
||||
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
|
||||
|
||||
allMedia = [...allMedia, ...media];
|
||||
if (pageBundleContentTypes.length > 0) {
|
||||
if (contentFolders && wsFolder) {
|
||||
for (let i = 0; i < contentFolders.length; i++) {
|
||||
const contentFolder = contentFolders[i];
|
||||
const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1);
|
||||
const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*');
|
||||
const files = await workspace.findFiles(folderSearch);
|
||||
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
|
||||
|
||||
allMedia = [...allMedia, ...media];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,11 +152,13 @@ export class MediaHelpers {
|
||||
allFolders = readdirSync(selectedFolder, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(selectedFolder, dir.name)));
|
||||
}
|
||||
} else {
|
||||
for (const contentFolder of contentFolders) {
|
||||
const contentPath = contentFolder.path;
|
||||
if (contentPath && existsSync(contentPath)) {
|
||||
const subFolders = readdirSync(contentPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name)));
|
||||
allContentFolders = [...allContentFolders, ...subFolders];
|
||||
if (pageBundleContentTypes.length > 0) {
|
||||
for (const contentFolder of contentFolders) {
|
||||
const contentPath = contentFolder.path;
|
||||
if (contentPath && existsSync(contentPath)) {
|
||||
const subFolders = readdirSync(contentPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name)));
|
||||
allContentFolders = [...allContentFolders, ...subFolders];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,15 +289,27 @@ export class MediaHelpers {
|
||||
const filePath = data.file;
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
|
||||
|
||||
const imgDir = dirname(absImgPath);
|
||||
const fileDir = dirname(filePath);
|
||||
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
|
||||
const articleCt = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
|
||||
|
||||
if (imgDir === fileDir) {
|
||||
imgPath = join('/', basename(imgPath));
|
||||
// Check if relative paths need to be created for the media files
|
||||
if (articleCt.pageBundle) {
|
||||
const fileDir = parseWinPath(dirname(filePath));
|
||||
const imgDir = parseWinPath(dirname(absImgPath));
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
if (imgDir.toLowerCase().indexOf(fileDir.toLowerCase()) !== -1) {
|
||||
const relImgPath = relative(fileDir, imgDir);
|
||||
|
||||
imgPath = join(relImgPath, basename(imgPath));
|
||||
|
||||
if (!imgPath.startsWith("/")) {
|
||||
imgPath = `./${imgPath}`;
|
||||
}
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user