From 13d30beba017c34b8cf31424137f4273c40e3d00 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Sat, 5 Nov 2022 00:22:38 -0400 Subject: [PATCH] Feature/improved types (#68) * v1.0.0-alpha34 --- core/dev-test/index.html | 4 - core/package.json | 10 +- core/src/components/Collection/Sidebar.tsx | 88 +-- .../EditorPreviewContent.tsx | 2 - .../EditorPreviewPane/EditorPreviewPane.tsx | 15 +- core/src/formats/TomlFormatter.ts | 36 - core/src/formats/formats.ts | 10 +- core/src/formats/frontmatter.ts | 17 - core/src/index.ts | 2 +- core/src/interface.ts | 33 +- core/src/lib/registry.ts | 12 +- core/src/widgets/datetime/DateTimeControl.tsx | 53 +- core/yarn.lock | 705 +++++++++++++++++- 13 files changed, 791 insertions(+), 196 deletions(-) delete mode 100644 core/src/formats/TomlFormatter.ts diff --git a/core/dev-test/index.html b/core/dev-test/index.html index f6b3c46a..925414e3 100644 --- a/core/dev-test/index.html +++ b/core/dev-test/index.html @@ -15,10 +15,6 @@ content: '{\n"title": "This is a JSON front matter post",\n"image": "/nf-logo.png",\n"date": "2015-02-15T00:00:00.000Z"\n}\n\n# I Am a Title in Markdown\n\nHello, world\n\n* One Thing\n* Another Thing\n* A Third Thing\n', }, - '2015-02-16-this-is-a-toml-frontmatter-post.md': { - content: - '+++\ntitle = "This is a TOML front matter post"\nimage = "/nf-logo.png"\n"date" = "2015-02-16T00:00:00.000Z"\n+++\n\n# I Am a Title in Markdown\n\nHello, world\n\n* One Thing\n* Another Thing\n* A Third Thing\n', - }, '2015-02-14-this-is-a-post-with-a-different-extension.other': { content: '---\ntitle: This post should not appear because the extension is different\nimage: /nf-logo.png\ndate: 2015-02-14T00:00:00.000Z\n---\n\n# I Am a Title in Markdown\n\nHello, world\n\n* One Thing\n* Another Thing\n* A Third Thing\n', diff --git a/core/package.json b/core/package.json index cc264200..f1d8ba46 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@staticcms/core", - "version": "1.0.0-alpha33", + "version": "1.0.0-alpha34", "license": "MIT", "description": "Static CMS core application.", "repository": "https://github.com/StaticJsCMS/static-cms", @@ -27,12 +27,10 @@ "type-check": "tsc --watch" }, "main": "dist/static-cms-core.js", + "types": "dist/index.d.ts", "files": [ - "src/", - "dist/", - "index.d.ts" + "dist/**/*" ], - "types": "index.d.ts", "browserslist": [ "last 2 Chrome versions", "last 2 ChromeAndroid versions", @@ -47,7 +45,6 @@ "@emotion/css": "11.10.0", "@emotion/react": "11.10.4", "@emotion/styled": "11.10.4", - "@iarna/toml": "2.2.5", "@mui/icons-material": "5.10.6", "@mui/material": "5.10.10", "@mui/system": "5.4.1", @@ -111,7 +108,6 @@ "semaphore": "1.1.0", "stream-browserify": "3.0.0", "symbol-observable": "4.0.0", - "tomlify-j0.4": "3.0.0", "ts-loader": "9.4.1", "uploadcare-widget": "3.19.0", "uploadcare-widget-tab-effects": "1.5.0", diff --git a/core/src/components/Collection/Sidebar.tsx b/core/src/components/Collection/Sidebar.tsx index 478bce7d..97189e0a 100644 --- a/core/src/components/Collection/Sidebar.tsx +++ b/core/src/components/Collection/Sidebar.tsx @@ -96,54 +96,56 @@ const Sidebar = ({ const additionalLinks = useMemo(() => getAdditionalLinks(), []); const links = useMemo( () => - Object.values(additionalLinks).map(({ id, title, data, options: { iconName } = {} }) => { - let icon: ReactNode = ; - if (iconName) { - const StoredIcon = getIcon(iconName); - if (StoredIcon) { - icon = ; + Object.values(additionalLinks).map( + ({ id, title, data, options: { icon: iconName } = {} }) => { + let icon: ReactNode = ; + if (iconName) { + const StoredIcon = getIcon(iconName); + if (StoredIcon) { + icon = ; + } } - } - const content = ( - <> - {icon} - - - ); + const content = ( + <> + {icon} + + + ); - return typeof data === 'string' ? ( - - {content} - - ) : ( - - {content} - - ); - }), + }} + > + {content} + + ) : ( + + {content} + + ); + }, + ), [additionalLinks], ); diff --git a/core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.tsx b/core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.tsx index ad8e2b46..76379495 100644 --- a/core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.tsx +++ b/core/src/components/Editor/EditorPreviewPane/EditorPreviewContent.tsx @@ -13,8 +13,6 @@ const EditorPreviewContent = ({ previewComponent, previewProps }: EditorPreviewC let children: ReactNode; if (!previewComponent) { children = null; - } else if (React.isValidElement(previewComponent)) { - children = React.cloneElement(previewComponent, previewProps); } else { children = React.createElement(previewComponent, previewProps); } diff --git a/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx b/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx index cbde8fcc..e0f8ca4e 100644 --- a/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx +++ b/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx @@ -429,13 +429,14 @@ const PreviewPane = (props: TranslatedProps) => { const element = useMemo(() => document.getElementById('cms-root'), []); - const previewProps: Omit = useMemo( - () => ({ - ...props, - getAsset: handleGetAsset, - widgetFor, - widgetsFor, - }), + const previewProps = useMemo( + () => + ({ + ...props, + getAsset: handleGetAsset, + widgetFor, + widgetsFor, + } as Omit), [handleGetAsset, props, widgetFor, widgetsFor], ); diff --git a/core/src/formats/TomlFormatter.ts b/core/src/formats/TomlFormatter.ts deleted file mode 100644 index 6d05bfb9..00000000 --- a/core/src/formats/TomlFormatter.ts +++ /dev/null @@ -1,36 +0,0 @@ -import toml from '@iarna/toml'; -import moment from 'moment'; -import tomlify from 'tomlify-j0.4'; - -import AssetProxy from '../valueObjects/AssetProxy'; -import { sortKeys } from './helpers'; -import { FileFormatter } from './FileFormatter'; - -function outputReplacer(_key: string, value: unknown) { - if (moment.isMoment(value)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return value.format(value._f); - } - if (value instanceof AssetProxy) { - return `${value.path}`; - } - if (typeof value === 'number' && Number.isInteger(value)) { - // Return the string representation of integers so tomlify won't render with tenths (".0") - return value.toString(); - } - // Return `false` to use default (`undefined` would delete key). - return false; -} - -class TomlFormatter extends FileFormatter { - fromFile(content: string) { - return toml.parse(content); - } - - toFile(data: object, sortedKeys: string[] = []): string { - return tomlify.toToml(data as object, { replace: outputReplacer, sort: sortKeys(sortedKeys) }); - } -} - -export default new TomlFormatter(); diff --git a/core/src/formats/formats.ts b/core/src/formats/formats.ts index d64acc05..44ce6153 100644 --- a/core/src/formats/formats.ts +++ b/core/src/formats/formats.ts @@ -1,29 +1,25 @@ import YamlFormatter from './YamlFormatter'; -import TomlFormatter from './TomlFormatter'; import JsonFormatter from './JsonFormatter'; -import { FrontmatterInfer, frontmatterJSON, frontmatterTOML, frontmatterYAML } from './frontmatter'; +import { FrontmatterInfer, frontmatterJSON, frontmatterYAML } from './frontmatter'; import type { Delimiter } from './frontmatter'; import type { Collection, Entry, Format } from '../interface'; import type { FileFormatter } from './FileFormatter'; -export const frontmatterFormats = ['yaml-frontmatter', 'toml-frontmatter', 'json-frontmatter']; +export const frontmatterFormats = ['yaml-frontmatter', 'json-frontmatter']; export const formatExtensions = { yml: 'yml', yaml: 'yml', - toml: 'toml', json: 'json', frontmatter: 'md', 'json-frontmatter': 'md', - 'toml-frontmatter': 'md', 'yaml-frontmatter': 'md', }; export const extensionFormatters: Record = { yml: YamlFormatter, yaml: YamlFormatter, - toml: TomlFormatter, json: JsonFormatter, md: FrontmatterInfer, markdown: FrontmatterInfer, @@ -34,11 +30,9 @@ function formatByName(name: Format, customDelimiter?: Delimiter): FileFormatter const fileFormatter: Record = { yml: YamlFormatter, yaml: YamlFormatter, - toml: TomlFormatter, json: JsonFormatter, frontmatter: FrontmatterInfer, 'json-frontmatter': frontmatterJSON(customDelimiter), - 'toml-frontmatter': frontmatterTOML(customDelimiter), 'yaml-frontmatter': frontmatterYAML(customDelimiter), }; diff --git a/core/src/formats/frontmatter.ts b/core/src/formats/frontmatter.ts index 71c7f3ae..525510fa 100644 --- a/core/src/formats/frontmatter.ts +++ b/core/src/formats/frontmatter.ts @@ -1,13 +1,11 @@ import matter from 'gray-matter'; -import TomlFormatter from './TomlFormatter'; import YamlFormatter from './YamlFormatter'; import JsonFormatter from './JsonFormatter'; import { FileFormatter } from './FileFormatter'; const Languages = { YAML: 'yaml', - TOML: 'toml', JSON: 'json', } as const; @@ -17,13 +15,6 @@ export type Delimiter = string | [string, string]; type Format = { language: Language; delimiters: Delimiter }; const parsers = { - toml: { - parse: (input: string) => TomlFormatter.fromFile(input), - stringify: (metadata: object, opts?: { sortedKeys?: string[] }) => { - const { sortedKeys } = opts || {}; - return TomlFormatter.toFile(metadata, sortedKeys); - }, - }, json: { parse: (input: string) => { let JSONinput = input.trim(); @@ -58,14 +49,11 @@ function inferFrontmatterFormat(str: string) { const lineEnd = str.indexOf('\n'); const firstLine = str.slice(0, lineEnd !== -1 ? lineEnd : 0).trim(); if (firstLine.length > 3 && firstLine.slice(0, 3) === '---') { - // No need to infer, `gray-matter` will handle things like `---toml` for us. return; } switch (firstLine) { case '---': return getFormatOpts(Languages.YAML); - case '+++': - return getFormatOpts(Languages.TOML); case '{': return getFormatOpts(Languages.JSON); default: @@ -80,7 +68,6 @@ export function getFormatOpts(format?: Language, customDelimiter?: Delimiter) { const formats: { [key in Language]: Format } = { yaml: { language: Languages.YAML, delimiters: '---' }, - toml: { language: Languages.TOML, delimiters: '+++' }, json: { language: Languages.JSON, delimiters: ['{', '}'] }, }; @@ -143,10 +130,6 @@ export function frontmatterYAML(customDelimiter?: Delimiter) { return new FrontmatterFormatter(Languages.YAML, customDelimiter); } -export function frontmatterTOML(customDelimiter?: Delimiter) { - return new FrontmatterFormatter(Languages.TOML, customDelimiter); -} - export function frontmatterJSON(customDelimiter?: Delimiter) { return new FrontmatterFormatter(Languages.JSON, customDelimiter); } diff --git a/core/src/index.ts b/core/src/index.ts index a4f3b05a..0a522c26 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -10,7 +10,7 @@ export * from './media-libraries'; export * from './locales'; export * from './lib'; -export const CMS = { +const CMS = { ...Registry, init: bootstrap, }; diff --git a/core/src/interface.ts b/core/src/interface.ts index 961628c9..4326171e 100644 --- a/core/src/interface.ts +++ b/core/src/interface.ts @@ -263,29 +263,30 @@ export type WidgetPreviewComponent = | React.ReactElement> | ComponentType>; -export interface TemplatePreviewProps { +export type WidgetsFor

= ( + name: K, +) => P[K] extends Array + ? { + data: U | null; + widgets: Record; + }[] + : { + data: P[K] | null; + widgets: Record; + }; + +export interface TemplatePreviewProps { collection: Collection; fields: Field[]; entry: Entry; document: Document | undefined | null; window: Window | undefined | null; getAsset: GetAssetFunction; - widgetFor: (name: string) => ReactNode; - widgetsFor: (name: string) => - | { - data: EntryData | null; - widgets: Record; - } - | { - data: EntryData | null; - widgets: Record; - }[]; + widgetFor: (name: T extends EntryData ? string : keyof T) => ReactNode; + widgetsFor: WidgetsFor; } -export type TemplatePreviewComponent = - // eslint-disable-next-line @typescript-eslint/no-explicit-any - | React.ReactElement> - | ComponentType; +export type TemplatePreviewComponent = ComponentType>; export interface WidgetOptions { validator?: Widget['validator']; @@ -752,7 +753,7 @@ export interface EventListener { } export interface AdditionalLinkOptions { - iconName?: string; + icon?: string; } export interface AdditionalLink { diff --git a/core/src/lib/registry.ts b/core/src/lib/registry.ts index ea0f4fd2..147ac64d 100644 --- a/core/src/lib/registry.ts +++ b/core/src/lib/registry.ts @@ -8,6 +8,7 @@ import type { Config, CustomIcon, Entry, + EntryData, EventData, EventListener, Field, @@ -34,7 +35,7 @@ const eventHandlers = allowedEvents.reduce((acc, e) => { interface Registry { backends: Record; - templates: Record; + templates: Record>; widgets: Record; icons: Record; additionalLinks: Record; @@ -109,11 +110,11 @@ export function getPreviewStyles() { /** * Preview Templates */ -export function registerPreviewTemplate(name: string, component: TemplatePreviewComponent) { - registry.templates[name] = component; +export function registerPreviewTemplate(name: string, component: TemplatePreviewComponent) { + registry.templates[name] = component as TemplatePreviewComponent; } -export function getPreviewTemplate(name: string): TemplatePreviewComponent { +export function getPreviewTemplate(name: string): TemplatePreviewComponent { return registry.templates[name]; } @@ -126,7 +127,7 @@ export function registerWidget(widget: WidgetParam): void; export function registerWidget( name: string, control: string | Widget['control'], - preview: Widget['preview'], + preview?: Widget['preview'], options?: WidgetOptions, ): void; export function registerWidget( @@ -358,7 +359,6 @@ export function getAdditionalLinks(): Record { } export function getAdditionalLink(id: string): AdditionalLink | undefined { - console.log('additionalLinks', registry.additionalLinks); return registry.additionalLinks[id]; } diff --git a/core/src/widgets/datetime/DateTimeControl.tsx b/core/src/widgets/datetime/DateTimeControl.tsx index db90c353..6235984d 100644 --- a/core/src/widgets/datetime/DateTimeControl.tsx +++ b/core/src/widgets/datetime/DateTimeControl.tsx @@ -85,14 +85,33 @@ const DateTimeControl = ({ [timezoneOffset], ); + const inputFormat = useMemo(() => { + if (typeof dateFormat === 'string' || typeof timeFormat === 'string') { + const formatParts: string[] = []; + if (typeof dateFormat === 'string' && isNotEmpty(dateFormat)) { + formatParts.push(dateFormat); + } + + if (typeof timeFormat === 'string' && isNotEmpty(timeFormat)) { + formatParts.push(timeFormat); + } + + if (formatParts.length > 0) { + return formatParts.join(' '); + } + } + + return "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + }, [dateFormat, timeFormat]); + const defaultValue = useMemo(() => { const today = field.picker_utc ? localToUTC(new Date()) : new Date(); return field.default === undefined ? format ? formatDate(today, format) - : formatISO(today) + : formatDate(today, inputFormat) : field.default; - }, [field.default, field.picker_utc, format, localToUTC]); + }, [field.default, field.picker_utc, format, inputFormat, localToUTC]); const [internalValue, setInternalValue] = useState(value ?? defaultValue); @@ -132,14 +151,12 @@ const DateTimeControl = ({ const dateTimePicker = useMemo(() => { if (dateFormat && !timeFormat) { - const inputDateFormat = typeof dateFormat === 'string' ? dateFormat : 'MMM d, yyyy'; - return ( ( ( 0) { - inputFormat = formatParts.join(' '); - } - } - return (