From d97a11f8b5e1c7d7bad3c8cee736eb3134e3906f Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 12 Oct 2021 20:34:06 +0200 Subject: [PATCH] #146 - Date parsing logic added with fallbacks --- CHANGELOG.md | 1 + src/commands/Dashboard.ts | 4 +- src/dashboardWebView/components/DateField.tsx | 9 +-- src/dashboardWebView/models/Page.ts | 2 +- src/helpers/DateHelper.ts | 64 +++++++++++++++++++ .../components/Fields/DateTimeField.tsx | 9 +-- src/panelWebView/components/Metadata.tsx | 10 ++- 7 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 src/helpers/DateHelper.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 92543e19..f9a28b42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#142](https://github.com/estruyf/vscode-front-matter/issues/142): Fix for unknown tags where it throws an error - [#143](https://github.com/estruyf/vscode-front-matter/issues/143): Fix for duplicate values in the file list - [#144](https://github.com/estruyf/vscode-front-matter/issues/144): Fix for `toISOString` does not exist on object +- [#146](https://github.com/estruyf/vscode-front-matter/issues/146): Pelican date format cannot be parsed ## [5.0.0] - 2021-10-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.0.0) diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index b41c47b6..c0487931 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -14,7 +14,6 @@ import { Template } from './Template'; import { Notifications } from '../helpers/Notifications'; import { Settings } from '../dashboardWebView/models/Settings'; import { Extension } from '../helpers/Extension'; -import { parseJSON } from 'date-fns'; import { ViewType } from '../dashboardWebView/state'; import { EditorHelper, WebviewHelper } from '@estruyf/vscode'; import { MediaInfo, MediaPaths } from './../models/MediaPaths'; @@ -24,6 +23,7 @@ import { ExplorerView } from '../explorerView/ExplorerView'; import { MediaLibrary } from '../helpers/MediaLibrary'; import imageSize from 'image-size'; import { parseWinPath } from '../helpers/parseWinPath'; +import { DateHelper } from '../helpers/DateHelper'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -447,7 +447,7 @@ export class Dashboard { fmFilePath: file.filePath, fmFileName: file.fileName, fmDraft: article?.data.draft ? "Draft" : "Published", - fmYear: article?.data[dateField] ? parseJSON(article?.data[dateField]).getFullYear() : null, + 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, diff --git a/src/dashboardWebView/components/DateField.tsx b/src/dashboardWebView/components/DateField.tsx index f783209d..9a97478f 100644 --- a/src/dashboardWebView/components/DateField.tsx +++ b/src/dashboardWebView/components/DateField.tsx @@ -1,5 +1,6 @@ -import { format, parseJSON } from 'date-fns'; +import { format } from 'date-fns'; import * as React from 'react'; +import { DateHelper } from '../../helpers/DateHelper'; export interface IDateFieldProps { value: Date | string; @@ -10,9 +11,9 @@ export const DateField: React.FunctionComponent = ({value}: Rea React.useEffect(() => { try { - const parsedValue = typeof value === 'string' ? parseJSON(value) : value; - const dateString = format(parsedValue, 'yyyy-MM-dd'); - setDateValue(dateString); + const parsedValue = typeof value === 'string' ? DateHelper.tryParse(value) : value; + const dateString = parsedValue ? format(parsedValue, 'yyyy-MM-dd') : parsedValue; + setDateValue(dateString || ""); } catch (e) { // Date is invalid } diff --git a/src/dashboardWebView/models/Page.ts b/src/dashboardWebView/models/Page.ts index 536ecd69..d1e037bb 100644 --- a/src/dashboardWebView/models/Page.ts +++ b/src/dashboardWebView/models/Page.ts @@ -6,7 +6,7 @@ export interface Page { fmFileName: string; fmModified: number; fmDraft: "Draft" | "Published", - fmYear: number | null; + fmYear: number | null | undefined; title: string; slug: string; diff --git a/src/helpers/DateHelper.ts b/src/helpers/DateHelper.ts new file mode 100644 index 00000000..c6e069e4 --- /dev/null +++ b/src/helpers/DateHelper.ts @@ -0,0 +1,64 @@ +import { parse, parseISO, parseJSON } from "date-fns"; + + +export class DateHelper { + + public static tryParse(date: any, format?: string): Date | null { + if (!date) { + return null; + } + + if (date instanceof Date) { + return date; + } + + if (typeof date === 'string') { + const jsonParsed = DateHelper.tryParseJson(date); + if (DateHelper.isValid(jsonParsed)) { + return jsonParsed; + } + + const isoParsed = DateHelper.tryParseIso(date); + if (DateHelper.isValid(isoParsed)) { + return isoParsed; + } + + if (format) { + const formatParsed = DateHelper.tryFormatParse(date, format); + if (DateHelper.isValid(formatParsed)) { + return formatParsed; + } + } + } + + return null; + } + + public static isValid(date: any): boolean { + return !isNaN(date.getTime()); + } + + public static tryFormatParse(date: string, format: string): Date | null { + try { + return parse(date, format, new Date()); + } catch (err) { + return null; + } + } + + public static tryParseJson(date: string): Date | null { + try { + return parseJSON(date); + } catch (err) { + return null; + } + } + + public static tryParseIso(date: string): Date | null { + try { + return parseISO(date); + } catch (err) { + return null; + } + } +} \ No newline at end of file diff --git a/src/panelWebView/components/Fields/DateTimeField.tsx b/src/panelWebView/components/Fields/DateTimeField.tsx index 42f244d8..8b125ceb 100644 --- a/src/panelWebView/components/Fields/DateTimeField.tsx +++ b/src/panelWebView/components/Fields/DateTimeField.tsx @@ -3,6 +3,7 @@ import { VsLabel } from '../VscodeComponents'; import { ClockIcon } from '@heroicons/react/outline'; import DatePicker from 'react-datepicker'; import { forwardRef } from 'react'; +import { DateHelper } from '../../../helpers/DateHelper'; export interface IDateTimeFieldProps { label: string; @@ -22,7 +23,7 @@ const CustomInput = forwardRef(({ value, onClick } }); export const DateTimeField: React.FunctionComponent = ({label, date, format, onChange}: React.PropsWithChildren) => { - const [ dateValue, setDateValue ] = React.useState(date); + const [ dateValue, setDateValue ] = React.useState(null); const onDateChange = (date: Date) => { setDateValue(date); @@ -30,10 +31,10 @@ export const DateTimeField: React.FunctionComponent = ({lab }; React.useEffect(() => { - const crntValue = typeof date?.toISOString === 'function' ? date.toISOString() : date; - const stateValue = typeof dateValue?.toISOString === 'function' ? dateValue.toISOString() : dateValue; + const crntValue = DateHelper.tryParse(date, format); + const stateValue = DateHelper.tryParse(dateValue, format); - if (crntValue !== stateValue) { + if (crntValue?.toISOString() !== stateValue?.toISOString()) { setDateValue(date); } }, [ date ]); diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index 697ad3cb..d79d031f 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -8,7 +8,6 @@ import { Toggle } from './Fields/Toggle'; import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon'; import { TagIcon } from './Icons/TagIcon'; import { TagPicker } from './TagPicker'; -import { parseJSON } from 'date-fns'; import { DateTimeField } from './Fields/DateTimeField'; import { TextField } from './Fields/TextField'; import "react-datepicker/dist/react-datepicker.css"; @@ -17,6 +16,7 @@ import { ListUnorderedIcon } from './Icons/ListUnorderedIcon'; import { NumberField } from './Fields/NumberField'; import { ChoiceField } from './Fields/ChoiceField'; import useContentType from '../../hooks/useContentType'; +import { DateHelper } from '../../helpers/DateHelper'; export interface IMetadataProps { settings: PanelSettings | undefined; @@ -39,11 +39,9 @@ export const Metadata: React.FunctionComponent = ({settings, met }); }; - const getDate = (date: string | Date) => { - if (typeof date === 'string') { - return parseJSON(date); - } - return date; + const getDate = (date: string | Date): Date | null => { + const parsedDate = DateHelper.tryParse(date, settings?.date?.format); + return parsedDate || date as Date | null; } if (!settings) {