mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 17:32:58 +02:00
Merge branch 'dev-refactor' into dev
This commit is contained in:
Generated
+15571
-6901
File diff suppressed because it is too large
Load Diff
+12
-4
@@ -3,7 +3,7 @@
|
||||
"displayName": "Front Matter",
|
||||
"description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
|
||||
"icon": "assets/frontmatter-teal-128x128.png",
|
||||
"version": "5.7.0",
|
||||
"version": "5.8.0",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -797,6 +797,15 @@
|
||||
"light": "/assets/icons/frontmatter-small-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.media",
|
||||
"title": "Open media dashboard",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"dark": "/assets/icons/frontmatter-small-dark.svg",
|
||||
"light": "/assets/icons/frontmatter-small-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.dashboard.close",
|
||||
"title": "Close dashboard",
|
||||
@@ -1137,7 +1146,7 @@
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-datepicker": "^4.1.7",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "1.51.0",
|
||||
"@types/vscode": "^1.63.0",
|
||||
"@vscode/codicons": "0.0.20",
|
||||
"@vscode/webview-ui-toolkit": "^0.8.1",
|
||||
"autoprefixer": "^10.3.2",
|
||||
@@ -1171,6 +1180,5 @@
|
||||
"wc-react": "github:estruyf/wc-react",
|
||||
"webpack": "4.44.2",
|
||||
"webpack-cli": "3.3.12"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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 } from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { TaxonomyType } from "../models";
|
||||
@@ -203,7 +204,7 @@ export class Article {
|
||||
|
||||
const file = parseWinPath(editor.document.fileName);
|
||||
|
||||
if (!file.endsWith(`.md`) && !file.endsWith(`.markdown`) && !file.endsWith(`.mdx`)) {
|
||||
if (!isValidFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+46
-590
@@ -1,42 +1,21 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, CONTEXT, SETTING_CUSTOM_SCRIPTS, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_MEDIA_SORTING_DEFAULT } from '../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { basename, dirname, extname, join, parse } from "path";
|
||||
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
|
||||
import { Settings as SettingsHelper } from '../helpers';
|
||||
import { CustomScript as ICustomScript, DraftField, Framework, ScriptType, SortingSetting, SortOrder, SortType, TaxonomyType } from '../models';
|
||||
import { Folders } from './Folders';
|
||||
import { PagesListener } from './../listeners/PagesListener';
|
||||
import { ExtensionListener } from './../listeners/ExtensionListener';
|
||||
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
|
||||
import { join } from "path";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
|
||||
import { Logger, Settings as SettingsHelper } from '../helpers';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
|
||||
import { Page } from '../dashboardWebView/models/Page';
|
||||
import { openFileInEditor } from '../helpers/openFileInEditor';
|
||||
import { Template } from './Template';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { Settings } from '../dashboardWebView/models/Settings';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { ViewType } from '../dashboardWebView/state';
|
||||
import { EditorHelper, WebviewHelper } from '@estruyf/vscode';
|
||||
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
|
||||
import { decodeBase64Image } from '../helpers/decodeBase64Image';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { MediaLibrary } from '../helpers/MediaLibrary';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { FrameworkDetector } from '../helpers/FrameworkDetector';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { SortingOption } from '../dashboardWebView/models';
|
||||
import { Sorting } from '../helpers/Sorting';
|
||||
import imageSize from 'image-size';
|
||||
import { CustomScript } from '../helpers/CustomScript';
|
||||
import { DashboardListener, MediaListener, SettingsListener } from '../listeners';
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
private static isDisposed: boolean = true;
|
||||
private static media: MediaInfo[] = [];
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
private static _viewData: DashboardData | undefined;
|
||||
private static mediaLib: MediaLibrary;
|
||||
private static isDisposed: boolean = true;
|
||||
|
||||
public static get viewData(): DashboardData | undefined {
|
||||
return Dashboard._viewData;
|
||||
@@ -56,12 +35,12 @@ export class Dashboard {
|
||||
* Open or reveal the dashboard
|
||||
*/
|
||||
public static async open(data?: DashboardData) {
|
||||
this.mediaLib = MediaLibrary.getInstance();
|
||||
MediaLibrary.getInstance();
|
||||
|
||||
Dashboard._viewData = data;
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.reveal();
|
||||
Dashboard.reveal(!!data);
|
||||
} else {
|
||||
Dashboard.create();
|
||||
}
|
||||
@@ -79,15 +58,33 @@ export class Dashboard {
|
||||
/**
|
||||
* Reveal the dashboard if it is open
|
||||
*/
|
||||
public static reveal() {
|
||||
public static reveal(hasData: boolean = false) {
|
||||
if (Dashboard.webview) {
|
||||
Dashboard.webview.reveal();
|
||||
|
||||
if (hasData) {
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: Dashboard.viewData });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static close() {
|
||||
Dashboard.webview?.dispose();
|
||||
}
|
||||
|
||||
public static reload() {
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.webview?.dispose();
|
||||
|
||||
setTimeout(() => {
|
||||
Dashboard.open();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
public static resetViewData() {
|
||||
Dashboard._viewData = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dashboard webview
|
||||
@@ -101,7 +98,8 @@ export class Dashboard {
|
||||
'FrontMatter Dashboard',
|
||||
ViewColumn.One,
|
||||
{
|
||||
enableScripts: true
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -119,6 +117,8 @@ export class Dashboard {
|
||||
Dashboard._viewData = undefined;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
panel.getMediaSelection();
|
||||
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null });
|
||||
}
|
||||
|
||||
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, this.webview?.visible);
|
||||
@@ -133,99 +133,26 @@ export class Dashboard {
|
||||
});
|
||||
|
||||
SettingsHelper.onConfigChange((global?: any) => {
|
||||
Dashboard.getSettings();
|
||||
SettingsListener.getSettings();
|
||||
});
|
||||
|
||||
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();
|
||||
break;
|
||||
case DashboardMessage.openFile:
|
||||
openFileInEditor(msg.data);
|
||||
break;
|
||||
case DashboardMessage.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case DashboardMessage.createByContentType:
|
||||
await commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
break;
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
case DashboardMessage.updateSetting:
|
||||
Dashboard.updateSetting(msg.data);
|
||||
break;
|
||||
case DashboardMessage.initializeProject:
|
||||
await commands.executeCommand(COMMAND_NAME.init, Dashboard.getSettings);
|
||||
break;
|
||||
case DashboardMessage.reload:
|
||||
if (!Dashboard.isDisposed) {
|
||||
Dashboard.webview?.dispose();
|
||||
setTimeout(() => {
|
||||
Dashboard.open();
|
||||
}, 100);
|
||||
}
|
||||
break;
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
|
||||
break;
|
||||
case DashboardMessage.getMedia:
|
||||
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder, msg?.data?.sorting);
|
||||
break;
|
||||
case DashboardMessage.copyToClipboard:
|
||||
env.clipboard.writeText(msg.data);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
Dashboard.resetMedia();
|
||||
Dashboard.getMedia(0, msg?.data?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
Dashboard.saveFile(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.deleteMedia:
|
||||
Dashboard.deleteFile(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
Dashboard.insertImage(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
Dashboard.updateMediaMetadata(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.createMediaFolder:
|
||||
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.data);
|
||||
break;
|
||||
case DashboardMessage.setFramework:
|
||||
Dashboard.setFramework(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.runCustomScript:
|
||||
CustomScript.run(msg?.data?.script, msg?.data?.path);
|
||||
break;
|
||||
case DashboardMessage.setState:
|
||||
if (msg?.data?.key && msg?.data?.value) {
|
||||
Extension.getInstance().setState(msg?.data?.key, msg?.data?.value, "workspace");
|
||||
}
|
||||
break;
|
||||
}
|
||||
Logger.info(`Receiving message from webview: ${msg.command}`);
|
||||
|
||||
DashboardListener.process(msg);
|
||||
ExtensionListener.process(msg);
|
||||
MediaListener.process(msg);
|
||||
PagesListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset media array
|
||||
* Return the webview
|
||||
* @returns The webview
|
||||
*/
|
||||
public static resetMedia() {
|
||||
Dashboard.media = [];
|
||||
}
|
||||
|
||||
public static switchFolder(folderPath: string) {
|
||||
Dashboard.resetMedia();
|
||||
Dashboard.getMedia(0, folderPath);
|
||||
public static getWebview() {
|
||||
return Dashboard.webview?.webview;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,477 +169,6 @@ export class Dashboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an image into the front matter or contents
|
||||
* @param data
|
||||
*/
|
||||
private static async insertImage(data: any) {
|
||||
if (data?.file && data?.image) {
|
||||
if (!data?.position) {
|
||||
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
|
||||
}
|
||||
|
||||
await EditorHelper.showFile(data.file);
|
||||
Dashboard._viewData = undefined;
|
||||
|
||||
const extensionUri = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
|
||||
if (data?.position) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const editor = window.activeTextEditor;
|
||||
const line = data.position.line;
|
||||
const character = data.position.character;
|
||||
if (line) {
|
||||
let imgPath = data.image;
|
||||
const filePath = data.file;
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
|
||||
|
||||
const imgDir = dirname(absImgPath);
|
||||
const fileDir = dirname(filePath);
|
||||
|
||||
if (imgDir === fileDir) {
|
||||
imgPath = join('/', basename(imgPath));
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
}
|
||||
}
|
||||
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
} else {
|
||||
panel.getMediaSelection();
|
||||
panel.updateMetadata({field: data.fieldName, value: data.image });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings for the dashboard
|
||||
*/
|
||||
private static async getSettings() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = await Template.isInitialized();
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.settings,
|
||||
data: {
|
||||
beta: ext.isBetaVersion(),
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER),
|
||||
folders: Folders.get(),
|
||||
initialized: isInitialized,
|
||||
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
|
||||
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
|
||||
openOnStart: SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
versionInfo: ext.getVersion(),
|
||||
pageViewType: await ext.getState<ViewType | undefined>(ExtensionState.PagesView, "workspace"),
|
||||
mediaSnippet: SettingsHelper.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
|
||||
contentTypes: SettingsHelper.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
draftField: SettingsHelper.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
|
||||
customSorting: SettingsHelper.get<SortingSetting[]>(SETTINGS_CONTENT_SORTING),
|
||||
contentFolders: Folders.get(),
|
||||
crntFramework: SettingsHelper.get<string>(SETTINGS_FRAMEWORK_ID),
|
||||
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
|
||||
scripts: (SettingsHelper.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type && s.type !== ScriptType.Content),
|
||||
dashboardState: {
|
||||
contents: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
|
||||
defaultSorting: SettingsHelper.get<string>(SETTINGS_CONTENT_SORTING_DEFAULT)
|
||||
},
|
||||
media: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
|
||||
defaultSorting: SettingsHelper.get<string>(SETTINGS_MEDIA_SORTING_DEFAULT)
|
||||
}
|
||||
}
|
||||
} as Settings
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current site-generator or framework + related settings
|
||||
* @param frameworkId
|
||||
*/
|
||||
private static setFramework(frameworkId: string | null) {
|
||||
SettingsHelper.update(SETTINGS_FRAMEWORK_ID, frameworkId, true);
|
||||
|
||||
if (frameworkId) {
|
||||
const allFrameworks = FrameworkDetector.getAll();
|
||||
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
|
||||
if (framework) {
|
||||
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true);
|
||||
} else {
|
||||
SettingsHelper.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a setting from the dashboard
|
||||
*/
|
||||
private static async updateSetting(data: { name: string, value: any }) {
|
||||
await SettingsHelper.update(data.name, data.value);
|
||||
Dashboard.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all media files
|
||||
*/
|
||||
private static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
const contentFolders = Folders.get();
|
||||
const viewData = Dashboard.viewData;
|
||||
let selectedFolder = requestedFolder;
|
||||
|
||||
const ext = Extension.getInstance();
|
||||
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
|
||||
|
||||
// If the static folder is not set, retreive the last opened location
|
||||
if (!selectedFolder) {
|
||||
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
|
||||
|
||||
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
|
||||
// Support for page bundles
|
||||
if (viewData?.data?.filePath && viewData?.data?.filePath.endsWith('index.md')) {
|
||||
const folderPath = parse(viewData.data.filePath).dir;
|
||||
selectedFolder = folderPath;
|
||||
} else if (stateValue && existsSync(stateValue)) {
|
||||
selectedFolder = stateValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go to the home folder
|
||||
if (selectedFolder === HOME_PAGE_NAVIGATION_ID) {
|
||||
selectedFolder = '';
|
||||
}
|
||||
|
||||
let relSelectedFolderPath = selectedFolder;
|
||||
const parsedPath = parseWinPath(wsFolder?.fsPath || "");
|
||||
if (selectedFolder && selectedFolder.startsWith(parsedPath)) {
|
||||
relSelectedFolderPath = selectedFolder.replace(parsedPath, '');
|
||||
}
|
||||
|
||||
if (relSelectedFolderPath.startsWith('/')) {
|
||||
relSelectedFolderPath = relSelectedFolderPath.substring(1);
|
||||
}
|
||||
|
||||
let allMedia: MediaInfo[] = [];
|
||||
|
||||
if (relSelectedFolderPath) {
|
||||
const files = await workspace.findFiles(join(relSelectedFolderPath, '/*'));
|
||||
const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files));
|
||||
|
||||
allMedia = [...media];
|
||||
} else {
|
||||
if (staticFolder) {
|
||||
const folderSearch = join(staticFolder || "", '/*');
|
||||
const files = await workspace.findFiles(folderSearch);
|
||||
const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files));
|
||||
|
||||
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 Dashboard.updateMediaData(Dashboard.filterMedia(files));
|
||||
|
||||
allMedia = [...allMedia, ...media];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (crntSort?.type === SortType.string) {
|
||||
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
|
||||
} else if (crntSort?.type === SortType.date) {
|
||||
allMedia = allMedia.sort(Sorting.dateWithFallback("mtime", "fsPath"));
|
||||
} else {
|
||||
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
|
||||
}
|
||||
|
||||
if (crntSort?.order === SortOrder.desc) {
|
||||
allMedia = allMedia.reverse();
|
||||
}
|
||||
|
||||
Dashboard.media = Object.assign([], allMedia);
|
||||
|
||||
let files: MediaInfo[] = Dashboard.media;
|
||||
|
||||
// Retrieve the total after filtering and before the slicing happens
|
||||
const total = files.length;
|
||||
|
||||
// Get media set
|
||||
files = files.slice(page * 16, ((page + 1) * 16));
|
||||
files = files.map((file) => {
|
||||
try {
|
||||
const metadata = Dashboard.mediaLib.get(file.fsPath);
|
||||
|
||||
return {
|
||||
...file,
|
||||
dimensions: imageSize(file.fsPath),
|
||||
...metadata
|
||||
};
|
||||
} catch (e) {
|
||||
return {...file};
|
||||
}
|
||||
});
|
||||
files = files.filter(f => f.mtime !== undefined);
|
||||
|
||||
// Retrieve all the folders
|
||||
let allContentFolders: string[] = [];
|
||||
let allFolders: string[] = [];
|
||||
|
||||
if (selectedFolder) {
|
||||
if (existsSync(selectedFolder)) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "");
|
||||
if (staticPath && existsSync(staticPath)) {
|
||||
allFolders = readdirSync(staticPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name)));
|
||||
}
|
||||
}
|
||||
|
||||
// Store the last opened folder
|
||||
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
|
||||
|
||||
let sortedFolders = [...allContentFolders, ...allFolders];
|
||||
sortedFolders = sortedFolders.sort((a, b) => {
|
||||
if (a.toLowerCase() < b.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.toLowerCase() > b.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (crntSort?.order === SortOrder.desc) {
|
||||
sortedFolders = sortedFolders.reverse();
|
||||
}
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.media,
|
||||
data: {
|
||||
media: files,
|
||||
total: total,
|
||||
folders: sortedFolders,
|
||||
selectedFolder
|
||||
} as MediaPaths
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the retrieved files
|
||||
* @param files
|
||||
*/
|
||||
private static async updateMediaData(files: MediaInfo[]) {
|
||||
files = files.map((m: MediaInfo) => {
|
||||
const stats = statSync(m.fsPath);
|
||||
return Object.assign({}, m, stats);
|
||||
});
|
||||
|
||||
return Object.assign([], files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
private static async getPages() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const descriptionField = SettingsHelper.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
|
||||
const dateField = SettingsHelper.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
|
||||
if (folderInfo) {
|
||||
for (const folder of folderInfo) {
|
||||
for (const file of folder.lastModified) {
|
||||
if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.markdown`) || file.fileName.endsWith(`.mdx`)) {
|
||||
try {
|
||||
const article = ArticleHelper.getFrontMatterByPath(file.filePath);
|
||||
|
||||
if (article?.data.title) {
|
||||
const page: Page = {
|
||||
...article.data,
|
||||
// FrontMatter properties
|
||||
fmFolder: folder.title,
|
||||
fmModified: file.mtime,
|
||||
fmFilePath: file.filePath,
|
||||
fmFileName: file.fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null,
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
date: article?.data[dateField] || "",
|
||||
draft: article?.data.draft,
|
||||
description: article?.data[descriptionField] || "",
|
||||
};
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
if (article?.data[previewField] && wsFolder) {
|
||||
let fieldValue = article?.data[previewField];
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(file.filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
|
||||
page[previewField] = preview?.toString() || "";
|
||||
} else {
|
||||
page[previewField] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
}
|
||||
} catch (error: any) {
|
||||
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.pages,
|
||||
data: pages
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the media files
|
||||
*/
|
||||
private static filterMedia(files: Uri[]) {
|
||||
return files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext);
|
||||
}).map((file) => ({
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.webview?.webview.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
} as MediaInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the dropped file in the current folder
|
||||
* @param fileData
|
||||
*/
|
||||
private static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) {
|
||||
if (fileName && contents) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
const wsPath = wsFolder ? wsFolder.fsPath : "";
|
||||
let absFolderPath = join(wsPath, staticFolder || "");
|
||||
|
||||
if (folder) {
|
||||
absFolderPath = folder;
|
||||
}
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
absFolderPath = join(wsPath, folder || "");
|
||||
}
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
Notifications.error(`We couldn't find your selected folder.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const staticPath = join(absFolderPath, fileName);
|
||||
const imgData = decodeBase64Image(contents);
|
||||
|
||||
if (imgData) {
|
||||
writeFileSync(staticPath, imgData.data);
|
||||
Notifications.info(`File ${fileName} uploaded to: ${folder}`);
|
||||
|
||||
const folderPath = `${folder}`;
|
||||
if (Dashboard.timers[folderPath]) {
|
||||
clearTimeout(Dashboard.timers[folderPath]);
|
||||
delete Dashboard.timers[folderPath];
|
||||
}
|
||||
|
||||
Dashboard.timers[folderPath] = setTimeout(() => {
|
||||
Dashboard.media = [];
|
||||
Dashboard.getMedia(0, folder || "");
|
||||
delete Dashboard.timers[folderPath];
|
||||
}, 500);
|
||||
} else {
|
||||
Notifications.error(`Something went wrong uploading ${fileName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the selected file
|
||||
* @param fileData
|
||||
*/
|
||||
private static async deleteFile({ file, page, folder }: { file: string; page: number; folder: string | null; }) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
unlinkSync(file);
|
||||
|
||||
Dashboard.media = [];
|
||||
Dashboard.getMedia(page || 0, folder || "");
|
||||
} catch(err) {
|
||||
Notifications.error(`Something went wrong deleting ${basename(file)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the selected file
|
||||
*/
|
||||
private static async updateMediaMetadata({ file, filename, page, folder, ...metadata }: { file:string; filename:string; page: number; folder: string | null; metadata: any; }) {
|
||||
Dashboard.mediaLib.set(file, metadata);
|
||||
|
||||
// Check if filename needs to be updated
|
||||
Dashboard.mediaLib.updateFilename(file, filename);
|
||||
|
||||
Dashboard.getMedia(page || 0, folder || "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the webview HTML contents
|
||||
* @param webView
|
||||
|
||||
@@ -11,6 +11,8 @@ import { existsSync, mkdirSync } from 'fs';
|
||||
import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { MediaHelpers } from '../helpers/MediaHelpers';
|
||||
import { MediaListener } from '../listeners';
|
||||
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
@@ -62,7 +64,8 @@ export class Folders {
|
||||
}
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
Dashboard.switchFolder(folderName);
|
||||
MediaHelpers.resetMedia();
|
||||
MediaListener.sendMediaFiles(0, folderName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export const COMMAND_NAME = {
|
||||
collapseSections: getCommandName("collapseSections"),
|
||||
preview: getCommandName("preview"),
|
||||
dashboard: getCommandName("dashboard"),
|
||||
dashboardMedia: getCommandName("dashboard.media"),
|
||||
dashboardClose: getCommandName("dashboard.close"),
|
||||
promote: getCommandName("promoteSettings"),
|
||||
insertImage: getCommandName("insertImage"),
|
||||
|
||||
@@ -20,19 +20,17 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loadin
|
||||
const pageFolders = [...new Set(pageItems.map(page => page.fmFolder))];
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
folders={pageFolders}
|
||||
totalPages={pageItems.length}
|
||||
settings={settings} />
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
folders={pageFolders}
|
||||
totalPages={pageItems.length}
|
||||
settings={settings} />
|
||||
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
|
||||
</div>
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,11 +3,12 @@ import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { SettingsSelector, ViewSelector, ViewType } from '../../state';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { DateField } from '../DateField';
|
||||
import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import useContentType from '../../../hooks/useContentType';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
|
||||
@@ -22,7 +23,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
};
|
||||
|
||||
if (view === ViewType.Grid) {
|
||||
if (view === DashboardViewType.Grid) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100`}
|
||||
@@ -53,7 +54,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
} else if (view === ViewType.List) {
|
||||
} else if (view === DashboardViewType.List) {
|
||||
return (
|
||||
<li className="relative">
|
||||
<button className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b border-gray-300 hover:bg-gray-200 dark:border-vulcan-50 dark:hover:bg-vulcan-50 hover:bg-opacity-70`} onClick={openFile}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ViewSelector, ViewType } from '../../state';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ViewSelector } from '../../state';
|
||||
|
||||
export interface IListProps {}
|
||||
|
||||
@@ -8,15 +9,15 @@ export const List: React.FunctionComponent<IListProps> = ({children}: React.Prop
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
|
||||
let className = '';
|
||||
if (view === ViewType.Grid) {
|
||||
if (view === DashboardViewType.Grid) {
|
||||
className = `grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`;
|
||||
} else if (view === ViewType.List) {
|
||||
} else if (view === DashboardViewType.List) {
|
||||
className = `-mx-4`;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul role="list" className={className}>
|
||||
{view === ViewType.List && (
|
||||
{view === DashboardViewType.List && (
|
||||
<li className="px-5 relative uppercase text-vulcan-100 dark:text-whisper-900 py-2 border-b border-vulcan-50">
|
||||
<div className={`grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8`}>
|
||||
<div className="col-span-8">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
import { ViewType } from '../models';
|
||||
import { NavigationType } from '../models';
|
||||
|
||||
export interface IDashboardProps {
|
||||
showWelcome: boolean;
|
||||
@@ -30,9 +30,15 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
|
||||
return <WelcomeScreen settings={settings} />;
|
||||
}
|
||||
|
||||
if (view === ViewType.Media) {
|
||||
return <Media />;
|
||||
}
|
||||
|
||||
return <Contents pages={pages} loading={loading} />;
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
{
|
||||
view === NavigationType.Media ? (
|
||||
<Media />
|
||||
) : (
|
||||
<Contents pages={pages} loading={loading} />
|
||||
)
|
||||
}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Sorting } from '.';
|
||||
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
import { ViewType } from '../../models';
|
||||
import { NavigationType } from '../../models';
|
||||
import { SelectedMediaFolderAtom, SettingsAtom } from '../../state';
|
||||
|
||||
export interface IBreadcrumbProps {}
|
||||
@@ -99,7 +99,7 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
|
||||
</ol>
|
||||
|
||||
<div className={`flex px-5`}>
|
||||
<Sorting view={ViewType.Media} disableCustomSorting />
|
||||
<Sorting view={NavigationType.Media} disableCustomSorting />
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Sorting } from './Sorting';
|
||||
import { Searchbox } from './Searchbox';
|
||||
import { Filter } from './Filter';
|
||||
import { Folders } from './Folders';
|
||||
import { Settings, ViewType } from '../../models';
|
||||
import { Settings, NavigationType } from '../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Startup } from '../Startup';
|
||||
import { Navigation } from '../Navigation';
|
||||
@@ -47,7 +47,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
Messenger.send(DashboardMessage.createByTemplate);
|
||||
};
|
||||
|
||||
const updateView = (view: ViewType) => {
|
||||
const updateView = (view: NavigationType) => {
|
||||
setView(view);
|
||||
resetSorting();
|
||||
}
|
||||
@@ -57,17 +57,17 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
|
||||
<div className={`px-4 bg-gray-50 dark:bg-vulcan-50 border-b-2 border-gray-200 dark:border-vulcan-200`}>
|
||||
<div className={`flex items-center justify-start`}>
|
||||
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Contents)}>
|
||||
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(NavigationType.Contents)}>
|
||||
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
|
||||
</button>
|
||||
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Media)}>
|
||||
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(NavigationType.Media)}>
|
||||
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
view === ViewType.Contents && (
|
||||
view === NavigationType.Contents && (
|
||||
<>
|
||||
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
@@ -112,14 +112,14 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
|
||||
<Grouping />
|
||||
|
||||
<Sorting view={ViewType.Contents} />
|
||||
<Sorting view={NavigationType.Contents} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
view === ViewType.Media && (
|
||||
view === NavigationType.Media && (
|
||||
<>
|
||||
<Pagination />
|
||||
<Breadcrumb />
|
||||
|
||||
@@ -6,14 +6,14 @@ import { ExtensionState } from '../../../constants';
|
||||
import { SortOrder, SortType } from '../../../models';
|
||||
import { SortOption } from '../../constants/SortOption';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ViewType } from '../../models';
|
||||
import { NavigationType } from '../../models';
|
||||
import { SortingOption } from '../../models/SortingOption';
|
||||
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
|
||||
import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
export interface ISortingProps {
|
||||
disableCustomSorting?: boolean;
|
||||
view: ViewType;
|
||||
view: NavigationType;
|
||||
}
|
||||
|
||||
export const sortOptions: SortingOption[] = [
|
||||
@@ -30,7 +30,7 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({disableCustomSo
|
||||
|
||||
const updateSorting = (value: SortingOption) => {
|
||||
Messenger.send(DashboardMessage.setState, {
|
||||
key: `${view === ViewType.Contents ? ExtensionState.Dashboard.Contents.Sorting : ExtensionState.Dashboard.Media.Sorting}`,
|
||||
key: `${view === NavigationType.Contents ? ExtensionState.Dashboard.Contents.Sorting : ExtensionState.Dashboard.Media.Sorting}`,
|
||||
value: value
|
||||
});
|
||||
|
||||
@@ -50,16 +50,16 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({disableCustomSo
|
||||
|
||||
let crntSortingOption = crntSorting;
|
||||
if (!crntSortingOption) {
|
||||
if (view === ViewType.Contents) {
|
||||
if (view === NavigationType.Contents) {
|
||||
crntSortingOption = settings?.dashboardState?.contents?.sorting || null;
|
||||
} else if (view === ViewType.Media) {
|
||||
} else if (view === NavigationType.Media) {
|
||||
crntSortingOption = settings?.dashboardState?.media?.sorting || null;
|
||||
}
|
||||
|
||||
if (crntSortingOption === null) {
|
||||
if (view === ViewType.Contents && settings?.dashboardState.contents.defaultSorting) {
|
||||
if (view === NavigationType.Contents && settings?.dashboardState.contents.defaultSorting) {
|
||||
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
|
||||
} else if (view === ViewType.Media && settings?.dashboardState.contents.defaultSorting) {
|
||||
} else if (view === NavigationType.Media && settings?.dashboardState.contents.defaultSorting) {
|
||||
crntSortingOption = allOptions.find(f => f.id === settings?.dashboardState.contents.defaultSorting) || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { ViewAtom, ViewType, SettingsSelector } from '../../state';
|
||||
import { ViewAtom, SettingsSelector } from '../../state';
|
||||
import { ViewGridIcon, ViewListIcon } from '@heroicons/react/solid';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IViewSwitchProps {}
|
||||
|
||||
@@ -12,7 +13,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: Rea
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const toggleView = () => {
|
||||
const newView = view === ViewType.Grid ? ViewType.List : ViewType.Grid;
|
||||
const newView = view === DashboardViewType.Grid ? DashboardViewType.List : DashboardViewType.Grid;
|
||||
setView(newView);
|
||||
Messenger.send(DashboardMessage.setPageViewType, newView);
|
||||
};
|
||||
@@ -25,11 +26,11 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: Rea
|
||||
|
||||
return (
|
||||
<div className={`flex rounded-sm bg-vulcan-50 lg:mb-1`}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-l-sm ${view === ViewType.Grid ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-l-sm ${view === DashboardViewType.Grid ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<ViewGridIcon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>Change to grid</span>
|
||||
</button>
|
||||
<button className={`flex items-center px-2 py-1 rounded-r-sm ${view === ViewType.List ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<button className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.List ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
|
||||
<ViewListIcon className={`w-4 h-4`} />
|
||||
<span className={`sr-only`}>Change to list</span>
|
||||
</button>
|
||||
|
||||
@@ -73,75 +73,73 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
}, ['']);
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header settings={settings} />
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<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 && (
|
||||
<div className="absolute top-0 left-0 w-full h-full text-whisper-500 bg-gray-900 bg-opacity-70 flex flex-col justify-center items-center z-50">
|
||||
<UploadIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder ? `Upload to ${selectedFolder}` : `No folder selected, files you drop will be added to the ${settings?.staticFolder || "public"} folder.`}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
(media.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`} />
|
||||
|
||||
<p className={`text-xl font-medium`}>No media files to show. You can drag & drop new files.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
folders && folders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
<List>
|
||||
{
|
||||
folders && folders.map((folder) => (
|
||||
<FolderItem key={folder} folder={folder} staticFolder={settings?.staticFolder} wsFolder={settings?.wsFolder} />
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<List>
|
||||
{
|
||||
media.map((file) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4" {...getRootProps()}>
|
||||
|
||||
{
|
||||
loading && ( <Spinner /> )
|
||||
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 && (
|
||||
<div className="absolute top-0 left-0 w-full h-full text-whisper-500 bg-gray-900 bg-opacity-70 flex flex-col justify-center items-center z-50">
|
||||
<UploadIcon className={`h-32`} />
|
||||
<p className={`text-xl max-w-md text-center`}>
|
||||
{selectedFolder ? `Upload to ${selectedFolder}` : `No folder selected, files you drop will be added to the ${settings?.staticFolder || "public"} folder.`}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Lightbox />
|
||||
{
|
||||
(media.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`} />
|
||||
|
||||
<p className={`text-xl font-medium`}>No media files to show. You can drag & drop new files.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
{
|
||||
folders && folders.length > 0 && (
|
||||
<div className={`mb-8`}>
|
||||
<List>
|
||||
{
|
||||
folders && folders.map((folder) => (
|
||||
<FolderItem key={folder} folder={folder} staticFolder={settings?.staticFolder} wsFolder={settings?.wsFolder} />
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<List>
|
||||
{
|
||||
media.map((file) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{
|
||||
loading && ( <Spinner /> )
|
||||
}
|
||||
|
||||
<Lightbox />
|
||||
|
||||
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,9 +3,11 @@ import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { RefreshIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import { usePrevious } from '../../../panelWebView/hooks/usePrevious';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector, SortingSelector } from '../../state';
|
||||
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector, SettingsSelector, SortingSelector } from '../../state';
|
||||
import { FolderCreation } from './FolderCreation';
|
||||
import { LIMIT } from './Media';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
@@ -13,12 +15,16 @@ import { PaginationButton } from './PaginationButton';
|
||||
export interface IPaginationProps {}
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.PropsWithChildren<IPaginationProps>) => {
|
||||
const [ lastUpdated, setLastUpdated ] = React.useState<string | null>(null);
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const crntSorting = useRecoilValue(SortingSelector);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const [ , setLoading ] = useRecoilState(LoadingAtom);
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const debounceGetMedia = useDebounce<string | null>(lastUpdated, 200);
|
||||
const prevSelectedFolder = usePrevious<string | null>(selectedFolder);
|
||||
|
||||
const totalPages = Math.ceil(totalMedia / LIMIT) - 1;
|
||||
|
||||
const getTotalPage = () => {
|
||||
@@ -61,35 +67,39 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true);
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page,
|
||||
folder: selectedFolder || '',
|
||||
sorting: crntSorting
|
||||
});
|
||||
setLastUpdated(new Date().getTime().toString());
|
||||
}, [page]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true);
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page: 0,
|
||||
folder: selectedFolder || '',
|
||||
sorting: crntSorting
|
||||
});
|
||||
setPage(0);
|
||||
if (prevSelectedFolder !== null || settings?.dashboardState?.media.selectedFolder !== selectedFolder) {
|
||||
setLoading(true);
|
||||
setPage(0);
|
||||
setLastUpdated(new Date().getTime().toString());
|
||||
}
|
||||
}, [selectedFolder]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true);
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page,
|
||||
folder: selectedFolder || '',
|
||||
sorting: crntSorting
|
||||
});
|
||||
setLastUpdated(new Date().getTime().toString());
|
||||
}, [crntSorting]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (debounceGetMedia) {
|
||||
setLoading(true);
|
||||
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page,
|
||||
folder: selectedFolder || '',
|
||||
sorting: crntSorting
|
||||
});
|
||||
}
|
||||
}, [debounceGetMedia]);
|
||||
|
||||
React.useEffect(() => {
|
||||
Messenger.listen(mediaUpdate);
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(mediaUpdate);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Page } from '../models/Page';
|
||||
import { DashboardViewAtom, SettingsAtom, ViewDataAtom } from '../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import { ViewType } from '../models';
|
||||
import { NavigationType } from '../models';
|
||||
|
||||
export default function useMessages() {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -22,8 +22,10 @@ export default function useMessages() {
|
||||
break;
|
||||
case DashboardCommand.viewData:
|
||||
setViewData(message.data.data);
|
||||
if (message.data.data?.type === ViewType.Media) {
|
||||
setView(ViewType.Media);
|
||||
if (message.data.data?.type === NavigationType.Media) {
|
||||
setView(NavigationType.Media);
|
||||
} else if (message.data.data?.type === NavigationType.Contents) {
|
||||
setView(NavigationType.Contents);
|
||||
}
|
||||
break;
|
||||
case DashboardCommand.settings:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum DashboardViewType {
|
||||
Grid = 1,
|
||||
List
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export enum ViewType {
|
||||
export enum NavigationType {
|
||||
Contents = "contents",
|
||||
Media = "media"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { VersionInfo } from '../../models/VersionInfo';
|
||||
import { ViewType } from '../state';
|
||||
import { ContentFolder } from '../../models/ContentFolder';
|
||||
import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models';
|
||||
import { SortingOption } from './SortingOption';
|
||||
import { DashboardViewType } from '.';
|
||||
|
||||
export interface Settings {
|
||||
beta: boolean;
|
||||
@@ -14,7 +14,7 @@ export interface Settings {
|
||||
categories: string[];
|
||||
openOnStart: boolean | null;
|
||||
versionInfo: VersionInfo;
|
||||
pageViewType: ViewType | undefined;
|
||||
pageViewType: DashboardViewType | undefined;
|
||||
mediaSnippet: string[];
|
||||
contentTypes: ContentType[];
|
||||
contentFolders: ContentFolder[];
|
||||
@@ -28,10 +28,14 @@ export interface Settings {
|
||||
|
||||
export interface DashboardState {
|
||||
contents: ViewState;
|
||||
media: ViewState;
|
||||
media: MediaViewState;
|
||||
}
|
||||
|
||||
export interface ViewState {
|
||||
sorting: SortingOption | null | undefined;
|
||||
defaultSorting: string | null | undefined;
|
||||
}
|
||||
|
||||
export interface MediaViewState extends ViewState {
|
||||
selectedFolder: string | null | undefined;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './DashboardViewType';
|
||||
export * from './NavigationType';
|
||||
export * from './Page';
|
||||
export * from './Settings';
|
||||
export * from './SortingOption';
|
||||
export * from './Status';
|
||||
export * from './ViewType';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
import { ViewType } from '../../models';
|
||||
import { NavigationType } from '../../models';
|
||||
|
||||
export const DashboardViewAtom = atom<ViewType>({
|
||||
export const DashboardViewAtom = atom<NavigationType>({
|
||||
key: 'DashboardViewAtom',
|
||||
default: ViewType.Contents
|
||||
default: NavigationType.Contents
|
||||
});
|
||||
@@ -1,11 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export enum ViewType {
|
||||
Grid = 1,
|
||||
List
|
||||
}
|
||||
|
||||
export const ViewAtom = atom<ViewType>({
|
||||
export const ViewAtom = atom<DashboardViewType>({
|
||||
key: 'ViewAtom',
|
||||
default: ViewType.Grid
|
||||
default: DashboardViewType.Grid
|
||||
});
|
||||
+9
-1
@@ -45,7 +45,15 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Pages dashboard
|
||||
Dashboard.init();
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Dashboard.open(data);
|
||||
if (!data) {
|
||||
Dashboard.open({ type: "contents" });
|
||||
} else {
|
||||
Dashboard.open(data);
|
||||
}
|
||||
}));
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardMedia, (data?: DashboardData) => {
|
||||
Dashboard.open({ type: "media" });
|
||||
}));
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Template } from "../commands/Template";
|
||||
import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
|
||||
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
|
||||
import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models";
|
||||
import { Extension } from "./Extension";
|
||||
import { FrameworkDetector } from "./FrameworkDetector";
|
||||
import { Settings } from "./SettingsHelper";
|
||||
|
||||
|
||||
export class DashboardSettings {
|
||||
|
||||
public static async get() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = await Template.isInitialized();
|
||||
|
||||
return {
|
||||
beta: ext.isBetaVersion(),
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER),
|
||||
folders: Folders.get(),
|
||||
initialized: isInitialized,
|
||||
tags: Settings.getTaxonomy(TaxonomyType.Tag),
|
||||
categories: Settings.getTaxonomy(TaxonomyType.Category),
|
||||
openOnStart: Settings.get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
versionInfo: ext.getVersion(),
|
||||
pageViewType: await ext.getState<DashboardViewType | undefined>(ExtensionState.PagesView, "workspace"),
|
||||
mediaSnippet: Settings.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
|
||||
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
|
||||
customSorting: Settings.get<SortingSetting[]>(SETTINGS_CONTENT_SORTING),
|
||||
contentFolders: Folders.get(),
|
||||
crntFramework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
|
||||
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
|
||||
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type && s.type !== ScriptType.Content),
|
||||
dashboardState: {
|
||||
contents: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
|
||||
defaultSorting: Settings.get<string>(SETTINGS_CONTENT_SORTING_DEFAULT)
|
||||
},
|
||||
media: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
|
||||
defaultSorting: Settings.get<string>(SETTINGS_MEDIA_SORTING_DEFAULT),
|
||||
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace")
|
||||
}
|
||||
}
|
||||
} as ISettings
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { existsSync, renameSync } from "fs";
|
||||
import { basename, join } from "path";
|
||||
import { extensions, Uri, ExtensionContext, window, workspace, commands } from "vscode";
|
||||
import { extensions, Uri, ExtensionContext, window, workspace, commands, ExtensionMode } from "vscode";
|
||||
import { Folders, WORKSPACE_PLACEHOLDER } from "../commands/Folders";
|
||||
import { EXTENSION_NAME, GITHUB_LINK, SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, DEFAULT_CONTENT_TYPE_NAME, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, DefaultFields, LocalStore, SETTING_TEMPLATES_FOLDER } from "../constants";
|
||||
import { ContentType } from "../models";
|
||||
@@ -74,6 +74,20 @@ export class Extension {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of the extension
|
||||
*/
|
||||
public get title(): string {
|
||||
return this.ctx.extension.packageJSON.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the extension is in production/development mode
|
||||
*/
|
||||
public get isProductionMode(): boolean {
|
||||
return this.ctx.extensionMode === ExtensionMode.Production;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current version information for the extension
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Extension } from './Extension';
|
||||
import { OutputChannel, window } from 'vscode';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export class Logger {
|
||||
private static instance: Logger;
|
||||
private static channel: OutputChannel | null = null;
|
||||
|
||||
private constructor() {
|
||||
const title = Extension.getInstance().title;
|
||||
Logger.channel = window.createOutputChannel(title);
|
||||
}
|
||||
|
||||
public static getInstance(): Logger {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger();
|
||||
}
|
||||
return Logger.instance;
|
||||
}
|
||||
|
||||
public static info(message: string, type: "INFO" | "WARNING" | "ERROR" = "INFO"): void {
|
||||
if (!Logger.channel) {
|
||||
Logger.getInstance();
|
||||
}
|
||||
|
||||
Logger.channel?.appendLine(`["${type}" - ${format(new Date(), "HH:MM:ss")}] ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
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, SETTINGS_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 { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { commands, Uri, workspace, window, Position } from "vscode";
|
||||
import imageSize from "image-size";
|
||||
import { EditorHelper } from "@estruyf/vscode";
|
||||
import { ExplorerView } from "../explorerView/ExplorerView";
|
||||
|
||||
|
||||
export class MediaHelpers {
|
||||
private static media: MediaInfo[] = [];
|
||||
|
||||
/**
|
||||
* Retrieve all media files
|
||||
* @param page
|
||||
* @param requestedFolder
|
||||
* @param sort
|
||||
* @returns
|
||||
*/
|
||||
public static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
const contentFolders = Folders.get();
|
||||
const viewData = Dashboard.viewData;
|
||||
let selectedFolder = requestedFolder;
|
||||
|
||||
const ext = Extension.getInstance();
|
||||
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
|
||||
|
||||
// If the static folder is not set, retreive the last opened location
|
||||
if (!selectedFolder) {
|
||||
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
|
||||
|
||||
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
|
||||
// Support for page bundles
|
||||
if (viewData?.data?.filePath && viewData?.data?.filePath.endsWith('index.md')) {
|
||||
const folderPath = parse(viewData.data.filePath).dir;
|
||||
selectedFolder = folderPath;
|
||||
} else if (stateValue && existsSync(stateValue)) {
|
||||
selectedFolder = stateValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go to the home folder
|
||||
if (selectedFolder === HOME_PAGE_NAVIGATION_ID) {
|
||||
selectedFolder = '';
|
||||
}
|
||||
|
||||
let relSelectedFolderPath = selectedFolder;
|
||||
const parsedPath = parseWinPath(wsFolder?.fsPath || "");
|
||||
if (selectedFolder && selectedFolder.startsWith(parsedPath)) {
|
||||
relSelectedFolderPath = selectedFolder.replace(parsedPath, '');
|
||||
}
|
||||
|
||||
if (relSelectedFolderPath.startsWith('/')) {
|
||||
relSelectedFolderPath = relSelectedFolderPath.substring(1);
|
||||
}
|
||||
|
||||
let allMedia: MediaInfo[] = [];
|
||||
|
||||
if (relSelectedFolderPath) {
|
||||
const files = await workspace.findFiles(join(relSelectedFolderPath, '/*'));
|
||||
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
|
||||
|
||||
allMedia = [...media];
|
||||
} else {
|
||||
if (staticFolder) {
|
||||
const folderSearch = join(staticFolder || "", '/*');
|
||||
const files = await workspace.findFiles(folderSearch);
|
||||
const media = await MediaHelpers.updateMediaData(MediaHelpers.filterMedia(files));
|
||||
|
||||
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 (crntSort?.type === SortType.string) {
|
||||
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
|
||||
} else if (crntSort?.type === SortType.date) {
|
||||
allMedia = allMedia.sort(Sorting.dateWithFallback("mtime", "fsPath"));
|
||||
} else {
|
||||
allMedia = allMedia.sort(Sorting.alphabetically("fsPath"));
|
||||
}
|
||||
|
||||
if (crntSort?.order === SortOrder.desc) {
|
||||
allMedia = allMedia.reverse();
|
||||
}
|
||||
|
||||
MediaHelpers.media = Object.assign([], allMedia);
|
||||
|
||||
let files: MediaInfo[] = MediaHelpers.media;
|
||||
|
||||
// Retrieve the total after filtering and before the slicing happens
|
||||
const total = files.length;
|
||||
|
||||
// Get media set
|
||||
files = files.slice(page * 16, ((page + 1) * 16));
|
||||
files = files.map((file) => {
|
||||
try {
|
||||
const metadata = MediaLibrary.getInstance().get(file.fsPath);
|
||||
|
||||
return {
|
||||
...file,
|
||||
dimensions: imageSize(file.fsPath),
|
||||
...metadata
|
||||
};
|
||||
} catch (e) {
|
||||
return {...file};
|
||||
}
|
||||
});
|
||||
files = files.filter(f => f.mtime !== undefined);
|
||||
|
||||
// Retrieve all the folders
|
||||
let allContentFolders: string[] = [];
|
||||
let allFolders: string[] = [];
|
||||
|
||||
if (selectedFolder) {
|
||||
if (existsSync(selectedFolder)) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "");
|
||||
if (staticPath && existsSync(staticPath)) {
|
||||
allFolders = readdirSync(staticPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name)));
|
||||
}
|
||||
}
|
||||
|
||||
// Store the last opened folder
|
||||
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
|
||||
|
||||
let sortedFolders = [...allContentFolders, ...allFolders];
|
||||
sortedFolders = sortedFolders.sort((a, b) => {
|
||||
if (a.toLowerCase() < b.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.toLowerCase() > b.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (crntSort?.order === SortOrder.desc) {
|
||||
sortedFolders = sortedFolders.reverse();
|
||||
}
|
||||
|
||||
return {
|
||||
media: files,
|
||||
total: total,
|
||||
folders: sortedFolders,
|
||||
selectedFolder
|
||||
} as MediaPaths
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset media array
|
||||
*/
|
||||
public static resetMedia() {
|
||||
MediaHelpers.media = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the dropped file in the current folder
|
||||
* @param fileData
|
||||
*/
|
||||
public static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) {
|
||||
if (fileName && contents) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
const wsPath = wsFolder ? wsFolder.fsPath : "";
|
||||
let absFolderPath = join(wsPath, staticFolder || "");
|
||||
|
||||
if (folder) {
|
||||
absFolderPath = folder;
|
||||
}
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
absFolderPath = join(wsPath, folder || "");
|
||||
}
|
||||
|
||||
if (!existsSync(absFolderPath)) {
|
||||
Notifications.error(`We couldn't find your selected folder.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const staticPath = join(absFolderPath, fileName);
|
||||
const imgData = decodeBase64Image(contents);
|
||||
|
||||
if (imgData) {
|
||||
writeFileSync(staticPath, imgData.data);
|
||||
Notifications.info(`File ${fileName} uploaded to: ${folder}`);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Notifications.error(`Something went wrong uploading ${fileName}`);
|
||||
throw new Error(`Something went wrong uploading ${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the selected file
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public static async deleteFile({ file, page, folder }: { file: string; page: number; folder: string | null; }) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
unlinkSync(file);
|
||||
|
||||
MediaHelpers.media = [];
|
||||
return true;
|
||||
} catch(err: any) {
|
||||
Notifications.error(`Something went wrong deleting ${basename(file)}`);
|
||||
throw new Error(`Something went wrong deleting ${basename(file)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an image into the front matter or contents
|
||||
* @param data
|
||||
*/
|
||||
public static async insertMediaToMarkdown(data: any) {
|
||||
if (data?.file && data?.image) {
|
||||
if (!data?.position) {
|
||||
await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`);
|
||||
}
|
||||
|
||||
await EditorHelper.showFile(data.file);
|
||||
Dashboard.resetViewData();
|
||||
|
||||
const extensionUri = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
|
||||
if (data?.position) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const editor = window.activeTextEditor;
|
||||
const line = data.position.line;
|
||||
const character = data.position.character;
|
||||
if (line) {
|
||||
let imgPath = data.image;
|
||||
const filePath = data.file;
|
||||
const absImgPath = join(parseWinPath(wsFolder?.fsPath || ""), imgPath);
|
||||
|
||||
const imgDir = dirname(absImgPath);
|
||||
const fileDir = dirname(filePath);
|
||||
|
||||
if (imgDir === fileDir) {
|
||||
imgPath = join('/', basename(imgPath));
|
||||
|
||||
// Snippets are already parsed, so update the URL of the image
|
||||
if (data.snippet) {
|
||||
data.snippet = data.snippet.replace(data.image, imgPath);
|
||||
}
|
||||
}
|
||||
|
||||
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || ``));
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
} else {
|
||||
panel.getMediaSelection();
|
||||
panel.updateMetadata({field: data.fieldName, value: data.image });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of a media file
|
||||
* @param data
|
||||
*/
|
||||
public static updateMetadata(data: any) {
|
||||
const { file, filename, page, folder, ...metadata }: { file:string; filename:string; page: number; folder: string | null; metadata: any; } = data;
|
||||
|
||||
const mediaLib = MediaLibrary.getInstance();
|
||||
mediaLib.set(file, metadata);
|
||||
|
||||
// Check if filename needs to be updated
|
||||
mediaLib.updateFilename(file, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the media files
|
||||
*/
|
||||
private static filterMedia(files: Uri[]) {
|
||||
return files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext);
|
||||
}).map((file) => ({
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.getWebview()?.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
} as MediaInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the retrieved files
|
||||
* @param files
|
||||
*/
|
||||
private static async updateMediaData(files: MediaInfo[]) {
|
||||
files = files.map((m: MediaInfo) => {
|
||||
const stats = statSync(m.fsPath);
|
||||
return Object.assign({}, m, stats);
|
||||
});
|
||||
|
||||
return Object.assign([], files);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { MediaHelpers } from './MediaHelpers';
|
||||
import { workspace } from 'vscode';
|
||||
import { JsonDB } from 'node-json-db/dist/JsonDB';
|
||||
import { basename, dirname, join, parse } from 'path';
|
||||
@@ -34,7 +33,7 @@ export class MediaLibrary {
|
||||
f.oldUri.path.endsWith('.png') ||
|
||||
f.oldUri.path.endsWith('.gif')) {
|
||||
this.rename(f.oldUri.fsPath, f.newUri.fsPath);
|
||||
Dashboard.resetMedia();
|
||||
MediaHelpers.resetMedia();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -89,7 +88,7 @@ export class MediaLibrary {
|
||||
} else {
|
||||
renameSync(filePath, newPath);
|
||||
this.rename(filePath, newPath);
|
||||
Dashboard.resetMedia();
|
||||
MediaHelpers.resetMedia();
|
||||
}
|
||||
} catch(err) {
|
||||
Notifications.error(`Something went wrong updating "${name}" to "${filename}".`);
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { window } from "vscode";
|
||||
import { Logger } from ".";
|
||||
import { EXTENSION_NAME } from "../constants";
|
||||
|
||||
|
||||
export class Notifications {
|
||||
|
||||
public static info(message: string, items?: any): Thenable<string> {
|
||||
Logger.info(`${EXTENSION_NAME}: ${message}`, "INFO");
|
||||
return window.showInformationMessage(`${EXTENSION_NAME}: ${message}`, items);
|
||||
}
|
||||
|
||||
public static warning(message: string, items?: any): Thenable<string> {
|
||||
Logger.info(`${EXTENSION_NAME}: ${message}`, "WARNING");
|
||||
return window.showWarningMessage(`${EXTENSION_NAME}: ${message}`, items);
|
||||
}
|
||||
|
||||
public static error(message: string, items?: any): Thenable<string> {
|
||||
Logger.info(`${EXTENSION_NAME}: ${message}`, "ERROR");
|
||||
return window.showErrorMessage(`${EXTENSION_NAME}: ${message}`, items);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,27 @@
|
||||
export * from './ArticleHelper';
|
||||
export * from './ContentType';
|
||||
export * from './CustomScript';
|
||||
export * from './DashboardSettings';
|
||||
export * from './DateHelper';
|
||||
export * from './Extension';
|
||||
export * from './FilesHelper';
|
||||
export * from './FrameworkDetector';
|
||||
export * from './GroupBy';
|
||||
export * from './ImageHelper';
|
||||
export * from './Logger';
|
||||
export * from './MediaLibrary';
|
||||
export * from './MessageHelper';
|
||||
export * from './Notifications';
|
||||
export * from './Questions';
|
||||
export * from './Sanitize';
|
||||
export * from './SeoHelper';
|
||||
export * from './SettingsHelper';
|
||||
export * from './SlugHelper';
|
||||
export * from './Sorting';
|
||||
export * from './StringHelpers';
|
||||
export * from './TomlEngine';
|
||||
export * from './decodeBase64Image';
|
||||
export * from './getNonce';
|
||||
export * from './isValidFile';
|
||||
export * from './openFileInEditor';
|
||||
export * from './parseWinPath';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export const isValidFile = (fileName: string) => {
|
||||
return fileName.endsWith(`.md`) ||
|
||||
fileName.endsWith(`.markdown`) ||
|
||||
fileName.endsWith(`.mdx`);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Logger } from "../helpers/Logger";
|
||||
|
||||
|
||||
export abstract class BaseListener {
|
||||
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: DashboardCommand, data: any) {
|
||||
Logger.info(`Sending message to webview: ${command}`);
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command,
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { ExtensionState } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Extension } from "../helpers";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class DashboardListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getViewType:
|
||||
if (Dashboard.viewData) {
|
||||
this.sendMsg(DashboardCommand.viewData, Dashboard.viewData);
|
||||
}
|
||||
break;
|
||||
case DashboardMessage.reload:
|
||||
Dashboard.reload();
|
||||
break;
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { commands, env } from "vscode";
|
||||
import { SettingsListener } from ".";
|
||||
import { COMMAND_NAME } from "../constants";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { CustomScript, Extension } from "../helpers";
|
||||
import { openFileInEditor } from "../helpers/openFileInEditor";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class ExtensionListener extends BaseListener {
|
||||
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.openFile:
|
||||
openFileInEditor(msg.data);
|
||||
break;
|
||||
case DashboardMessage.initializeProject:
|
||||
commands.executeCommand(COMMAND_NAME.init, SettingsListener.getSettings);
|
||||
break;
|
||||
case DashboardMessage.copyToClipboard:
|
||||
env.clipboard.writeText(msg.data);
|
||||
break;
|
||||
case DashboardMessage.runCustomScript:
|
||||
CustomScript.run(msg?.data?.script, msg?.data?.path);
|
||||
break;
|
||||
case DashboardMessage.setState:
|
||||
this.setState(msg?.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static setState(data: any) {
|
||||
const { key, value } = data;
|
||||
if (key && value) {
|
||||
Extension.getInstance().setState(key, value, "workspace");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { MediaHelpers } from './../helpers/MediaHelpers';
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { SortingOption } from '../dashboardWebView/models';
|
||||
import { commands } from 'vscode';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
|
||||
|
||||
export class MediaListener extends BaseListener {
|
||||
private static timers: { [folder: string]: any } = {};
|
||||
|
||||
public static async process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getMedia:
|
||||
const { page, folder, sorting } = msg?.data;
|
||||
this.sendMediaFiles(page, folder, sorting);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
MediaHelpers.resetMedia();
|
||||
this.sendMediaFiles(0, msg?.data?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
this.store(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.deleteMedia:
|
||||
this.delete(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
this.update(msg.data);
|
||||
break;
|
||||
case DashboardMessage.createMediaFolder:
|
||||
await commands.executeCommand(COMMAND_NAME.createFolder, msg?.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the media files to the dashboard
|
||||
* @param page
|
||||
* @param folder
|
||||
* @param sorting
|
||||
*/
|
||||
public static async sendMediaFiles(page: number = 0, folder: string = '', sorting: SortingOption | null = null) {
|
||||
const files = await MediaHelpers.getMedia(page, folder, sorting);
|
||||
this.sendMsg(DashboardCommand.media, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the file and send a message after multiple uploads
|
||||
* @param data
|
||||
*/
|
||||
private static async store(data: any) {
|
||||
try {
|
||||
const { folder } = data;
|
||||
await MediaHelpers.saveFile(data);
|
||||
|
||||
const folderPath = `${folder}`;
|
||||
if (this.timers[folderPath]) {
|
||||
clearTimeout(this.timers[folderPath]);
|
||||
delete this.timers[folderPath];
|
||||
}
|
||||
|
||||
this.timers[folderPath] = setTimeout(() => {
|
||||
MediaHelpers.resetMedia();
|
||||
this.sendMediaFiles(0, folder || "");
|
||||
delete this.timers[folderPath];
|
||||
}, 500);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a media file
|
||||
* @param data
|
||||
*/
|
||||
private static delete(data: { file: string; page: number; folder: string | null; }) {
|
||||
try {
|
||||
MediaHelpers.deleteFile(data);
|
||||
this.sendMediaFiles(data.page || 0, data.folder || "");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update media metadata
|
||||
* @param data
|
||||
*/
|
||||
private static update(data: any) {
|
||||
try {
|
||||
const { page, folder } = data;
|
||||
|
||||
MediaHelpers.updateMetadata(data);
|
||||
|
||||
this.sendMediaFiles(page || 0, folder || "");
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { isValidFile } from './../helpers/isValidFile';
|
||||
import { existsSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { commands, Uri } from "vscode";
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Page } from "../dashboardWebView/models";
|
||||
import { ArticleHelper, Settings } from "../helpers";
|
||||
import { ContentType } from "../helpers/ContentType";
|
||||
import { DateHelper } from "../helpers/DateHelper";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class PagesListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static async process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getData:
|
||||
this.getPagesData();
|
||||
break;
|
||||
case DashboardMessage.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case DashboardMessage.createByContentType:
|
||||
await commands.executeCommand(COMMAND_NAME.createByContentType);
|
||||
break;
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
private static async getPagesData() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
|
||||
const dateField = Settings.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
|
||||
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
|
||||
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
|
||||
if (folderInfo) {
|
||||
for (const folder of folderInfo) {
|
||||
for (const file of folder.lastModified) {
|
||||
if (isValidFile(file.fileName)) {
|
||||
try {
|
||||
const article = ArticleHelper.getFrontMatterByPath(file.filePath);
|
||||
|
||||
if (article?.data.title) {
|
||||
const page: Page = {
|
||||
...article.data,
|
||||
// FrontMatter properties
|
||||
fmFolder: folder.title,
|
||||
fmModified: file.mtime,
|
||||
fmFilePath: file.filePath,
|
||||
fmFileName: file.fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null,
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
date: article?.data[dateField] || "",
|
||||
draft: article?.data.draft,
|
||||
description: article?.data[descriptionField] || "",
|
||||
};
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
if (article?.data[previewField] && wsFolder) {
|
||||
let fieldValue = article?.data[previewField];
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(file.filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.getWebview()?.asWebviewUri(previewUri);
|
||||
page[previewField] = preview?.toString() || "";
|
||||
} else {
|
||||
page[previewField] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
}
|
||||
} catch (error: any) {
|
||||
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.sendMsg(DashboardCommand.pages, pages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { DashboardSettings, Settings } from "../helpers";
|
||||
import { FrameworkDetector } from "../helpers/FrameworkDetector";
|
||||
import { Framework } from "../models";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class SettingsListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.getData:
|
||||
this.getSettings();
|
||||
break;
|
||||
case DashboardMessage.updateSetting:
|
||||
this.update(msg.data);
|
||||
break;
|
||||
case DashboardMessage.setFramework:
|
||||
this.setFramework(msg?.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a setting from the dashboard
|
||||
* @param data
|
||||
*/
|
||||
private static async update(data: { name: string, value: any }) {
|
||||
if (data.name) {
|
||||
await Settings.update(data.name, data.value);
|
||||
this.getSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings for the dashboard
|
||||
*/
|
||||
public static async getSettings() {
|
||||
const settings = await DashboardSettings.get();
|
||||
|
||||
this.sendMsg(DashboardCommand.settings, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current site-generator or framework + related settings
|
||||
* @param frameworkId
|
||||
*/
|
||||
private static setFramework(frameworkId: string | null) {
|
||||
Settings.update(SETTINGS_FRAMEWORK_ID, frameworkId, true);
|
||||
|
||||
if (frameworkId) {
|
||||
const allFrameworks = FrameworkDetector.getAll();
|
||||
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
|
||||
if (framework) {
|
||||
Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true);
|
||||
} else {
|
||||
Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './DashboardListener';
|
||||
export * from './ExtensionListener';
|
||||
export * from './MediaListener';
|
||||
export * from './PagesListener';
|
||||
export * from './SettingsListener';
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { isValidFile } from '../../helpers/isValidFile';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { CommandToCode } from '../CommandToCode';
|
||||
import { FileIcon } from './Icons/FileIcon';
|
||||
@@ -19,7 +20,7 @@ const FileItem: React.FunctionComponent<IFileItemProps> = ({ name, path }: React
|
||||
<li className={`file_list__items__item`}
|
||||
onClick={openFile}>
|
||||
{
|
||||
(name.endsWith('.md') || name.endsWith('.markdown') || name.endsWith('.mdx')) ? (
|
||||
(isValidFile(name)) ? (
|
||||
<MarkdownIcon />
|
||||
) : (
|
||||
<FileIcon />
|
||||
|
||||
Reference in New Issue
Block a user