parent
6e9e5c53ef
commit
13d30beba0
@ -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',
|
||||
|
@ -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",
|
||||
|
@ -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 = <ArticleIcon />;
|
||||
if (iconName) {
|
||||
const StoredIcon = getIcon(iconName);
|
||||
if (StoredIcon) {
|
||||
icon = <StoredIcon />;
|
||||
Object.values(additionalLinks).map(
|
||||
({ id, title, data, options: { icon: iconName } = {} }) => {
|
||||
let icon: ReactNode = <ArticleIcon />;
|
||||
if (iconName) {
|
||||
const StoredIcon = getIcon(iconName);
|
||||
if (StoredIcon) {
|
||||
icon = <StoredIcon />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<StyledListItemIcon>{icon}</StyledListItemIcon>
|
||||
<ListItemText primary={title} />
|
||||
</>
|
||||
);
|
||||
const content = (
|
||||
<>
|
||||
<StyledListItemIcon>{icon}</StyledListItemIcon>
|
||||
<ListItemText primary={title} />
|
||||
</>
|
||||
);
|
||||
|
||||
return typeof data === 'string' ? (
|
||||
<ListItem
|
||||
key={title}
|
||||
href={data}
|
||||
component="a"
|
||||
disablePadding
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
sx={{
|
||||
color: colors.inactive,
|
||||
'&:hover': {
|
||||
color: colors.active,
|
||||
'.MuiListItemIcon-root': {
|
||||
return typeof data === 'string' ? (
|
||||
<ListItem
|
||||
key={title}
|
||||
href={data}
|
||||
component="a"
|
||||
disablePadding
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
sx={{
|
||||
color: colors.inactive,
|
||||
'&:hover': {
|
||||
color: colors.active,
|
||||
'.MuiListItemIcon-root': {
|
||||
color: colors.active,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemButton>{content}</ListItemButton>
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem
|
||||
key={title}
|
||||
to={`/page/${id}`}
|
||||
component={NavLink}
|
||||
disablePadding
|
||||
activeClassName="sidebar-active"
|
||||
>
|
||||
<ListItemButton>{content}</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<ListItemButton>{content}</ListItemButton>
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem
|
||||
key={title}
|
||||
to={`/page/${id}`}
|
||||
component={NavLink}
|
||||
disablePadding
|
||||
activeClassName="sidebar-active"
|
||||
>
|
||||
<ListItemButton>{content}</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
},
|
||||
),
|
||||
[additionalLinks],
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -429,13 +429,14 @@ const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
||||
|
||||
const element = useMemo(() => document.getElementById('cms-root'), []);
|
||||
|
||||
const previewProps: Omit<TemplatePreviewProps, 'document' | 'window'> = useMemo(
|
||||
() => ({
|
||||
...props,
|
||||
getAsset: handleGetAsset,
|
||||
widgetFor,
|
||||
widgetsFor,
|
||||
}),
|
||||
const previewProps = useMemo(
|
||||
() =>
|
||||
({
|
||||
...props,
|
||||
getAsset: handleGetAsset,
|
||||
widgetFor,
|
||||
widgetsFor,
|
||||
} as Omit<TemplatePreviewProps, 'document' | 'window'>),
|
||||
[handleGetAsset, props, widgetFor, widgetsFor],
|
||||
);
|
||||
|
||||
|
@ -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();
|
@ -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<string, FileFormatter> = {
|
||||
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<string, FileFormatter> = {
|
||||
yml: YamlFormatter,
|
||||
yaml: YamlFormatter,
|
||||
toml: TomlFormatter,
|
||||
json: JsonFormatter,
|
||||
frontmatter: FrontmatterInfer,
|
||||
'json-frontmatter': frontmatterJSON(customDelimiter),
|
||||
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
||||
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export * from './media-libraries';
|
||||
export * from './locales';
|
||||
export * from './lib';
|
||||
|
||||
export const CMS = {
|
||||
const CMS = {
|
||||
...Registry,
|
||||
init: bootstrap,
|
||||
};
|
||||
|
@ -263,29 +263,30 @@ export type WidgetPreviewComponent<T = unknown, F extends Field = Field> =
|
||||
| React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
|
||||
| ComponentType<WidgetPreviewProps<T, F>>;
|
||||
|
||||
export interface TemplatePreviewProps<T = unknown> {
|
||||
export type WidgetsFor<P = EntryData> = <K extends keyof P>(
|
||||
name: K,
|
||||
) => P[K] extends Array<infer U>
|
||||
? {
|
||||
data: U | null;
|
||||
widgets: Record<keyof U, React.ReactNode>;
|
||||
}[]
|
||||
: {
|
||||
data: P[K] | null;
|
||||
widgets: Record<keyof P[K], React.ReactNode>;
|
||||
};
|
||||
|
||||
export interface TemplatePreviewProps<T = EntryData> {
|
||||
collection: Collection;
|
||||
fields: Field[];
|
||||
entry: Entry<T>;
|
||||
document: Document | undefined | null;
|
||||
window: Window | undefined | null;
|
||||
getAsset: GetAssetFunction;
|
||||
widgetFor: (name: string) => ReactNode;
|
||||
widgetsFor: (name: string) =>
|
||||
| {
|
||||
data: EntryData | null;
|
||||
widgets: Record<string, React.ReactNode>;
|
||||
}
|
||||
| {
|
||||
data: EntryData | null;
|
||||
widgets: Record<string, React.ReactNode>;
|
||||
}[];
|
||||
widgetFor: (name: T extends EntryData ? string : keyof T) => ReactNode;
|
||||
widgetsFor: WidgetsFor<T>;
|
||||
}
|
||||
|
||||
export type TemplatePreviewComponent =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
|
||||
| ComponentType<TemplatePreviewProps>;
|
||||
export type TemplatePreviewComponent<T = EntryData> = ComponentType<TemplatePreviewProps<T>>;
|
||||
|
||||
export interface WidgetOptions<T = unknown, F extends Field = Field> {
|
||||
validator?: Widget<T, F>['validator'];
|
||||
@ -752,7 +753,7 @@ export interface EventListener {
|
||||
}
|
||||
|
||||
export interface AdditionalLinkOptions {
|
||||
iconName?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface AdditionalLink {
|
||||
|
@ -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<string, BackendInitializer>;
|
||||
templates: Record<string, TemplatePreviewComponent>;
|
||||
templates: Record<string, TemplatePreviewComponent<EntryData>>;
|
||||
widgets: Record<string, Widget>;
|
||||
icons: Record<string, CustomIcon>;
|
||||
additionalLinks: Record<string, AdditionalLink>;
|
||||
@ -109,11 +110,11 @@ export function getPreviewStyles() {
|
||||
/**
|
||||
* Preview Templates
|
||||
*/
|
||||
export function registerPreviewTemplate(name: string, component: TemplatePreviewComponent) {
|
||||
registry.templates[name] = component;
|
||||
export function registerPreviewTemplate<T>(name: string, component: TemplatePreviewComponent<T>) {
|
||||
registry.templates[name] = component as TemplatePreviewComponent<EntryData>;
|
||||
}
|
||||
|
||||
export function getPreviewTemplate(name: string): TemplatePreviewComponent {
|
||||
export function getPreviewTemplate(name: string): TemplatePreviewComponent<EntryData> {
|
||||
return registry.templates[name];
|
||||
}
|
||||
|
||||
@ -126,7 +127,7 @@ export function registerWidget(widget: WidgetParam): void;
|
||||
export function registerWidget<T = unknown>(
|
||||
name: string,
|
||||
control: string | Widget<T>['control'],
|
||||
preview: Widget<T>['preview'],
|
||||
preview?: Widget<T>['preview'],
|
||||
options?: WidgetOptions,
|
||||
): void;
|
||||
export function registerWidget<T = unknown>(
|
||||
@ -358,7 +359,6 @@ export function getAdditionalLinks(): Record<string, AdditionalLink> {
|
||||
}
|
||||
|
||||
export function getAdditionalLink(id: string): AdditionalLink | undefined {
|
||||
console.log('additionalLinks', registry.additionalLinks);
|
||||
return registry.additionalLinks[id];
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<MobileDatePicker
|
||||
key="mobile-date-picker"
|
||||
inputFormat={inputDateFormat}
|
||||
inputFormat={inputFormat}
|
||||
label={label}
|
||||
value={formatDate(field.picker_utc ? utcDate : dateValue, inputDateFormat)}
|
||||
value={formatDate(field.picker_utc ? utcDate : dateValue, inputFormat)}
|
||||
onChange={handleChange}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
@ -164,14 +181,12 @@ const DateTimeControl = ({
|
||||
}
|
||||
|
||||
if (!dateFormat && timeFormat) {
|
||||
const inputTimeFormat = typeof timeFormat === 'string' ? timeFormat : 'H:mm';
|
||||
|
||||
return (
|
||||
<TimePicker
|
||||
key="time-picker"
|
||||
label={label}
|
||||
inputFormat={inputTimeFormat}
|
||||
value={formatDate(field.picker_utc ? utcDate : dateValue, inputTimeFormat)}
|
||||
inputFormat={inputFormat}
|
||||
value={formatDate(field.picker_utc ? utcDate : dateValue, inputFormat)}
|
||||
onChange={handleChange}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
@ -195,22 +210,6 @@ const DateTimeControl = ({
|
||||
);
|
||||
}
|
||||
|
||||
let inputFormat = 'MMM d, yyyy H:mm';
|
||||
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) {
|
||||
inputFormat = formatParts.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileDateTimePicker
|
||||
key="mobile-date-time-picker"
|
||||
@ -244,6 +243,8 @@ const DateTimeControl = ({
|
||||
field.picker_utc,
|
||||
handleChange,
|
||||
hasErrors,
|
||||
inputFormat,
|
||||
internalValue,
|
||||
isDisabled,
|
||||
label,
|
||||
t,
|
||||
|
705
core/yarn.lock
705
core/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user