#101 - Date picker added to metadata

This commit is contained in:
Elio Struyf
2021-09-15 13:19:03 +02:00
parent 57623378fb
commit 0efdb019a0
14 changed files with 268 additions and 37 deletions
+18
View File
@@ -506,4 +506,22 @@ input:checked + .field__toggle__slider:before {
.sponsor a > span {
margin-right: .25rem;
}
/* Timepicker */
.react-datepicker button:hover {
background-color: none !important;
}
.react-datepicker__triangle {
transform: translate3d(15px, 0px, 0px) !important;
}
.react-datepicker-time__input {
background: transparent !important;
color: #000 !important;
}
.react-datepicker-time__input input {
border: 1px solid #aeaeae !important;
}
+69
View File
@@ -262,6 +262,12 @@
"fastq": "^1.6.0"
}
},
"@popperjs/core": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz",
"integrity": "sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==",
"dev": true
},
"@tailwindcss/forms": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.3.tgz",
@@ -375,6 +381,18 @@
"csstype": "^3.0.2"
}
},
"@types/react-datepicker": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.1.7.tgz",
"integrity": "sha512-8FZt62redGDsO/Dptb8/kdu/JZsAD17lsU3E8OwJqxhtNk4EsdVT0v2ArP8efDGkmtonIKVF2usoPzF6ZeL8zw==",
"dev": true,
"requires": {
"@popperjs/core": "^2.9.2",
"@types/react": "*",
"date-fns": "^2.0.1",
"react-popper": "^2.2.5"
}
},
"@types/react-dom": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz",
@@ -1360,6 +1378,12 @@
}
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
"dev": true
},
"clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -4808,6 +4832,20 @@
"object-assign": "^4.1.1"
}
},
"react-datepicker": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.2.1.tgz",
"integrity": "sha512-0gcvHMnX8rS1fV90PjjsB7MQdsWNU77JeVHf6bbwK9HnFxgwjVflTx40ebKmHV+leqe+f+FgUP9Nvqbe5RGyfA==",
"dev": true,
"requires": {
"@popperjs/core": "^2.9.2",
"classnames": "^2.2.6",
"date-fns": "^2.0.1",
"prop-types": "^15.7.2",
"react-onclickoutside": "^6.10.0",
"react-popper": "^2.2.5"
}
},
"react-dom": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz",
@@ -4830,12 +4868,34 @@
"prop-types": "^15.7.2"
}
},
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==",
"dev": true
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"react-onclickoutside": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.0.tgz",
"integrity": "sha512-oPlOTYcISLHfpMog2lUZMFSbqOs4LFcA4+vo7fpfevB5v9Z0D5VBDBkfeO5lv+hpEcGoaGk67braLT+QT+eICA==",
"dev": true
},
"react-popper": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz",
"integrity": "sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==",
"dev": true,
"requires": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@@ -6161,6 +6221,15 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
},
"watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
+2
View File
@@ -407,6 +407,7 @@
"@types/mocha": "^5.2.6",
"@types/node": "10.17.48",
"@types/react": "17.0.0",
"@types/react-datepicker": "^4.1.7",
"@types/react-dom": "17.0.0",
"@types/vscode": "1.51.0",
"@vscode/codicons": "0.0.20",
@@ -424,6 +425,7 @@
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"react": "17.0.1",
"react-datepicker": "4.2.1",
"react-dom": "17.0.1",
"react-dropzone": "^11.3.4",
"recoil": "^0.4.1",
+24 -20
View File
@@ -7,6 +7,7 @@ import { ArticleHelper, SettingsHelper, SlugHelper } from '../helpers';
import matter = require('gray-matter');
import { Notifications } from '../helpers/Notifications';
import { extname, basename } from 'path';
import { DefaultFields } from '../constants';
export class Article {
@@ -102,12 +103,11 @@ export class Article {
*/
public static updateDate(article: matter.GrayMatterFile<string>, forceCreate: boolean = false) {
const config = SettingsHelper.getConfig();
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
const modField = config.get(SETTING_MODIFIED_FIELD) as string || "date";
const dateField = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
const modField = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.PublishingDate;
article = this.articleDate(article, dateFormat, dateField, forceCreate);
article = this.articleDate(article, dateFormat, modField, false);
article = this.articleDate(article, dateField, forceCreate);
article = this.articleDate(article, modField, false);
return article;
}
@@ -128,19 +128,13 @@ export class Article {
}
const cloneArticle = Object.assign({}, article);
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_MODIFIED_FIELD) as string || "lastmod";
const dateField = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified;
try {
if (dateFormat && typeof dateFormat === "string") {
cloneArticle.data[dateField] = format(new Date(), dateFormat);
} else {
cloneArticle.data[dateField] = new Date().toISOString();
}
cloneArticle.data[dateField] = Article.formatDate(new Date());
ArticleHelper.update(editor, cloneArticle);
} catch (e) {
} catch (e: any) {
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
console.log(e.message);
}
}
@@ -250,6 +244,20 @@ export class Article {
}
}
/**
* Format the date to the defined format
*/
public static formatDate(dateValue: Date) {
const config = SettingsHelper.getConfig();
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
if (dateFormat && typeof dateFormat === "string") {
return format(dateValue, dateFormat);
} else {
return dateValue.toISOString();
}
}
/**
* Get the current article
*/
@@ -274,13 +282,9 @@ export class Article {
* @param field
* @param forceCreate
*/
private static articleDate(article: matter.GrayMatterFile<string>, dateFormat: string, field: string, forceCreate: boolean) {
private static articleDate(article: matter.GrayMatterFile<string>, field: string, forceCreate: boolean) {
if (typeof article.data[field] !== "undefined" || forceCreate) {
if (dateFormat && typeof dateFormat === "string") {
article.data[field] = format(new Date(), dateFormat);
} else {
article.data[field] = new Date().toISOString();
}
article.data[field] = Article.formatDate(new Date());
}
return article;
}
+3 -2
View File
@@ -20,6 +20,7 @@ import { ViewType } from '../pagesView/state';
import { WebviewHelper } from '@estruyf/vscode';
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
import { decodeBase64Image } from '../helpers/decodeBase64Image';
import { DefaultFields } from '../constants';
export class Dashboard {
@@ -280,8 +281,8 @@ export class Dashboard {
const config = SettingsHelper.getConfig();
const wsFolder = Folders.getWorkspaceFolder();
const descriptionField = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description";
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
const descriptionField = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
const dateField = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
const staticFolder = config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
const folderInfo = await Folders.getInfo();
+2 -1
View File
@@ -2,6 +2,7 @@ import { SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_
import * as vscode from 'vscode';
import { ArticleHelper, SeoHelper, SettingsHelper } from '../helpers';
import { ExplorerView } from '../webview/ExplorerView';
import { DefaultFields } from '../constants';
export class StatusListener {
@@ -39,7 +40,7 @@ export class StatusListener {
const config = SettingsHelper.getConfig();
const titleLength = config.get(SETTING_SEO_TITLE_LENGTH) as number || -1;
const descLength = config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1;
const fieldName = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description";
const fieldName = config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
if (article.data.title && titleLength > -1) {
SeoHelper.checkLength(editor, collection, article, "title", titleLength);
+6
View File
@@ -0,0 +1,6 @@
export const DefaultFields = {
PublishingDate: `date`,
LastModified: `lastmod`,
Description: `description`
};
+6
View File
@@ -1 +1,7 @@
export * from './DefaultFields';
export * from './Extension';
export * from './Links';
export * from './charMap';
export * from './context';
export * from './settings';
export * from './stopwords-en';
+2 -2
View File
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as matter from "gray-matter";
import * as fs from "fs";
import { CONFIG_KEY, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES } from '../constants';
import { CONFIG_KEY, DefaultFields, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES } from '../constants';
import { DumpOptions } from 'js-yaml';
import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine';
import { SettingsHelper } from '.';
@@ -117,7 +117,7 @@ export class ArticleHelper {
const config = SettingsHelper.getConfig();
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_DATE_FIELD) as string || "date";
const dateField = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
if (typeof article.data[dateField] !== "undefined") {
if (dateFormat && typeof dateFormat === "string") {
+7
View File
@@ -4,6 +4,7 @@ export interface PanelSettings {
seo: SEO;
slug: Slug;
tags: string[];
date: DateInfo;
categories: string[];
freeform: boolean;
scripts: CustomScript[];
@@ -14,6 +15,12 @@ export interface PanelSettings {
preview: PreviewSettings;
}
export interface DateInfo {
format: string;
pubDate: string;
modDate: string;
}
export interface SEO {
title: number;
description: number;
@@ -0,0 +1,56 @@
import * as React from 'react';
import { VsLabel } from '../VscodeComponents';
import { ClockIcon } from '@heroicons/react/outline';
import DatePicker from 'react-datepicker';
import { forwardRef } from 'react';
export interface IDateTimeProps {
label: string;
date: Date | null;
format?: string;
onChange: (date: Date) => void;
}
type InputProps = JSX.IntrinsicElements["input"];
const CustomInput = forwardRef<HTMLInputElement, InputProps>(({ value, onClick }, ref) => {
return (
<button className="example-custom-input" onClick={onClick as any} ref={ref as any}>
{value || "Pick your date"}
</button>
)
});
export const DateTime: React.FunctionComponent<IDateTimeProps> = ({label, date, format, onChange}: React.PropsWithChildren<IDateTimeProps>) => {
const [ dateValue, setDateValue ] = React.useState<Date | null>(date);
const onDateChange = (date: Date) => {
setDateValue(date);
onChange(date);
};
React.useEffect(() => {
if (dateValue?.toISOString() !== date?.toISOString()) {
setDateValue(date);
}
}, [ date ]);
return (
<div className={`metadata_field`}>
<VsLabel>
<div className={`metadata_field__label`}>
<ClockIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
</div>
</VsLabel>
<DatePicker
selected={dateValue as Date}
onChange={onDateChange}
timeInputLabel="Time:"
dateFormat={format || "MM/dd/yyyy HH:mm"}
customInput={(<CustomInput />)}
showTimeInput
/>
</div>
);
};
+51 -7
View File
@@ -10,40 +10,84 @@ import { RocketIcon } from './Icons/RocketIcon';
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
import { TagIcon } from './Icons/TagIcon';
import { TagPicker } from './TagPicker';
import { VsCheckbox, VsLabel } from './VscodeComponents';
import { VsLabel } from './VscodeComponents';
import { useState } from 'react';
import "react-datepicker/dist/react-datepicker.css";
import { parseJSON } from 'date-fns';
import { DateTime } from './Fields/DateTime';
export interface IMetadataProps {
settings: PanelSettings | undefined;
metadata: { [prop: string]: string[] | null };
metadata: { [prop: string]: string[] | string | null };
focusElm: TagType | null;
unsetFocus: () => void;
}
export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata, focusElm, unsetFocus}: React.PropsWithChildren<IMetadataProps>) => {
const sendUpdate = (field: string, value: any) => {
const sendUpdate = (field: string | undefined, value: any) => {
if (!field) {
return;
}
MessageHelper.sendMessage(CommandToCode.updateMetadata, {
field,
value
});
};
const getDate = (date: string | Date) => {
if (typeof date === 'string') {
return parseJSON(date);
}
return date;
}
let publishing: Date | null = null;
let modifying: Date | null = null;
if (settings?.date) {
const { modDate, pubDate } = settings.date;
publishing = metadata[pubDate] ? getDate(metadata[pubDate] as string) : null;
modifying = metadata[modDate] ? getDate(metadata[modDate] as string) : null;
}
return (
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
<DateTime
label={`Article date`}
date={publishing}
format={settings?.date?.format}
onChange={(date => sendUpdate(settings?.date?.pubDate, date))} />
{
modifying && (
<DateTime
label={`Modified date`}
date={modifying}
format={settings?.date?.format}
onChange={(date => sendUpdate(settings?.date?.modDate, date))} />
)
}
<div className={`metadata_field`}>
<VsLabel>
<div className={`metadata_field__label`}>
<RocketIcon /> <span style={{ lineHeight: "16px"}}>Published</span>
</div>
</VsLabel>
<Toggle checked={!metadata.draft as any} onChanged={(checked) => sendUpdate("draft", !checked)} />
<Toggle
checked={!metadata.draft as any}
onChanged={(checked) => sendUpdate("draft", !checked)} />
</div>
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
crntSelected={metadata.keywords || []}
crntSelected={metadata.keywords as string[] || []}
options={[]}
freeform={true}
focussed={focusElm === TagType.keywords}
@@ -54,7 +98,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
(settings && settings.tags && settings.tags.length > 0) && (
<TagPicker type={TagType.tags}
icon={<TagIcon />}
crntSelected={metadata.tags || []}
crntSelected={metadata.tags as string[] || []}
options={settings.tags}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
@@ -65,7 +109,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
(settings && settings.categories && settings.categories.length > 0) && (
<TagPicker type={TagType.categories}
icon={<ListUnorderedIcon />}
crntSelected={metadata.categories || []}
crntSelected={metadata.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
+18 -5
View File
@@ -1,9 +1,9 @@
import { Template } from './../commands/Template';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST } from './../constants/settings';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD } from './../constants/settings';
import * as os from 'os';
import { PanelSettings, CustomScript } from './../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
import { SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { DefaultFields, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { ArticleHelper, SettingsHelper } from "../helpers";
import { Command } from "../viewpanel/Command";
import { CommandToCode } from '../viewpanel/CommandToCode';
@@ -242,6 +242,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* Update the metadata of the article
*/
private updateMetadata({field, value}: { field: string, value: string }) {
const config = SettingsHelper.getConfig();
const pubDate = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
const modDate = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified;
if (!field) {
return;
}
@@ -256,8 +260,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
return;
}
article.data[field] = value;
ArticleHelper.update(editor, article);
if ((field === pubDate || field === modDate) && value) {
article.data[field] = Article.formatDate(new Date(value));
} else {
article.data[field] = value;
}
ArticleHelper.update(editor, article);
}
/**
@@ -315,13 +323,18 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
title: config.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
description: config.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
content: config.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
descriptionField: config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || "description"
descriptionField: config.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
},
slug: {
prefix: config.get(SETTING_SLUG_PREFIX) || "",
suffix: config.get(SETTING_SLUG_SUFFIX) || "",
updateFileName: !!config.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
},
date: {
format: config.get(SETTING_DATE_FORMAT),
pubDate: config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate,
modDate: config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified
},
tags: config.get(SETTING_TAXONOMY_TAGS) || [],
categories: config.get(SETTING_TAXONOMY_CATEGORIES) || [],
freeform: config.get(SETTING_PANEL_FREEFORM),
+4
View File
@@ -56,6 +56,10 @@ const config = [
use: [{
loader: 'ts-loader'
}]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},