diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index f7ce1ebd..e12ab77b 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -4,7 +4,7 @@ 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 { DraftField, Framework, SortingSetting, SortOrder, TaxonomyType } from '../models'; +import { DraftField, Framework, SortingSetting, SortOrder, SortType, TaxonomyType } from '../models'; import { Folders } from './Folders'; import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { DashboardMessage } from '../dashboardWebView/DashboardMessage'; @@ -27,6 +27,7 @@ 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'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -384,13 +385,16 @@ export class Dashboard { if (relSelectedFolderPath) { const files = await workspace.findFiles(join(relSelectedFolderPath, '/*')); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(selectedFolder, Dashboard.filterMedia(files)); + allMedia = [...media]; } else { if (staticFolder) { const folderSearch = join(staticFolder || "", '/*'); + const absFolderSearch = join(wsFolder?.fsPath || "", folderSearch); + const files = await workspace.findFiles(folderSearch); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(absFolderSearch, Dashboard.filterMedia(files)); allMedia = [...media]; } @@ -401,22 +405,20 @@ export class Dashboard { const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1); const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*'); const files = await workspace.findFiles(folderSearch); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(join(wsFolder.fsPath || "", folderSearch), Dashboard.filterMedia(files)); allMedia = [...allMedia, ...media]; } } } - allMedia = allMedia.sort((a, b) => { - if (a.fsPath.toLowerCase() < b.fsPath.toLowerCase()) { - return -1; - } - if (a.fsPath.toLowerCase() > b.fsPath.toLowerCase()) { - return 1; - } - return 0; - }); + if (crntSort?.type === SortType.string) { + allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); + } else if (crntSort?.type === SortType.date) { + allMedia = allMedia.sort(Sorting.date("modified")); + } else { + allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); + } if (crntSort?.order === SortOrder.desc) { allMedia = allMedia.reverse(); @@ -499,6 +501,27 @@ export class Dashboard { }); } + /** + * Update the metadata of the retrieved files + * @param folder + * @param files + */ + private static async updateMediaData(folder: string, files: MediaInfo[]) { + const fileMetadata = await MediaLibrary.getMetadata(folder); + + if (fileMetadata && fileMetadata.length > 0) { + files = files.map((m: MediaInfo) => { + const metadata = fileMetadata.find((f) => m.fsPath.endsWith(f.fileName)); + if (metadata) { + m.modified = metadata.date; + } + return m; + }); + } + + return Object.assign([], files); + } + /** * Retrieve all the markdown pages */ diff --git a/src/dashboardWebView/components/Header/Sorting.tsx b/src/dashboardWebView/components/Header/Sorting.tsx index bd80a69c..cc1f4207 100644 --- a/src/dashboardWebView/components/Header/Sorting.tsx +++ b/src/dashboardWebView/components/Header/Sorting.tsx @@ -17,7 +17,8 @@ export interface ISortingProps { } export const sortOptions: SortingOption[] = [ - { name: "Last modified", id: SortOption.LastModified, order: SortOrder.desc, type: SortType.string }, + { name: "Last modified (asc)", id: SortOption.LastModifiedAsc, order: SortOrder.asc, type: SortType.date }, + { name: "Last modified (desc)", id: SortOption.LastModifiedDesc, order: SortOrder.desc, type: SortType.date }, { name: "By filename (asc)", id: SortOption.FileNameAsc, order: SortOrder.asc, type: SortType.string }, { name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string }, ]; @@ -36,7 +37,7 @@ export const Sorting: React.FunctionComponent = ({disableCustomSo setCrntSorting(value) }; - let allOptions = view === ViewType.Contents ? [...sortOptions] : [...sortOptions.slice(1)]; + let allOptions = [...sortOptions]; if (settings?.customSorting && !disableCustomSorting) { allOptions = [...allOptions, ...settings.customSorting.map((s) => ({ title: s.title || s.name, diff --git a/src/dashboardWebView/constants/SortOption.ts b/src/dashboardWebView/constants/SortOption.ts index 0628a1c5..0d138217 100644 --- a/src/dashboardWebView/constants/SortOption.ts +++ b/src/dashboardWebView/constants/SortOption.ts @@ -1,5 +1,6 @@ export enum SortOption { - LastModified = "LastModified", + LastModifiedAsc = "LastModifiedAsc", + LastModifiedDesc = "LastModifiedDesc", FileNameAsc = "FileNameAsc", FileNameDesc = "FileNameDesc" } \ No newline at end of file diff --git a/src/dashboardWebView/hooks/usePages.tsx b/src/dashboardWebView/hooks/usePages.tsx index a72c7e00..6811f1e6 100644 --- a/src/dashboardWebView/hooks/usePages.tsx +++ b/src/dashboardWebView/hooks/usePages.tsx @@ -6,7 +6,7 @@ import Fuse from 'fuse.js'; import { useRecoilState, useRecoilValue } from 'recoil'; import { CategorySelector, FolderSelector, SearchSelector, SettingsSelector, SortingAtom, TabSelector, TagSelector } from '../state'; import { SortOrder, SortType } from '../../models'; -import { DateHelper } from '../../helpers/DateHelper'; +import { Sorting } from '../../helpers/Sorting'; const fuseOptions: Fuse.IFuseOptions = { keys: [ @@ -26,29 +26,6 @@ export default function usePages(pages: Page[]) { const tag = useRecoilValue(TagSelector); const category = useRecoilValue(CategorySelector); - // Sort field value alphabetically - const sortAlphabetically = (property: string) => { - return (a: Page, b: Page) => { - if (a[property] < b[property]) { - return -1; - } - if (a[property] > b[property]) { - return 1; - } - return 0; - }; - }; - - // Sort by date - const sortByDate = (property: string) => { - return (a: Page, b: Page) => { - const dateA = DateHelper.tryParse(a[property]); - const dateB = DateHelper.tryParse(b[property]); - - return (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime(); - }; - }; - useEffect(() => { const draftField = settings?.draftField; let usedSorting = sorting; @@ -91,27 +68,30 @@ export default function usePages(pages: Page[]) { // Sort the pages let pagesSorted: Page[] = Object.assign([], pagesToShow); + debugger; if (!search) { if (sorting && sorting.id === SortOption.FileNameAsc) { - pagesSorted = pagesSorted.sort(sortAlphabetically("fmFileName")); + pagesSorted = pagesSorted.sort(Sorting.alphabetically("fmFileName")); } else if (sorting && sorting.id === SortOption.FileNameDesc) { - pagesSorted = pagesSorted.sort(sortAlphabetically("fmFileName")).reverse(); - } else if (sorting && sorting.id === SortOption.LastModified) { - pagesSorted = pagesSorted.sort((a, b) => b.fmModified - a.fmModified); + pagesSorted = pagesSorted.sort(Sorting.alphabetically("fmFileName")).reverse(); + } else if (sorting && sorting.id === SortOption.LastModifiedAsc) { + pagesSorted = pagesSorted.sort(Sorting.number("fmModified")); + } else if (sorting && sorting.id === SortOption.LastModifiedDesc) { + pagesSorted = pagesSorted.sort(Sorting.number("fmModified")).reverse(); } else if (sorting && sorting.id && sorting.name) { const { order, name, type } = sorting; if (type === SortType.string) { - pagesSorted = pagesSorted.sort(sortAlphabetically(name)); + pagesSorted = pagesSorted.sort(Sorting.alphabetically(name)); } else if (type === SortType.date) { - pagesSorted = pagesSorted.sort(sortByDate(name)); + pagesSorted = pagesSorted.sort(Sorting.date(name)); } if (order === SortOrder.desc) { pagesSorted = pagesSorted.reverse(); } } else { - pagesSorted = pagesSorted.sort((a, b) => b.fmModified - a.fmModified); + pagesSorted = pagesSorted.sort(Sorting.number("fmModified")).reverse(); } } diff --git a/src/dashboardWebView/state/atom/SortingAtom.ts b/src/dashboardWebView/state/atom/SortingAtom.ts index f80b4391..91ed9d20 100644 --- a/src/dashboardWebView/state/atom/SortingAtom.ts +++ b/src/dashboardWebView/state/atom/SortingAtom.ts @@ -2,7 +2,7 @@ import { atom } from 'recoil'; import { SortOption } from '../../constants/SortOption'; import { SortingOption } from '../../models/SortingOption'; -export const DEFAULT_SORTING_OPTION = SortOption.LastModified; +export const DEFAULT_SORTING_OPTION = SortOption.LastModifiedDesc; export const SortingAtom = atom({ key: 'SortingAtom', diff --git a/src/helpers/MediaLibrary.ts b/src/helpers/MediaLibrary.ts index f7f09a75..6a44e5f4 100644 --- a/src/helpers/MediaLibrary.ts +++ b/src/helpers/MediaLibrary.ts @@ -1,5 +1,7 @@ +import * as os from 'os'; +import { DateHelper } from './DateHelper'; import { Dashboard } from '../commands/Dashboard'; -import { workspace } from 'vscode'; +import { workspace, env as vscodeEnv } from 'vscode'; import { JsonDB } from 'node-json-db/dist/JsonDB'; import { basename, dirname, join, parse } from 'path'; import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders'; @@ -7,6 +9,7 @@ import { existsSync, renameSync } from 'fs'; import { Notifications } from './Notifications'; import { parseWinPath } from './parseWinPath'; import { LocalStore } from '../constants'; +import { exec } from 'child_process'; interface MediaRecord { description: string; @@ -47,6 +50,54 @@ export class MediaLibrary { return MediaLibrary.instance; } + public static async getMetadata(path: string): Promise<{ date: Date, fileName: string }[] | null> { + return new Promise((resolve) => { + let command: string = ""; + + // Run bash scripts to retrieve metadata faster + if (os.type() === "Darwin") { + command = `for file in *; do + fdate=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$file") + echo "$file,$fdate" +done`; + } else if (os.type() === "Windows_NT") { + resolve(null); + return; + } else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { + command = `for file in *; do + fdate=$(stat -c "%y" "$file") + echo "$file,$fdate" +done`; + } else { + resolve(null); + return; + } + + exec(`cd ${path} && ${command}`, (error, stdout, stderr) => { + if (error) { + resolve(null); + return; + } + + const lines = stdout.split('\n'); + const metadata: any[] = []; + + let lineData: any = {}; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(','); + metadata.push({ + fileName: parts[0], + date: DateHelper.tryParse(parts[1]) + }); + } + + resolve(metadata); + }); + }); + } + public get(id: string): MediaRecord | undefined { try { const fileId = this.parsePath(id); diff --git a/src/helpers/Sorting.ts b/src/helpers/Sorting.ts new file mode 100644 index 00000000..20055e6a --- /dev/null +++ b/src/helpers/Sorting.ts @@ -0,0 +1,47 @@ +import { DateHelper } from "./DateHelper"; + + +export class Sorting { + + /** + * Sort field value alphabetically + * @param property + * @returns + */ + public static alphabetically = (property: string) => { + return (a: any, b: any) => { + if (a[property] < b[property]) { + return -1; + } + if (a[property] > b[property]) { + return 1; + } + return 0; + }; + }; + + /** + * Sort by date + * @param property + * @returns + */ + public static date = (property: string) => { + return (a: any, b: any) => { + const dateA = DateHelper.tryParse(a[property]); + const dateB = DateHelper.tryParse(b[property]); + + return (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime(); + }; + }; + + /** + * Sort by number + * @param property + * @returns + */ + public static number = (property: string) => { + return (a: any, b: any) => { + return a[property] - b[property]; + }; + }; +} \ No newline at end of file diff --git a/src/models/MediaPaths.ts b/src/models/MediaPaths.ts index 967f1d76..6dca358f 100644 --- a/src/models/MediaPaths.ts +++ b/src/models/MediaPaths.ts @@ -15,4 +15,5 @@ export interface MediaInfo { dimensions: ISizeCalculationResult | undefined; caption?: string | undefined; alt?: string | undefined; + modified?: Date | undefined; } \ No newline at end of file