#270 #282 - Enhancements for page bundles and related folders + files

This commit is contained in:
Elio Struyf
2022-03-07 20:52:21 +01:00
parent 5644c0c381
commit 0d7b55c52f
7 changed files with 100 additions and 34 deletions
+2
View File
@@ -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
+5
View File
@@ -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
+3 -1
View File
@@ -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} />
))
}
+9 -1
View 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
View File
@@ -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);
}
}
}