feat: custom collection card template (#433)
This commit is contained in:
parent
c6994ea45b
commit
1641630cfd
@ -14,7 +14,8 @@
|
|||||||
"test:ci": "lerna run test:ci",
|
"test:ci": "lerna run test:ci",
|
||||||
"test:integration:ci": "lerna run test:integration:ci",
|
"test:integration:ci": "lerna run test:integration:ci",
|
||||||
"test:integration": "lerna run test:integration",
|
"test:integration": "lerna run test:integration",
|
||||||
"test": "lerna run test"
|
"test": "lerna run test",
|
||||||
|
"type-check": "lerna run type-check --scope @staticcms/core"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
_posts: {
|
_posts: {
|
||||||
'2015-02-14-this-is-a-post.md': {
|
'2015-02-14-this-is-a-post.md': {
|
||||||
content:
|
content:
|
||||||
'---\ntitle: This is a YAML front matter post\nimage: /assets/uploads/moby-dick.jpg\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',
|
'---\ntitle: This is a YAML front matter post\ndraft: true\nimage: /assets/uploads/moby-dick.jpg\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',
|
||||||
},
|
},
|
||||||
'2015-02-15-this-is-a-json-frontmatter-post.md': {
|
'2015-02-15-this-is-a-json-frontmatter-post.md': {
|
||||||
content:
|
content:
|
||||||
'{\n"title": "This is a JSON front matter post",\n"image": "/assets/uploads/moby-dick.jpg",\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',
|
'{\n"title": "This is a JSON front matter post",\n"draft": false,\n"image": "/assets/uploads/moby-dick.jpg",\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': {
|
'2015-02-16-this-is-a-toml-frontmatter-post.md': {
|
||||||
content:
|
content:
|
||||||
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
'2015-02-14-this-is-a-post-with-a-different-extension.other': {
|
'2015-02-14-this-is-a-post-with-a-different-extension.other': {
|
||||||
content:
|
content:
|
||||||
'---\ntitle: This post should not appear because the extension is different\nimage: /assets/uploads/moby-dick.jpg\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',
|
'---\ntitle: This post should not appear because the extension is different\ndraft: false\nimage: /assets/uploads/moby-dick.jpg\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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_faqs: {
|
_faqs: {
|
||||||
|
@ -11,6 +11,59 @@ const PostPreview = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ style: { width: '100%' } },
|
||||||
|
viewStyle === 'grid' ? widgetFor('image') : null,
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ style: { padding: '16px', width: '100%' } },
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: viewStyle === 'grid' ? 'column' : 'row',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h('strong', { style: { fontSize: '24px' } }, entry.data.title),
|
||||||
|
h('span', { style: { fontSize: '16px' } }, entry.data.date),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
padding: '4px 8px',
|
||||||
|
textAlign: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entry.data.draft === true ? 'Draft' : 'Published',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const GeneralPreview = ({ widgetsFor, entry, collection }) => {
|
const GeneralPreview = ({ widgetsFor, entry, collection }) => {
|
||||||
const title = entry.data.site_title;
|
const title = entry.data.site_title;
|
||||||
const posts = entry.data.posts;
|
const posts = entry.data.posts;
|
||||||
@ -80,6 +133,7 @@ const CustomPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CMS.registerPreviewTemplate('posts', PostPreview);
|
CMS.registerPreviewTemplate('posts', PostPreview);
|
||||||
|
CMS.registerPreviewCard('posts', PostPreviewCard);
|
||||||
CMS.registerPreviewTemplate('general', GeneralPreview);
|
CMS.registerPreviewTemplate('general', GeneralPreview);
|
||||||
CMS.registerPreviewTemplate('authors', AuthorsPreview);
|
CMS.registerPreviewTemplate('authors', AuthorsPreview);
|
||||||
// Pass the name of a registered control to reuse with a new widget preview.
|
// Pass the name of a registered control to reuse with a new widget preview.
|
||||||
|
@ -10,8 +10,16 @@ import { Link } from 'react-router-dom';
|
|||||||
import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
|
import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
|
||||||
import { VIEW_STYLE_GRID, VIEW_STYLE_LIST } from '@staticcms/core/constants/collectionViews';
|
import { VIEW_STYLE_GRID, VIEW_STYLE_LIST } from '@staticcms/core/constants/collectionViews';
|
||||||
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
|
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
|
||||||
import { selectEntryCollectionTitle } from '@staticcms/core/lib/util/collection.util';
|
import { getPreviewCard } from '@staticcms/core/lib/registry';
|
||||||
|
import {
|
||||||
|
selectEntryCollectionTitle,
|
||||||
|
selectFields,
|
||||||
|
selectTemplateName,
|
||||||
|
} from '@staticcms/core/lib/util/collection.util';
|
||||||
|
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
|
||||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
||||||
|
import { useAppSelector } from '@staticcms/core/store/hooks';
|
||||||
|
import useWidgetsFor from '../../common/widget/useWidgetsFor';
|
||||||
|
|
||||||
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
|
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
|
||||||
import type { Collection, Entry, Field } from '@staticcms/core/interface';
|
import type { Collection, Entry, Field } from '@staticcms/core/interface';
|
||||||
@ -29,8 +37,45 @@ const EntryCard = ({
|
|||||||
}: NestedCollectionProps) => {
|
}: NestedCollectionProps) => {
|
||||||
const summary = useMemo(() => selectEntryCollectionTitle(collection, entry), [collection, entry]);
|
const summary = useMemo(() => selectEntryCollectionTitle(collection, entry), [collection, entry]);
|
||||||
|
|
||||||
|
const fields = selectFields(collection, entry.slug);
|
||||||
const imageUrl = useMediaAsset(image, collection, imageField, entry);
|
const imageUrl = useMediaAsset(image, collection, imageField, entry);
|
||||||
|
|
||||||
|
const config = useAppSelector(selectConfig);
|
||||||
|
|
||||||
|
const { widgetFor, widgetsFor } = useWidgetsFor(config, collection, fields, entry);
|
||||||
|
|
||||||
|
const PreviewCardComponent = useMemo(
|
||||||
|
() => getPreviewCard(selectTemplateName(collection, entry.slug)) ?? null,
|
||||||
|
[collection, entry.slug],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (PreviewCardComponent) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardActionArea
|
||||||
|
component={Link}
|
||||||
|
to={path}
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PreviewCardComponent
|
||||||
|
collection={collection}
|
||||||
|
fields={fields}
|
||||||
|
entry={entry}
|
||||||
|
viewStyle={viewStyle === VIEW_STYLE_LIST ? 'list' : 'grid'}
|
||||||
|
widgetFor={widgetFor}
|
||||||
|
widgetsFor={widgetsFor}
|
||||||
|
/>
|
||||||
|
</CardActionArea>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardActionArea component={Link} to={path}>
|
<CardActionArea component={Link} to={path}>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { TemplatePreviewProps } from '@staticcms/core/interface';
|
import type { ObjectValue, TemplatePreviewProps } from '@staticcms/core/interface';
|
||||||
|
|
||||||
const PreviewContainer = styled('div')`
|
const PreviewContainer = styled('div')`
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -10,7 +10,7 @@ const PreviewContainer = styled('div')`
|
|||||||
font-family: Roboto, 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
font-family: Roboto, 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Preview = ({ collection, fields, widgetFor }: TemplatePreviewProps) => {
|
const Preview = ({ collection, fields, widgetFor }: TemplatePreviewProps<ObjectValue>) => {
|
||||||
if (!collection || !fields) {
|
if (!collection || !fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import React, { Fragment, isValidElement, useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Frame from 'react-frame-component';
|
import Frame from 'react-frame-component';
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
@ -9,34 +9,23 @@ import { ScrollSyncPane } from 'react-scroll-sync';
|
|||||||
import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
|
import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
|
||||||
import { ErrorBoundary } from '@staticcms/core/components/UI';
|
import { ErrorBoundary } from '@staticcms/core/components/UI';
|
||||||
import { lengths } from '@staticcms/core/components/UI/styles';
|
import { lengths } from '@staticcms/core/components/UI/styles';
|
||||||
import { getPreviewStyles, getPreviewTemplate, resolveWidget } from '@staticcms/core/lib/registry';
|
import { getPreviewStyles, getPreviewTemplate } from '@staticcms/core/lib/registry';
|
||||||
import { selectTemplateName, useInferredFields } from '@staticcms/core/lib/util/collection.util';
|
import { selectTemplateName } from '@staticcms/core/lib/util/collection.util';
|
||||||
import { selectField } from '@staticcms/core/lib/util/field.util';
|
|
||||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
||||||
import { getTypedFieldForValue } from '@staticcms/list/typedListHelpers';
|
import useWidgetsFor from '../../common/widget/useWidgetsFor';
|
||||||
import EditorPreview from './EditorPreview';
|
import EditorPreview from './EditorPreview';
|
||||||
import EditorPreviewContent from './EditorPreviewContent';
|
import EditorPreviewContent from './EditorPreviewContent';
|
||||||
import PreviewFrameContent from './PreviewFrameContent';
|
import PreviewFrameContent from './PreviewFrameContent';
|
||||||
import PreviewHOC from './PreviewHOC';
|
|
||||||
|
|
||||||
import type { InferredField } from '@staticcms/core/constants/fieldInference';
|
|
||||||
import type {
|
import type {
|
||||||
Collection,
|
Collection,
|
||||||
Config,
|
|
||||||
Entry,
|
Entry,
|
||||||
EntryData,
|
|
||||||
Field,
|
Field,
|
||||||
GetAssetFunction,
|
|
||||||
ListField,
|
|
||||||
ObjectValue,
|
|
||||||
RenderedField,
|
|
||||||
TemplatePreviewProps,
|
TemplatePreviewProps,
|
||||||
TranslatedProps,
|
TranslatedProps,
|
||||||
ValueOrNestedValue,
|
|
||||||
WidgetPreviewComponent,
|
|
||||||
} from '@staticcms/core/interface';
|
} from '@staticcms/core/interface';
|
||||||
import type { RootState } from '@staticcms/core/store';
|
import type { RootState } from '@staticcms/core/store';
|
||||||
import type { ComponentType, ReactFragment, ReactNode } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import type { ConnectedProps } from 'react-redux';
|
import type { ConnectedProps } from 'react-redux';
|
||||||
|
|
||||||
const PreviewPaneFrame = styled(Frame)`
|
const PreviewPaneFrame = styled(Frame)`
|
||||||
@ -81,265 +70,10 @@ const StyledPreviewContent = styled('div')`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the widget component for a named field, and makes recursive calls
|
|
||||||
* to retrieve components for nested and deeply nested fields, which occur in
|
|
||||||
* object and list type fields. Used internally to retrieve widgets, and also
|
|
||||||
* exposed for use in custom preview templates.
|
|
||||||
*/
|
|
||||||
function getWidgetFor(
|
|
||||||
config: Config,
|
|
||||||
collection: Collection,
|
|
||||||
name: string,
|
|
||||||
fields: Field[],
|
|
||||||
entry: Entry,
|
|
||||||
inferredFields: Record<string, InferredField>,
|
|
||||||
getAsset: GetAssetFunction,
|
|
||||||
widgetFields: Field[] = fields,
|
|
||||||
values: EntryData = entry.data,
|
|
||||||
idx: number | null = null,
|
|
||||||
): ReactNode {
|
|
||||||
// We retrieve the field by name so that this function can also be used in
|
|
||||||
// custom preview templates, where the field object can't be passed in.
|
|
||||||
const field = widgetFields && widgetFields.find(f => f.name === name);
|
|
||||||
if (!field) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = values?.[field.name];
|
|
||||||
let fieldWithWidgets = field as RenderedField;
|
|
||||||
|
|
||||||
if ('fields' in field && field.fields) {
|
|
||||||
fieldWithWidgets = {
|
|
||||||
...fieldWithWidgets,
|
|
||||||
renderedFields: getNestedWidgets(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
field.fields,
|
|
||||||
value as EntryData | EntryData[],
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if ('types' in field && field.types) {
|
|
||||||
fieldWithWidgets = {
|
|
||||||
...fieldWithWidgets,
|
|
||||||
renderedFields: getTypedNestedWidgets(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
field,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
value as EntryData[],
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelledWidgets = ['string', 'text', 'number'];
|
|
||||||
const inferredField = Object.entries(inferredFields)
|
|
||||||
.filter(([key]) => {
|
|
||||||
const fieldToMatch = selectField(collection, key);
|
|
||||||
return fieldToMatch === fieldWithWidgets;
|
|
||||||
})
|
|
||||||
.map(([, value]) => value)[0];
|
|
||||||
|
|
||||||
let renderedValue: ValueOrNestedValue | ReactNode = value;
|
|
||||||
if (inferredField) {
|
|
||||||
renderedValue = inferredField.defaultPreview(String(value));
|
|
||||||
} else if (
|
|
||||||
value &&
|
|
||||||
fieldWithWidgets.widget &&
|
|
||||||
labelledWidgets.indexOf(fieldWithWidgets.widget) !== -1 &&
|
|
||||||
value.toString().length < 50
|
|
||||||
) {
|
|
||||||
renderedValue = (
|
|
||||||
<div key={field.name}>
|
|
||||||
<>
|
|
||||||
<strong>{field.label ?? field.name}:</strong> {value}
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return renderedValue
|
|
||||||
? getWidget(config, fieldWithWidgets, collection, renderedValue, entry, getAsset, idx)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
function isJsxElement(value: any): value is JSX.Element {
|
|
||||||
return isValidElement(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
function isReactFragment(value: any): value is ReactFragment {
|
|
||||||
if (value.type) {
|
|
||||||
return value.type === Fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value === Fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWidget(
|
|
||||||
config: Config,
|
|
||||||
field: RenderedField<Field>,
|
|
||||||
collection: Collection,
|
|
||||||
value: ValueOrNestedValue | ReactNode,
|
|
||||||
entry: Entry,
|
|
||||||
getAsset: GetAssetFunction,
|
|
||||||
idx: number | null = null,
|
|
||||||
) {
|
|
||||||
if (!field.widget) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const widget = resolveWidget(field.widget);
|
|
||||||
const key = idx ? field.name + '_' + idx : field.name;
|
|
||||||
|
|
||||||
if (field.widget === 'hidden' || !widget.preview) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use an HOC to provide conditional updates for all previews.
|
|
||||||
*/
|
|
||||||
return !widget.preview ? null : (
|
|
||||||
<PreviewHOC
|
|
||||||
previewComponent={widget.preview as WidgetPreviewComponent}
|
|
||||||
key={key}
|
|
||||||
field={field as RenderedField}
|
|
||||||
getAsset={getAsset}
|
|
||||||
config={config}
|
|
||||||
collection={collection}
|
|
||||||
value={
|
|
||||||
value &&
|
|
||||||
typeof value === 'object' &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
field.name in value &&
|
|
||||||
!isJsxElement(value) &&
|
|
||||||
!isReactFragment(value)
|
|
||||||
? (value as Record<string, unknown>)[field.name]
|
|
||||||
: value
|
|
||||||
}
|
|
||||||
entry={entry}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use getWidgetFor as a mapping function for recursive widget retrieval
|
|
||||||
*/
|
|
||||||
function widgetsForNestedFields(
|
|
||||||
config: Config,
|
|
||||||
collection: Collection,
|
|
||||||
fields: Field[],
|
|
||||||
entry: Entry,
|
|
||||||
inferredFields: Record<string, InferredField>,
|
|
||||||
getAsset: GetAssetFunction,
|
|
||||||
widgetFields: Field[],
|
|
||||||
values: EntryData,
|
|
||||||
idx: number | null = null,
|
|
||||||
) {
|
|
||||||
return widgetFields
|
|
||||||
.map(field =>
|
|
||||||
getWidgetFor(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
field.name,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
widgetFields,
|
|
||||||
values,
|
|
||||||
idx,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.filter(widget => Boolean(widget)) as JSX.Element[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves widgets for nested fields (children of object/list fields)
|
|
||||||
*/
|
|
||||||
function getTypedNestedWidgets(
|
|
||||||
config: Config,
|
|
||||||
collection: Collection,
|
|
||||||
field: ListField,
|
|
||||||
entry: Entry,
|
|
||||||
inferredFields: Record<string, InferredField>,
|
|
||||||
getAsset: GetAssetFunction,
|
|
||||||
values: EntryData[],
|
|
||||||
) {
|
|
||||||
return values
|
|
||||||
?.flatMap((value, index) => {
|
|
||||||
const itemType = getTypedFieldForValue(field, value ?? {}, index);
|
|
||||||
if (!itemType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return widgetsForNestedFields(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
itemType.fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
itemType.fields,
|
|
||||||
value,
|
|
||||||
index,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves widgets for nested fields (children of object/list fields)
|
|
||||||
*/
|
|
||||||
function getNestedWidgets(
|
|
||||||
config: Config,
|
|
||||||
collection: Collection,
|
|
||||||
fields: Field[],
|
|
||||||
entry: Entry,
|
|
||||||
inferredFields: Record<string, InferredField>,
|
|
||||||
getAsset: GetAssetFunction,
|
|
||||||
widgetFields: Field[],
|
|
||||||
values: EntryData | EntryData[],
|
|
||||||
) {
|
|
||||||
// Fields nested within a list field will be paired with a List of value Maps.
|
|
||||||
if (Array.isArray(values)) {
|
|
||||||
return values.flatMap(value =>
|
|
||||||
widgetsForNestedFields(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
widgetFields,
|
|
||||||
value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields nested within an object field will be paired with a single Record of values.
|
|
||||||
return widgetsForNestedFields(
|
|
||||||
config,
|
|
||||||
collection,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
getAsset,
|
|
||||||
widgetFields,
|
|
||||||
values,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
||||||
const { entry, collection, config, fields, previewInFrame, getAsset, t } = props;
|
const { entry, collection, config, fields, previewInFrame, getAsset, t } = props;
|
||||||
|
|
||||||
const inferredFields = useInferredFields(collection);
|
const { widgetFor, widgetsFor } = useWidgetsFor(config.config, collection, fields, entry);
|
||||||
|
|
||||||
const handleGetAsset = useCallback(
|
const handleGetAsset = useCallback(
|
||||||
(path: string, field?: Field) => {
|
(path: string, field?: Field) => {
|
||||||
@ -349,118 +83,6 @@ const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
|||||||
[collection],
|
[collection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const widgetFor = useCallback(
|
|
||||||
(name: string) => {
|
|
||||||
if (!config.config) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return getWidgetFor(
|
|
||||||
config.config,
|
|
||||||
collection,
|
|
||||||
name,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
handleGetAsset,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[collection, config, entry, fields, handleGetAsset, inferredFields],
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function exists entirely to expose nested widgets for object and list
|
|
||||||
* fields to custom preview templates.
|
|
||||||
*/
|
|
||||||
const widgetsFor = useCallback(
|
|
||||||
(name: string) => {
|
|
||||||
const cmsConfig = config.config;
|
|
||||||
if (!cmsConfig) {
|
|
||||||
return {
|
|
||||||
data: null,
|
|
||||||
widgets: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = fields.find(f => f.name === name);
|
|
||||||
if (!field || !('fields' in field)) {
|
|
||||||
return {
|
|
||||||
data: null,
|
|
||||||
widgets: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = entry.data?.[field.name];
|
|
||||||
const nestedFields = field && 'fields' in field ? field.fields ?? [] : [];
|
|
||||||
|
|
||||||
if (field.widget === 'list' || Array.isArray(value)) {
|
|
||||||
let finalValue: ObjectValue[];
|
|
||||||
if (!value || typeof value !== 'object') {
|
|
||||||
finalValue = [];
|
|
||||||
} else if (!Array.isArray(value)) {
|
|
||||||
finalValue = [value];
|
|
||||||
} else {
|
|
||||||
finalValue = value as ObjectValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalValue
|
|
||||||
.filter((val: unknown) => typeof val === 'object')
|
|
||||||
.map((val: ObjectValue) => {
|
|
||||||
const widgets = nestedFields.reduce((acc, field, index) => {
|
|
||||||
acc[field.name] = (
|
|
||||||
<div key={index}>
|
|
||||||
{getWidgetFor(
|
|
||||||
cmsConfig,
|
|
||||||
collection,
|
|
||||||
field.name,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
handleGetAsset,
|
|
||||||
nestedFields,
|
|
||||||
val,
|
|
||||||
index,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, ReactNode>);
|
|
||||||
return { data: val, widgets };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value !== 'object') {
|
|
||||||
return {
|
|
||||||
data: {},
|
|
||||||
widgets: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: value,
|
|
||||||
widgets: nestedFields.reduce((acc, field, index) => {
|
|
||||||
acc[field.name] = (
|
|
||||||
<div key={index}>
|
|
||||||
{getWidgetFor(
|
|
||||||
cmsConfig,
|
|
||||||
collection,
|
|
||||||
field.name,
|
|
||||||
fields,
|
|
||||||
entry,
|
|
||||||
inferredFields,
|
|
||||||
handleGetAsset,
|
|
||||||
nestedFields,
|
|
||||||
value,
|
|
||||||
index,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, ReactNode>),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[collection, config.config, entry, fields, handleGetAsset, inferredFields],
|
|
||||||
);
|
|
||||||
|
|
||||||
const previewStyles = useMemo(
|
const previewStyles = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...getPreviewStyles().map((style, i) => {
|
...getPreviewStyles().map((style, i) => {
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
const StyledWidgetPreviewContainer = styled('div')`
|
|
||||||
margin: 15px 2px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface WidgetPreviewContainerProps {
|
interface WidgetPreviewContainerProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetPreviewContainer = ({ children }: WidgetPreviewContainerProps) => {
|
const WidgetPreviewContainer = ({ children }: WidgetPreviewContainerProps) => {
|
||||||
return <StyledWidgetPreviewContainer>{children}</StyledWidgetPreviewContainer>;
|
return <div>{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WidgetPreviewContainer;
|
export default WidgetPreviewContainer;
|
||||||
|
147
packages/core/src/components/common/widget/useWidgetsFor.tsx
Normal file
147
packages/core/src/components/common/widget/useWidgetsFor.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { getAsset } from '@staticcms/core/actions/media';
|
||||||
|
import { useInferredFields } from '@staticcms/core/lib/util/collection.util';
|
||||||
|
import { useAppDispatch } from '@staticcms/core/store/hooks';
|
||||||
|
import getWidgetFor from './widgetFor';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Collection,
|
||||||
|
Config,
|
||||||
|
Entry,
|
||||||
|
EntryData,
|
||||||
|
Field,
|
||||||
|
ObjectValue,
|
||||||
|
WidgetFor,
|
||||||
|
WidgetsFor,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function useWidgetsFor(
|
||||||
|
config: Config | undefined,
|
||||||
|
collection: Collection,
|
||||||
|
fields: Field[],
|
||||||
|
entry: Entry,
|
||||||
|
): {
|
||||||
|
widgetFor: WidgetFor<EntryData>;
|
||||||
|
widgetsFor: WidgetsFor<EntryData>;
|
||||||
|
} {
|
||||||
|
const inferredFields = useInferredFields(collection);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleGetAsset = useCallback(
|
||||||
|
(path: string, field?: Field) => {
|
||||||
|
return dispatch(getAsset(collection, entry, path, field));
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[collection],
|
||||||
|
);
|
||||||
|
|
||||||
|
const widgetFor = useCallback(
|
||||||
|
(name: string): ReturnType<WidgetFor<EntryData>> => {
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getWidgetFor(config, collection, name, fields, entry, inferredFields, handleGetAsset);
|
||||||
|
},
|
||||||
|
[collection, config, entry, fields, handleGetAsset, inferredFields],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function exists entirely to expose nested widgets for object and list
|
||||||
|
* fields to custom preview templates.
|
||||||
|
*/
|
||||||
|
const widgetsFor = useCallback(
|
||||||
|
(name: string): ReturnType<WidgetsFor<EntryData>> => {
|
||||||
|
if (!config) {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
widgets: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = fields.find(f => f.name === name);
|
||||||
|
if (!field || !('fields' in field)) {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
widgets: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = entry.data?.[field.name];
|
||||||
|
const nestedFields = field && 'fields' in field ? field.fields ?? [] : [];
|
||||||
|
|
||||||
|
if (field.widget === 'list' || Array.isArray(value)) {
|
||||||
|
let finalValue: ObjectValue[];
|
||||||
|
if (!value || typeof value !== 'object') {
|
||||||
|
finalValue = [];
|
||||||
|
} else if (!Array.isArray(value)) {
|
||||||
|
finalValue = [value];
|
||||||
|
} else {
|
||||||
|
finalValue = value as ObjectValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalValue
|
||||||
|
.filter((val: unknown) => typeof val === 'object')
|
||||||
|
.map((val: ObjectValue) => {
|
||||||
|
const widgets = nestedFields.reduce((acc, field, index) => {
|
||||||
|
acc[field.name] = (
|
||||||
|
<div key={index}>
|
||||||
|
{getWidgetFor(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
field.name,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
handleGetAsset,
|
||||||
|
nestedFields,
|
||||||
|
val,
|
||||||
|
index,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, ReactNode>);
|
||||||
|
return { data: val, widgets };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'object') {
|
||||||
|
return {
|
||||||
|
data: {},
|
||||||
|
widgets: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: value,
|
||||||
|
widgets: nestedFields.reduce((acc, field, index) => {
|
||||||
|
acc[field.name] = (
|
||||||
|
<div key={index}>
|
||||||
|
{getWidgetFor(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
field.name,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
handleGetAsset,
|
||||||
|
nestedFields,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, ReactNode>),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[collection, config, entry, fields, handleGetAsset, inferredFields],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
widgetFor,
|
||||||
|
widgetsFor,
|
||||||
|
};
|
||||||
|
}
|
277
packages/core/src/components/common/widget/widgetFor.tsx
Normal file
277
packages/core/src/components/common/widget/widgetFor.tsx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import React, { Fragment, isValidElement } from 'react';
|
||||||
|
|
||||||
|
import { resolveWidget } from '@staticcms/core/lib/registry';
|
||||||
|
import { selectField } from '@staticcms/core/lib/util/field.util';
|
||||||
|
import { getTypedFieldForValue } from '@staticcms/list/typedListHelpers';
|
||||||
|
import PreviewHOC from './PreviewHOC';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Collection,
|
||||||
|
Config,
|
||||||
|
Entry,
|
||||||
|
EntryData,
|
||||||
|
Field,
|
||||||
|
GetAssetFunction,
|
||||||
|
InferredField,
|
||||||
|
ListField,
|
||||||
|
RenderedField,
|
||||||
|
ValueOrNestedValue,
|
||||||
|
WidgetPreviewComponent,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
|
import type { ReactFragment, ReactNode } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the widget component for a named field, and makes recursive calls
|
||||||
|
* to retrieve components for nested and deeply nested fields, which occur in
|
||||||
|
* object and list type fields. Used internally to retrieve widgets, and also
|
||||||
|
* exposed for use in custom preview templates.
|
||||||
|
*/
|
||||||
|
export default function getWidgetFor(
|
||||||
|
config: Config,
|
||||||
|
collection: Collection,
|
||||||
|
name: string,
|
||||||
|
fields: Field[],
|
||||||
|
entry: Entry,
|
||||||
|
inferredFields: Record<string, InferredField>,
|
||||||
|
getAsset: GetAssetFunction,
|
||||||
|
widgetFields: Field[] = fields,
|
||||||
|
values: EntryData = entry.data,
|
||||||
|
idx: number | null = null,
|
||||||
|
): ReactNode {
|
||||||
|
// We retrieve the field by name so that this function can also be used in
|
||||||
|
// custom preview templates, where the field object can't be passed in.
|
||||||
|
const field = widgetFields && widgetFields.find(f => f.name === name);
|
||||||
|
if (!field) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = values?.[field.name];
|
||||||
|
let fieldWithWidgets = field as RenderedField;
|
||||||
|
|
||||||
|
if ('fields' in field && field.fields) {
|
||||||
|
fieldWithWidgets = {
|
||||||
|
...fieldWithWidgets,
|
||||||
|
renderedFields: getNestedWidgets(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
field.fields,
|
||||||
|
value as EntryData | EntryData[],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if ('types' in field && field.types) {
|
||||||
|
fieldWithWidgets = {
|
||||||
|
...fieldWithWidgets,
|
||||||
|
renderedFields: getTypedNestedWidgets(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
field,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
value as EntryData[],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelledWidgets = ['string', 'text', 'number'];
|
||||||
|
const inferredField = Object.entries(inferredFields)
|
||||||
|
.filter(([key]) => {
|
||||||
|
const fieldToMatch = selectField(collection, key);
|
||||||
|
return fieldToMatch === fieldWithWidgets;
|
||||||
|
})
|
||||||
|
.map(([, value]) => value)[0];
|
||||||
|
|
||||||
|
let renderedValue: ValueOrNestedValue | ReactNode = value;
|
||||||
|
if (inferredField) {
|
||||||
|
renderedValue = inferredField.defaultPreview(String(value));
|
||||||
|
} else if (
|
||||||
|
value &&
|
||||||
|
fieldWithWidgets.widget &&
|
||||||
|
labelledWidgets.indexOf(fieldWithWidgets.widget) !== -1 &&
|
||||||
|
value.toString().length < 50
|
||||||
|
) {
|
||||||
|
renderedValue = (
|
||||||
|
<div key={field.name}>
|
||||||
|
<>
|
||||||
|
<strong>{field.label ?? field.name}:</strong> {value}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedValue
|
||||||
|
? getWidget(config, fieldWithWidgets, collection, renderedValue, entry, getAsset, idx)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves widgets for nested fields (children of object/list fields)
|
||||||
|
*/
|
||||||
|
function getNestedWidgets(
|
||||||
|
config: Config,
|
||||||
|
collection: Collection,
|
||||||
|
fields: Field[],
|
||||||
|
entry: Entry,
|
||||||
|
inferredFields: Record<string, InferredField>,
|
||||||
|
getAsset: GetAssetFunction,
|
||||||
|
widgetFields: Field[],
|
||||||
|
values: EntryData | EntryData[],
|
||||||
|
) {
|
||||||
|
// Fields nested within a list field will be paired with a List of value Maps.
|
||||||
|
if (Array.isArray(values)) {
|
||||||
|
return values.flatMap(value =>
|
||||||
|
widgetsForNestedFields(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
widgetFields,
|
||||||
|
value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields nested within an object field will be paired with a single Record of values.
|
||||||
|
return widgetsForNestedFields(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
widgetFields,
|
||||||
|
values,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves widgets for nested fields (children of object/list fields)
|
||||||
|
*/
|
||||||
|
function getTypedNestedWidgets(
|
||||||
|
config: Config,
|
||||||
|
collection: Collection,
|
||||||
|
field: ListField,
|
||||||
|
entry: Entry,
|
||||||
|
inferredFields: Record<string, InferredField>,
|
||||||
|
getAsset: GetAssetFunction,
|
||||||
|
values: EntryData[],
|
||||||
|
) {
|
||||||
|
return values
|
||||||
|
?.flatMap((value, index) => {
|
||||||
|
const itemType = getTypedFieldForValue(field, value ?? {}, index);
|
||||||
|
if (!itemType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgetsForNestedFields(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
itemType.fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
itemType.fields,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use getWidgetFor as a mapping function for recursive widget retrieval
|
||||||
|
*/
|
||||||
|
function widgetsForNestedFields(
|
||||||
|
config: Config,
|
||||||
|
collection: Collection,
|
||||||
|
fields: Field[],
|
||||||
|
entry: Entry,
|
||||||
|
inferredFields: Record<string, InferredField>,
|
||||||
|
getAsset: GetAssetFunction,
|
||||||
|
widgetFields: Field[],
|
||||||
|
values: EntryData,
|
||||||
|
idx: number | null = null,
|
||||||
|
) {
|
||||||
|
return widgetFields
|
||||||
|
.map(field =>
|
||||||
|
getWidgetFor(
|
||||||
|
config,
|
||||||
|
collection,
|
||||||
|
field.name,
|
||||||
|
fields,
|
||||||
|
entry,
|
||||||
|
inferredFields,
|
||||||
|
getAsset,
|
||||||
|
widgetFields,
|
||||||
|
values,
|
||||||
|
idx,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.filter(widget => Boolean(widget)) as JSX.Element[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWidget(
|
||||||
|
config: Config,
|
||||||
|
field: RenderedField<Field>,
|
||||||
|
collection: Collection,
|
||||||
|
value: ValueOrNestedValue | ReactNode,
|
||||||
|
entry: Entry,
|
||||||
|
getAsset: GetAssetFunction,
|
||||||
|
idx: number | null = null,
|
||||||
|
) {
|
||||||
|
if (!field.widget) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = resolveWidget(field.widget);
|
||||||
|
const key = idx ? field.name + '_' + idx : field.name;
|
||||||
|
|
||||||
|
if (field.widget === 'hidden' || !widget.preview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use an HOC to provide conditional updates for all previews.
|
||||||
|
*/
|
||||||
|
return !widget.preview ? null : (
|
||||||
|
<PreviewHOC
|
||||||
|
previewComponent={widget.preview as WidgetPreviewComponent}
|
||||||
|
key={key}
|
||||||
|
field={field as RenderedField}
|
||||||
|
getAsset={getAsset}
|
||||||
|
config={config}
|
||||||
|
collection={collection}
|
||||||
|
value={
|
||||||
|
value &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
field.name in value &&
|
||||||
|
!isJsxElement(value) &&
|
||||||
|
!isReactFragment(value)
|
||||||
|
? (value as Record<string, unknown>)[field.name]
|
||||||
|
: value
|
||||||
|
}
|
||||||
|
entry={entry}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function isJsxElement(value: any): value is JSX.Element {
|
||||||
|
return isValidElement(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function isReactFragment(value: any): value is ReactFragment {
|
||||||
|
if (value.type) {
|
||||||
|
return value.type === Fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value === Fragment;
|
||||||
|
}
|
@ -1,20 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { InferredField } from '../interface';
|
||||||
|
|
||||||
export const IDENTIFIER_FIELDS = ['title', 'path'] as const;
|
export const IDENTIFIER_FIELDS = ['title', 'path'] as const;
|
||||||
|
|
||||||
export const SORTABLE_FIELDS = ['title', 'date', 'author', 'description'] as const;
|
export const SORTABLE_FIELDS = ['title', 'date', 'author', 'description'] as const;
|
||||||
|
|
||||||
export interface InferredField {
|
|
||||||
type: string;
|
|
||||||
secondaryTypes: string[];
|
|
||||||
synonyms: string[];
|
|
||||||
defaultPreview: (value: string | boolean | number) => JSX.Element | ReactNode;
|
|
||||||
fallbackToFirstField: boolean;
|
|
||||||
showError: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const INFERABLE_FIELDS: Record<string, InferredField> = {
|
export const INFERABLE_FIELDS: Record<string, InferredField> = {
|
||||||
title: {
|
title: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
495
packages/core/src/formats/util/j-toml.d.ts
vendored
495
packages/core/src/formats/util/j-toml.d.ts
vendored
@ -1,6 +1,493 @@
|
|||||||
interface StringifyOptions {
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
newline?: string;
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
|
export as namespace TOML;
|
||||||
|
export = exports;
|
||||||
|
|
||||||
|
declare namespace exports {
|
||||||
|
export const version: '1.38.0';
|
||||||
|
|
||||||
|
export const parse: {
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
source: Source,
|
||||||
|
specificationVersion: 1.0 | 0.5 | 0.4 | 0.3 | 0.2 | 0.1,
|
||||||
|
multilineStringJoiner?: string,
|
||||||
|
useBigInt?: boolean | number,
|
||||||
|
xOptions?: XOptions,
|
||||||
|
): Table;
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
source: Source,
|
||||||
|
multilineStringJoiner?: string,
|
||||||
|
useBigInt?: boolean | number,
|
||||||
|
xOptions?: XOptions,
|
||||||
|
): Table;
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
source: Source,
|
||||||
|
options?: {
|
||||||
|
readonly joiner?: string;
|
||||||
|
readonly bigint?: boolean | number;
|
||||||
|
readonly x?: XOptions;
|
||||||
|
},
|
||||||
|
): Table;
|
||||||
|
} & {
|
||||||
|
readonly [SpecificationVersion in 1.0 | 0.5 | 0.4 | 0.3 | 0.2 | 0.1]: {
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
source: Source,
|
||||||
|
multilineStringJoiner?: string,
|
||||||
|
useBigInt?: boolean | number,
|
||||||
|
xOptions?: XOptions,
|
||||||
|
): Table;
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
source: Source,
|
||||||
|
options?: {
|
||||||
|
readonly joiner?: string;
|
||||||
|
readonly bigint?: boolean | number;
|
||||||
|
readonly x?: XOptions;
|
||||||
|
},
|
||||||
|
): Table;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function stringify(
|
||||||
|
this: void,
|
||||||
|
rootTable: ReadonlyTable,
|
||||||
|
options?: {
|
||||||
|
readonly integer?: number;
|
||||||
|
readonly newline?: '\n' | '\r\n';
|
||||||
|
readonly newlineAround?: 'document' | 'section' | 'header' | 'pairs' | 'pair';
|
||||||
|
readonly indent?: string | number;
|
||||||
|
readonly T?: 'T' | 't' | ' ';
|
||||||
|
readonly Z?: 'Z' | 'z';
|
||||||
|
readonly xNull?: boolean;
|
||||||
|
readonly xBeforeNewlineInMultilineTable?: ',' | '';
|
||||||
|
readonly forceInlineArraySpacing?: 0 | 1 | 2 | 3;
|
||||||
|
},
|
||||||
|
): string;
|
||||||
|
|
||||||
|
export function isSection(this: void, table: ReadonlyTable): boolean;
|
||||||
|
export function isInline(this: void, value: ReadonlyTable | ReadonlyArray): boolean;
|
||||||
|
|
||||||
|
export function Section<T extends ReadonlyTable>(this: void, table: T): T;
|
||||||
|
export function inline<T extends ReadonlyArray>(
|
||||||
|
this: void,
|
||||||
|
value: T,
|
||||||
|
inlineMode?: 0 | 1 | 2 | 3,
|
||||||
|
): T;
|
||||||
|
export function inline<T extends ReadonlyTable>(this: void, value: T): T;
|
||||||
|
export const multiline: {
|
||||||
|
readonly array: {
|
||||||
|
<T extends ReadonlyArray>(this: void, array: T): T;
|
||||||
|
};
|
||||||
|
<T extends ReadonlyTable>(this: void, table: T): T;
|
||||||
|
(this: void, value: string): {
|
||||||
|
[_literal]: [`"""`, ...string[], `${string}"""`] | [`'''`, ...string[], `${string}'''`];
|
||||||
|
} & object &
|
||||||
|
String;
|
||||||
|
(this: void, lines: readonly string[]): {
|
||||||
|
[_literal]: [`"""`, ...string[], `${string}"""`] | [`'''`, ...string[], `${string}'''`];
|
||||||
|
} & object;
|
||||||
|
(this: void, lines: readonly string[], value: string): {
|
||||||
|
[_literal]: [`"""`, ...string[], `${string}"""`] | [`'''`, ...string[], `${string}'''`];
|
||||||
|
} & object &
|
||||||
|
String;
|
||||||
|
readonly basic: {
|
||||||
|
(this: void, value: string): { [_literal]: [`"""`, ...string[], `${string}"""`] } & object &
|
||||||
|
String;
|
||||||
|
(this: void, lines: readonly string[]): {
|
||||||
|
[_literal]: [`"""`, ...string[], `${string}"""`];
|
||||||
|
} & object;
|
||||||
|
(this: void, lines: readonly string[], value: string): {
|
||||||
|
[_literal]: [`"""`, ...string[], `${string}"""`];
|
||||||
|
} & object &
|
||||||
|
String;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export function basic(this: void, value: string): { [_literal]: `"${string}"` } & object & String;
|
||||||
|
export function literal(
|
||||||
|
this: void,
|
||||||
|
literal: string,
|
||||||
|
): { [_literal]: string | [string, ...string[]] } & object;
|
||||||
|
export function literal(
|
||||||
|
this: void,
|
||||||
|
literal: string,
|
||||||
|
value: string,
|
||||||
|
): { [_literal]: string | [string, ...string[]] } & object & String;
|
||||||
|
export function literal(
|
||||||
|
this: void,
|
||||||
|
literal: string,
|
||||||
|
value: number,
|
||||||
|
): { [_literal]: string | [string, ...string[]] } & object & Number;
|
||||||
|
export function literal(
|
||||||
|
this: void,
|
||||||
|
literal: string,
|
||||||
|
value: bigint,
|
||||||
|
): { [_literal]: string | [string, ...string[]] } & object & BigInt;
|
||||||
|
export function literal(
|
||||||
|
this: void,
|
||||||
|
literal: TemplateStringsArray,
|
||||||
|
...chars: string[]
|
||||||
|
): { [_literal]: string | [string, ...string[]] } & object;
|
||||||
|
|
||||||
|
export function commentFor(this: void, key: string): symbol;
|
||||||
|
export const commentForThis: unique symbol;
|
||||||
|
|
||||||
|
export { OffsetDateTime, LocalDateTime, LocalDate, LocalTime, Keys };
|
||||||
|
|
||||||
|
export { exports as default };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parse = (_content: string) => object;
|
declare class OffsetDateTime {
|
||||||
export const stringify = (_content: object, _options?: StringifyOptions) => string;
|
readonly toJSON: Date['toJSON'];
|
||||||
|
|
||||||
|
readonly [Symbol.toStringTag]: 'OffsetDateTime';
|
||||||
|
|
||||||
|
readonly toISOString: (
|
||||||
|
this: Readonly<OffsetDateTime>,
|
||||||
|
) => `${number}-${number}-${number}T${number}:${number}:${number}${'' | `.${number}`}${
|
||||||
|
| 'Z'
|
||||||
|
| `${'+' | '-'}${number}:${number}`}`;
|
||||||
|
readonly valueOf: (this: Readonly<OffsetDateTime>) => `${number}${'' | `.${number}`}`;
|
||||||
|
|
||||||
|
private [OffsetDateTime_ISOString]: string;
|
||||||
|
private [OffsetDateTime_value]: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
literal: `${number}-${number}-${number}${'T' | 't' | ' '}${number}:${number}:${number}${
|
||||||
|
| ''
|
||||||
|
| `.${number}`}${'Z' | 'z' | `${'+' | '-'}${number}:${number}`}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly getUTCFullYear: (this: Readonly<OffsetDateTime>) => _1_10000;
|
||||||
|
readonly getUTCMonth: (this: Readonly<OffsetDateTime>) => _0_11;
|
||||||
|
readonly getUTCDate: (this: Readonly<OffsetDateTime>) => _1_31;
|
||||||
|
|
||||||
|
readonly getUTCHours: (this: Readonly<OffsetDateTime>) => _0_23;
|
||||||
|
readonly getUTCMinutes: (this: Readonly<OffsetDateTime>) => _0_59;
|
||||||
|
readonly getUTCSeconds: (this: Readonly<OffsetDateTime>) => _0_59;
|
||||||
|
readonly getUTCMilliseconds: (this: Readonly<OffsetDateTime>) => _0_999;
|
||||||
|
|
||||||
|
readonly getUTCDay: (this: Readonly<OffsetDateTime>) => _0_6;
|
||||||
|
readonly getTimezoneOffset: (this: Readonly<OffsetDateTime>) => _1439_1439;
|
||||||
|
readonly getTime: (this: Readonly<OffsetDateTime>) => number;
|
||||||
|
}
|
||||||
|
declare class LocalDateTime {
|
||||||
|
readonly toJSON: Date['toJSON'];
|
||||||
|
|
||||||
|
readonly [Symbol.toStringTag]: 'LocalDateTime';
|
||||||
|
|
||||||
|
readonly toISOString: (
|
||||||
|
this: Readonly<LocalDateTime>,
|
||||||
|
) => `${number}-${number}-${number}T${number}:${number}:${number}${'' | `.${number}`}`;
|
||||||
|
readonly valueOf: (this: Readonly<LocalDateTime>) => `${number}`;
|
||||||
|
|
||||||
|
private [LocalDateTime_ISOString]: string;
|
||||||
|
private [LocalDateTime_value]: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
literal: `${number}-${number}-${number}${'T' | 't' | ' '}${number}:${number}:${number}${
|
||||||
|
| ''
|
||||||
|
| `.${number}`}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly getFullYear: (this: Readonly<LocalDateTime>) => _0_9999;
|
||||||
|
readonly setFullYear: (this: LocalDateTime, year: _0_9999) => void;
|
||||||
|
readonly getMonth: (this: Readonly<LocalDateTime>) => _0_11;
|
||||||
|
readonly setMonth: (this: LocalDateTime, month: _0_11) => void;
|
||||||
|
readonly getDate: (this: Readonly<LocalDateTime>) => _1_31;
|
||||||
|
readonly setDate: (this: LocalDateTime, date: _1_31) => void;
|
||||||
|
|
||||||
|
readonly getHours: (this: Readonly<LocalDateTime>) => _0_23;
|
||||||
|
readonly setHours: (this: LocalDateTime, hours: _0_23) => void;
|
||||||
|
readonly getMinutes: (this: Readonly<LocalDateTime>) => _0_59;
|
||||||
|
readonly setMinutes: (this: LocalDateTime, min: _0_59) => void;
|
||||||
|
readonly getSeconds: (this: Readonly<LocalDateTime>) => _0_59;
|
||||||
|
readonly setSeconds: (this: LocalDateTime, sec: _0_59) => void;
|
||||||
|
readonly getMilliseconds: (this: Readonly<LocalDateTime>) => _0_999;
|
||||||
|
readonly setMilliseconds: (this: LocalDateTime, ms: _0_999) => void;
|
||||||
|
}
|
||||||
|
declare class LocalDate {
|
||||||
|
readonly toJSON: Date['toJSON'];
|
||||||
|
|
||||||
|
readonly [Symbol.toStringTag]: 'LocalDate';
|
||||||
|
|
||||||
|
readonly toISOString: (this: Readonly<LocalDate>) => `${number}-${number}-${number}`;
|
||||||
|
readonly valueOf: (this: Readonly<LocalDate>) => `${number}`;
|
||||||
|
|
||||||
|
private [LocalDate_ISOString]: string;
|
||||||
|
private [LocalDate_value]: string;
|
||||||
|
|
||||||
|
constructor(literal: `${number}-${number}-${number}`);
|
||||||
|
|
||||||
|
readonly getFullYear: (this: Readonly<LocalDate>) => _0_9999;
|
||||||
|
readonly setFullYear: (this: LocalDate, year: _0_9999) => void;
|
||||||
|
readonly getMonth: (this: Readonly<LocalDate>) => _0_11;
|
||||||
|
readonly setMonth: (this: LocalDate, month: _0_11) => void;
|
||||||
|
readonly getDate: (this: Readonly<LocalDate>) => _1_31;
|
||||||
|
readonly setDate: (this: LocalDate, date: _1_31) => void;
|
||||||
|
}
|
||||||
|
declare class LocalTime {
|
||||||
|
readonly toJSON: Date['toJSON'];
|
||||||
|
|
||||||
|
readonly [Symbol.toStringTag]: 'LocalTime';
|
||||||
|
|
||||||
|
readonly toISOString: (
|
||||||
|
this: Readonly<LocalTime>,
|
||||||
|
) => `${number}:${number}:${number}${'' | `.${number}`}`;
|
||||||
|
readonly valueOf: (this: Readonly<LocalTime>) => `${number}`;
|
||||||
|
|
||||||
|
private [LocalTime_ISOString]: string;
|
||||||
|
private [LocalTime_value]: string;
|
||||||
|
|
||||||
|
constructor(literal: `${number}:${number}:${number}${'' | `.${number}`}`);
|
||||||
|
|
||||||
|
readonly getHours: (this: Readonly<LocalTime>) => _0_23;
|
||||||
|
readonly setHours: (this: LocalTime, hours: _0_23) => void;
|
||||||
|
readonly getMinutes: (this: Readonly<LocalTime>) => _0_59;
|
||||||
|
readonly setMinutes: (this: LocalTime, min: _0_59) => void;
|
||||||
|
readonly getSeconds: (this: Readonly<LocalTime>) => _0_59;
|
||||||
|
readonly setSeconds: (this: LocalTime, sec: _0_59) => void;
|
||||||
|
readonly getMilliseconds: (this: Readonly<LocalTime>) => _0_999;
|
||||||
|
readonly setMilliseconds: (this: LocalTime, ms: _0_999) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Keys extends RegExp {
|
||||||
|
readonly lastIndex: number;
|
||||||
|
constructor(keys: ArrayLike<string>);
|
||||||
|
readonly test: (this: Keys, key: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const _literal: unique symbol;
|
||||||
|
|
||||||
|
type Source =
|
||||||
|
| string
|
||||||
|
| ArrayBufferLike
|
||||||
|
| {
|
||||||
|
readonly path: string;
|
||||||
|
readonly data?: undefined;
|
||||||
|
readonly require:
|
||||||
|
| {
|
||||||
|
readonly resolve?: { readonly paths?: undefined };
|
||||||
|
(this: void, id: 'fs'): {
|
||||||
|
readonly readFileSync: (this: void, path: string) => ArrayBufferLike;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly resolve: { readonly paths: (this: void, request: string) => null | string[] };
|
||||||
|
(this: void, id: 'path'): {
|
||||||
|
readonly resolve: (this: void, dirname: string, filename: string) => string;
|
||||||
|
};
|
||||||
|
(this: void, id: 'fs'): {
|
||||||
|
readonly readFileSync: (this: void, path: string) => ArrayBufferLike;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly path: string;
|
||||||
|
readonly data: string | ArrayBufferLike;
|
||||||
|
readonly require?:
|
||||||
|
| {
|
||||||
|
readonly resolve?: { readonly paths?: undefined };
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly resolve: { readonly paths: (this: void, request: string) => null | string[] };
|
||||||
|
(this: void, id: 'path'): {
|
||||||
|
readonly resolve: (this: void, dirname: string, filename: string) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type XOptions = null | {
|
||||||
|
readonly keys?: null | Keys;
|
||||||
|
readonly order?: boolean;
|
||||||
|
readonly exact?: boolean;
|
||||||
|
readonly multi?: boolean;
|
||||||
|
readonly longer?: boolean;
|
||||||
|
readonly string?: boolean;
|
||||||
|
readonly comment?: boolean;
|
||||||
|
readonly literal?: boolean;
|
||||||
|
readonly null?: boolean;
|
||||||
|
readonly tag?:
|
||||||
|
| null
|
||||||
|
| (<
|
||||||
|
Table extends object & { [key: string | symbol]: any },
|
||||||
|
Key extends string | symbol,
|
||||||
|
Array extends any[],
|
||||||
|
Index extends number,
|
||||||
|
Tag extends string,
|
||||||
|
>(
|
||||||
|
this: void,
|
||||||
|
each:
|
||||||
|
| { table: Table; key: Key; tag: Tag }
|
||||||
|
| { array: Array; index: Index; tag: Tag }
|
||||||
|
| { table: Table; key: Key; array: Array; index: Index; tag: Tag },
|
||||||
|
) => void);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadonlyTable = object & { readonly [key: string]: ReadonlyValue };
|
||||||
|
type ReadonlyArray = readonly ReadonlyValue[];
|
||||||
|
type ReadonlyValue =
|
||||||
|
| ({ readonly [_literal]: string | readonly [string, ...string[]] } & object)
|
||||||
|
| null
|
||||||
|
| boolean
|
||||||
|
| bigint
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| ReadonlyDatetime
|
||||||
|
| ReadonlyArray
|
||||||
|
| ReadonlyTable;
|
||||||
|
interface ReadonlyDatetime {
|
||||||
|
readonly toISOString: (this: this) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Table = object & { [key: string]: Value };
|
||||||
|
type Array = Value[];
|
||||||
|
type Value =
|
||||||
|
| (object & BigInt & { [_literal]: string })
|
||||||
|
| (object & Number & { [_literal]: string })
|
||||||
|
| (object & String & { [_literal]: string | [string, ...string[]] })
|
||||||
|
| null
|
||||||
|
| boolean
|
||||||
|
| bigint
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| Datetime
|
||||||
|
| Array
|
||||||
|
| Table;
|
||||||
|
type Datetime = OffsetDateTime | LocalDateTime | LocalDate | LocalTime;
|
||||||
|
declare const OffsetDateTime_ISOString: unique symbol;
|
||||||
|
declare const OffsetDateTime_value: unique symbol;
|
||||||
|
declare const LocalDateTime_ISOString: unique symbol;
|
||||||
|
declare const LocalDateTime_value: unique symbol;
|
||||||
|
declare const LocalDate_ISOString: unique symbol;
|
||||||
|
declare const LocalDate_value: unique symbol;
|
||||||
|
declare const LocalTime_ISOString: unique symbol;
|
||||||
|
declare const LocalTime_value: unique symbol;
|
||||||
|
|
||||||
|
type _1439_1439 = -1439 | ({} & number) | 1439;
|
||||||
|
type _1_10000 = -1 | ({} & number) | 10000;
|
||||||
|
type _0_9999 = 0 | ({} & number) | 9999;
|
||||||
|
type _0_999 = 0 | ({} & number) | 999;
|
||||||
|
type _0_6 = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
|
type _0_11 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
||||||
|
type _0_23 =
|
||||||
|
| 0
|
||||||
|
| 1
|
||||||
|
| 2
|
||||||
|
| 3
|
||||||
|
| 4
|
||||||
|
| 5
|
||||||
|
| 6
|
||||||
|
| 7
|
||||||
|
| 8
|
||||||
|
| 9
|
||||||
|
| 10
|
||||||
|
| 11
|
||||||
|
| 12
|
||||||
|
| 13
|
||||||
|
| 14
|
||||||
|
| 15
|
||||||
|
| 16
|
||||||
|
| 17
|
||||||
|
| 18
|
||||||
|
| 19
|
||||||
|
| 20
|
||||||
|
| 21
|
||||||
|
| 22
|
||||||
|
| 23;
|
||||||
|
type _1_31 =
|
||||||
|
| 1
|
||||||
|
| 2
|
||||||
|
| 3
|
||||||
|
| 4
|
||||||
|
| 5
|
||||||
|
| 6
|
||||||
|
| 7
|
||||||
|
| 8
|
||||||
|
| 9
|
||||||
|
| 10
|
||||||
|
| 11
|
||||||
|
| 12
|
||||||
|
| 13
|
||||||
|
| 14
|
||||||
|
| 15
|
||||||
|
| 16
|
||||||
|
| 17
|
||||||
|
| 18
|
||||||
|
| 19
|
||||||
|
| 20
|
||||||
|
| 21
|
||||||
|
| 22
|
||||||
|
| 23
|
||||||
|
| 24
|
||||||
|
| 25
|
||||||
|
| 26
|
||||||
|
| 27
|
||||||
|
| 28
|
||||||
|
| 29
|
||||||
|
| 30
|
||||||
|
| 31;
|
||||||
|
type _0_59 =
|
||||||
|
| 0
|
||||||
|
| 1
|
||||||
|
| 2
|
||||||
|
| 3
|
||||||
|
| 4
|
||||||
|
| 5
|
||||||
|
| 6
|
||||||
|
| 7
|
||||||
|
| 8
|
||||||
|
| 9
|
||||||
|
| 10
|
||||||
|
| 11
|
||||||
|
| 12
|
||||||
|
| 13
|
||||||
|
| 14
|
||||||
|
| 15
|
||||||
|
| 16
|
||||||
|
| 17
|
||||||
|
| 18
|
||||||
|
| 19
|
||||||
|
| 20
|
||||||
|
| 21
|
||||||
|
| 22
|
||||||
|
| 23
|
||||||
|
| 24
|
||||||
|
| 25
|
||||||
|
| 26
|
||||||
|
| 27
|
||||||
|
| 28
|
||||||
|
| 29
|
||||||
|
| 30
|
||||||
|
| 31
|
||||||
|
| 32
|
||||||
|
| 33
|
||||||
|
| 34
|
||||||
|
| 35
|
||||||
|
| 36
|
||||||
|
| 37
|
||||||
|
| 38
|
||||||
|
| 39
|
||||||
|
| 40
|
||||||
|
| 41
|
||||||
|
| 42
|
||||||
|
| 43
|
||||||
|
| 44
|
||||||
|
| 45
|
||||||
|
| 46
|
||||||
|
| 47
|
||||||
|
| 48
|
||||||
|
| 49
|
||||||
|
| 50
|
||||||
|
| 51
|
||||||
|
| 52
|
||||||
|
| 53
|
||||||
|
| 54
|
||||||
|
| 55
|
||||||
|
| 56
|
||||||
|
| 57
|
||||||
|
| 58
|
||||||
|
| 59;
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
/*!@preserve@license
|
/*!@preserve@license
|
||||||
* 模块名称:j-toml
|
* 模块名称:j-toml
|
||||||
* 模块功能:龙腾道为汤小明语写的实现。从属于“简计划”。
|
* 模块功能:龙腾道为汤小明语写的实现。从属于“简计划”。
|
||||||
An implementation of TOML written by LongTengDao. Belong to "Plan J".
|
An implementation of TOML written by LongTengDao. Belong to "Plan J".
|
||||||
* 模块版本:1.37.0
|
* 模块版本:1.38.0
|
||||||
* 许可条款:LGPL-3.0
|
* 许可条款:LGPL-3.0
|
||||||
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
||||||
* 问题反馈:https://GitHub.com/LongTengDao/j-toml/issues
|
* 问题反馈:https://GitHub.com/LongTengDao/j-toml/issues
|
||||||
* 项目主页:https://GitHub.com/LongTengDao/j-toml/
|
* 项目主页:https://GitHub.com/LongTengDao/j-toml/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const version = '1.37.0';
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||||
|
typeof define === 'function' && define.amd ? define(factory) :
|
||||||
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TOML = factory());
|
||||||
|
})(this, (function () { 'use strict';
|
||||||
|
|
||||||
|
const version = '1.38.0';
|
||||||
|
|
||||||
const SyntaxError$1 = SyntaxError;
|
const SyntaxError$1 = SyntaxError;
|
||||||
|
|
||||||
@ -128,15 +134,15 @@ const Default = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
/*!@preserve@license
|
/*!@preserve@license
|
||||||
* 模块名称:j-regexp
|
* 模块名称:j-regexp
|
||||||
* 模块功能:可读性更好的正则表达式创建方式。从属于“简计划”。
|
* 模块功能:可读性更好的正则表达式创建方式。从属于“简计划”。
|
||||||
More readable way for creating RegExp. Belong to "Plan J".
|
More readable way for creating RegExp. Belong to "Plan J".
|
||||||
* 模块版本:8.2.0
|
* 模块版本:8.2.0
|
||||||
* 许可条款:LGPL-3.0
|
* 许可条款:LGPL-3.0
|
||||||
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
||||||
* 问题反馈:https://GitHub.com/LongTengDao/j-regexp/issues
|
* 问题反馈:https://GitHub.com/LongTengDao/j-regexp/issues
|
||||||
* 项目主页:https://GitHub.com/LongTengDao/j-regexp/
|
* 项目主页:https://GitHub.com/LongTengDao/j-regexp/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Test = bind
|
var Test = bind
|
||||||
? /*#__PURE__*/bind.bind(test )
|
? /*#__PURE__*/bind.bind(test )
|
||||||
@ -401,15 +407,15 @@ const Reflect_deleteProperty = Reflect.deleteProperty;
|
|||||||
const ownKeys = Reflect.ownKeys;
|
const ownKeys = Reflect.ownKeys;
|
||||||
|
|
||||||
/*!@preserve@license
|
/*!@preserve@license
|
||||||
* 模块名称:j-orderify
|
* 模块名称:j-orderify
|
||||||
* 模块功能:返回一个能保证给定对象的属性按此后添加顺序排列的 proxy,即使键名是 symbol,或整数 string。从属于“简计划”。
|
* 模块功能:返回一个能保证给定对象的属性按此后添加顺序排列的 proxy,即使键名是 symbol,或整数 string。从属于“简计划”。
|
||||||
Return a proxy for given object, which can guarantee own keys are in setting order, even if the key name is symbol or int string. Belong to "Plan J".
|
Return a proxy for given object, which can guarantee own keys are in setting order, even if the key name is symbol or int string. Belong to "Plan J".
|
||||||
* 模块版本:7.0.1
|
* 模块版本:7.0.1
|
||||||
* 许可条款:LGPL-3.0
|
* 许可条款:LGPL-3.0
|
||||||
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
* 所属作者:龙腾道 <LongTengDao@LongTengDao.com> (www.LongTengDao.com)
|
||||||
* 问题反馈:https://GitHub.com/LongTengDao/j-orderify/issues
|
* 问题反馈:https://GitHub.com/LongTengDao/j-orderify/issues
|
||||||
* 项目主页:https://GitHub.com/LongTengDao/j-orderify/
|
* 项目主页:https://GitHub.com/LongTengDao/j-orderify/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Keeper = () => [];
|
const Keeper = () => [];
|
||||||
|
|
||||||
@ -423,16 +429,16 @@ const newWeakMap = () => {
|
|||||||
const target2keeper = /*#__PURE__*/newWeakMap()
|
const target2keeper = /*#__PURE__*/newWeakMap()
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
const proxy2target = /*#__PURE__*/newWeakMap()
|
const proxy2target = /*#__PURE__*/newWeakMap()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
const target2proxy = /*#__PURE__*/newWeakMap()
|
const target2proxy = /*#__PURE__*/newWeakMap()
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
const handlers = /*#__PURE__*/assign$1(create$1(NULL), {
|
const handlers = /*#__PURE__*/assign$1(create$1(NULL), {
|
||||||
defineProperty: (target , key , descriptor ) => {
|
defineProperty: (target , key , descriptor ) => {
|
||||||
@ -518,11 +524,11 @@ const ofInline = /*#__PURE__*/get.bind(INLINES)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
const beInline = /*#__PURE__*/set.bind(INLINES)
|
const beInline = /*#__PURE__*/set.bind(INLINES)
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
const inline = (value , mode , looping ) => {
|
const inline = (value , mode , looping ) => {
|
||||||
if ( isArray$1(value) ) {
|
if ( isArray$1(value) ) {
|
||||||
if ( looping ) { mode = 3; }
|
if ( looping ) { mode = 3; }
|
||||||
@ -636,9 +642,9 @@ const next = () => sourceLines[++lineIndex] ;
|
|||||||
const rest = () => lineIndex!==lastLineIndex;
|
const rest = () => lineIndex!==lastLineIndex;
|
||||||
|
|
||||||
class mark {
|
class mark {
|
||||||
lineIndex = lineIndex;
|
lineIndex = lineIndex;
|
||||||
type ;
|
type ;
|
||||||
restColumn ;
|
restColumn ;
|
||||||
constructor (type , restColumn ) {
|
constructor (type , restColumn ) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.restColumn = restColumn;
|
this.restColumn = restColumn;
|
||||||
@ -667,7 +673,7 @@ const done = () => {
|
|||||||
const Whitespace = /[ \t]/;
|
const Whitespace = /[ \t]/;
|
||||||
|
|
||||||
const PRE_WHITESPACE = /*#__PURE__*/newRegExp`
|
const PRE_WHITESPACE = /*#__PURE__*/newRegExp`
|
||||||
^${Whitespace}+`.valueOf();
|
^${Whitespace}+`.valueOf();
|
||||||
|
|
||||||
const { exec: VALUE_REST_exec } = /*#__PURE__*/newRegExp.s `
|
const { exec: VALUE_REST_exec } = /*#__PURE__*/newRegExp.s `
|
||||||
^
|
^
|
||||||
@ -713,8 +719,8 @@ ${Whitespace}*
|
|||||||
=
|
=
|
||||||
${Whitespace}*
|
${Whitespace}*
|
||||||
(?:
|
(?:
|
||||||
<(${Tag})>
|
<(${Tag})>
|
||||||
${Whitespace}*
|
${Whitespace}*
|
||||||
)?
|
)?
|
||||||
(.*)
|
(.*)
|
||||||
$`.valueOf();
|
$`.valueOf();
|
||||||
@ -917,7 +923,7 @@ const Keys = class KeysRegExp extends RegExp$1 {
|
|||||||
this.lastIndex = maxLength+1;
|
this.lastIndex = maxLength+1;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
test ( key ) {
|
test ( key ) {
|
||||||
return key.length<this.lastIndex && super.test(key);
|
return key.length<this.lastIndex && super.test(key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1398,7 +1404,7 @@ const OffsetDateTime = /*#__PURE__*/fpc(class OffsetDateTime extends Datetime {
|
|||||||
|
|
||||||
get [Symbol$1.toStringTag] () { return 'OffsetDateTime' ; }
|
get [Symbol$1.toStringTag] () { return 'OffsetDateTime' ; }
|
||||||
|
|
||||||
valueOf ( ) { return this[OffsetDateTime_value]; }
|
valueOf ( ) { return this[OffsetDateTime_value]; }
|
||||||
toISOString ( ) { return this[OffsetDateTime_ISOString]; }
|
toISOString ( ) { return this[OffsetDateTime_ISOString]; }
|
||||||
|
|
||||||
constructor (literal ) {
|
constructor (literal ) {
|
||||||
@ -1488,7 +1494,7 @@ const LocalDateTime = /*#__PURE__*/fpc(class LocalDateTime extends Datetime {
|
|||||||
|
|
||||||
get [Symbol$1.toStringTag] () { return 'LocalDateTime' ; }
|
get [Symbol$1.toStringTag] () { return 'LocalDateTime' ; }
|
||||||
|
|
||||||
valueOf ( ) { return this[LocalDateTime_value]; }
|
valueOf ( ) { return this[LocalDateTime_value]; }
|
||||||
toISOString ( ) { return this[LocalDateTime_ISOString]; }
|
toISOString ( ) { return this[LocalDateTime_ISOString]; }
|
||||||
|
|
||||||
constructor (literal ) {
|
constructor (literal ) {
|
||||||
@ -1540,7 +1546,7 @@ const LocalDate = /*#__PURE__*/fpc(class LocalDate extends Datetime {
|
|||||||
|
|
||||||
get [Symbol$1.toStringTag] () { return 'LocalDate' ; }
|
get [Symbol$1.toStringTag] () { return 'LocalDate' ; }
|
||||||
|
|
||||||
valueOf ( ) { return this[LocalDate_value]; }
|
valueOf ( ) { return this[LocalDate_value]; }
|
||||||
toISOString ( ) { return this[LocalDate_ISOString]; }
|
toISOString ( ) { return this[LocalDate_ISOString]; }
|
||||||
|
|
||||||
constructor (literal ) {
|
constructor (literal ) {
|
||||||
@ -1579,7 +1585,7 @@ const LocalTime = /*#__PURE__*/fpc(class LocalTime extends Datetime {
|
|||||||
|
|
||||||
get [Symbol$1.toStringTag] () { return 'LocalTime' ; }
|
get [Symbol$1.toStringTag] () { return 'LocalTime' ; }
|
||||||
|
|
||||||
valueOf ( ) { return this[LocalTime_value]; }
|
valueOf ( ) { return this[LocalTime_value]; }
|
||||||
toISOString ( ) { return this[LocalTime_ISOString]; }
|
toISOString ( ) { return this[LocalTime_ISOString]; }
|
||||||
|
|
||||||
constructor (literal ) {
|
constructor (literal ) {
|
||||||
@ -1766,6 +1772,10 @@ const Float = (literal ) => {
|
|||||||
if ( literal==='nan' || literal==='+nan' ) { return NaN$1; }
|
if ( literal==='nan' || literal==='+nan' ) { return NaN$1; }
|
||||||
if ( literal==='-nan' ) { return _NaN; }
|
if ( literal==='-nan' ) { return _NaN; }
|
||||||
}
|
}
|
||||||
|
else if ( !sError ) {
|
||||||
|
if ( literal==='inf' || literal==='+inf' ) { return Infinity; }
|
||||||
|
if ( literal==='-inf' ) { return _Infinity$1; }
|
||||||
|
}
|
||||||
throw throws(SyntaxError$1(`Invalid Float ${literal}` + where(' at ')));
|
throw throws(SyntaxError$1(`Invalid Float ${literal}` + where(' at ')));
|
||||||
}
|
}
|
||||||
const withoutUnderscores = literal.replace(UNDERSCORES, '');
|
const withoutUnderscores = literal.replace(UNDERSCORES, '');
|
||||||
@ -1899,7 +1909,7 @@ const assignLiteralString = ( (table , finalKey , literal )
|
|||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
const assignBasicString = ( (table , finalKey , literal ) => {
|
const assignBasicString = ( (table , finalKey , literal ) => {
|
||||||
if ( !literal.startsWith('"""') ) {
|
if ( !literal.startsWith('"""') ) {
|
||||||
@ -1952,7 +1962,7 @@ const assignBasicString = ( (table , finalKey , literal )
|
|||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
const KEYS = /*#__PURE__*/Null$1 (null);
|
const KEYS = /*#__PURE__*/Null$1 (null);
|
||||||
const commentFor = (key ) => KEYS[key] || ( KEYS[key] = Symbol$1(key) );
|
const commentFor = (key ) => KEYS[key] || ( KEYS[key] = Symbol$1(key) );
|
||||||
@ -2097,7 +2107,7 @@ const equalStaticArray = function * ( table , finalKey ,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( lineRest[0]===']' ) { break; }
|
if ( lineRest[0]===']' ) { break; }
|
||||||
throw throws(SyntaxError$1(`Unexpected character in static array item value` + where(', which is found at ')));
|
throw throws(SyntaxError$1(`Unexpect character in static array item value` + where(', which is found at ')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline===null || beInline(staticArray, inline);
|
inline===null || beInline(staticArray, inline);
|
||||||
@ -2105,7 +2115,7 @@ const equalStaticArray = function * ( table , finalKey ,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
const equalInlineTable = function * ( table , finalKey , lineRest ) {
|
const equalInlineTable = function * ( table , finalKey , lineRest ) {
|
||||||
const inlineTable = table[finalKey] = new Table(DIRECTLY, INLINE);
|
const inlineTable = table[finalKey] = new Table(DIRECTLY, INLINE);
|
||||||
@ -2158,7 +2168,7 @@ const equalInlineTable = function * ( table , finalKey ,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
const ForComment = (lastInlineTable , lineRest ) => {
|
const ForComment = (lastInlineTable , lineRest ) => {
|
||||||
@ -2231,7 +2241,7 @@ const Root = () => {
|
|||||||
const { leadingKeys, finalKey, asArrayItem, tag, lineRest } = TABLE_DEFINITION_exec_groups(line, parseKeys);
|
const { leadingKeys, finalKey, asArrayItem, tag, lineRest } = TABLE_DEFINITION_exec_groups(line, parseKeys);
|
||||||
const table = prepareTable(rootTable, leadingKeys);
|
const table = prepareTable(rootTable, leadingKeys);
|
||||||
if ( lineRest ) {
|
if ( lineRest ) {
|
||||||
lineRest[0]==='#' || throws(SyntaxError$1(`Unexpected charachtor after table header` + where(' at ')));
|
lineRest[0]==='#' || throws(SyntaxError$1(`Unexpect charachtor after table header` + where(' at ')));
|
||||||
}
|
}
|
||||||
lastSectionTable = appendTable(table, finalKey, asArrayItem, tag);
|
lastSectionTable = appendTable(table, finalKey, asArrayItem, tag);
|
||||||
preserveComment && lineRest && ( lastSectionTable[commentForThis] = asArrayItem ? lineRest.slice(1) : table[commentFor(finalKey)] = lineRest.slice(1) );
|
preserveComment && lineRest && ( lastSectionTable[commentForThis] = asArrayItem ? lineRest.slice(1) : table[commentFor(finalKey)] = lineRest.slice(1) );
|
||||||
@ -2244,7 +2254,7 @@ const Root = () => {
|
|||||||
let rest = assign(forComment);
|
let rest = assign(forComment);
|
||||||
typeof rest==='string' || ( rest = x (rest) );
|
typeof rest==='string' || ( rest = x (rest) );
|
||||||
if ( rest ) {
|
if ( rest ) {
|
||||||
rest[0]==='#' || throws(SyntaxError$1(`Unexpected charachtor after key/value pair` + where(' at ')));
|
rest[0]==='#' || throws(SyntaxError$1(`Unexpect charachtor after key/value pair` + where(' at ')));
|
||||||
if ( preserveComment ) { forComment.table[commentFor(forComment.finalKey)] = rest.slice(1); }
|
if ( preserveComment ) { forComment.table[commentFor(forComment.finalKey)] = rest.slice(1); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2455,7 +2465,7 @@ const $Keys = (keys ) => isAmazing(keys) ? keys.replace(FIRST, li
|
|||||||
|
|
||||||
class TOMLSection extends Array$1 {
|
class TOMLSection extends Array$1 {
|
||||||
|
|
||||||
document ;
|
document ;
|
||||||
|
|
||||||
constructor (document ) {
|
constructor (document ) {
|
||||||
super();
|
super();
|
||||||
@ -2706,26 +2716,26 @@ const return_false = () => false;
|
|||||||
|
|
||||||
class TOMLDocument extends Array$1 {
|
class TOMLDocument extends Array$1 {
|
||||||
|
|
||||||
get ['constructor'] () { return Array$1; }
|
get ['constructor'] () { return Array$1; }
|
||||||
|
|
||||||
0 = new TOMLSection(this);
|
0 = new TOMLSection(this);
|
||||||
|
|
||||||
asInteger = return_false;
|
asInteger = return_false;
|
||||||
newline = '';
|
newline = '';
|
||||||
newlineUnderSection = true;
|
newlineUnderSection = true;
|
||||||
newlineUnderSectionButPair = true;
|
newlineUnderSectionButPair = true;
|
||||||
newlineUnderHeader = true;
|
newlineUnderHeader = true;
|
||||||
newlineUnderPair = false;
|
newlineUnderPair = false;
|
||||||
newlineUnderPairButDotted = false;
|
newlineUnderPairButDotted = false;
|
||||||
newlineUnderDotted = false;
|
newlineUnderDotted = false;
|
||||||
indent = '\t';
|
indent = '\t';
|
||||||
T = 'T';
|
T = 'T';
|
||||||
Z = 'Z';
|
Z = 'Z';
|
||||||
nullDisabled = true;
|
nullDisabled = true;
|
||||||
multilineTableDisabled = true;
|
multilineTableDisabled = true;
|
||||||
multilineTableComma ;
|
multilineTableComma ;
|
||||||
preferCommentForThis = false;
|
preferCommentForThis = false;
|
||||||
$singlelineArray ;
|
$singlelineArray ;
|
||||||
|
|
||||||
constructor (options ) {
|
constructor (options ) {
|
||||||
|
|
||||||
@ -2887,7 +2897,16 @@ const assertFulScalar = (string ) => {
|
|||||||
let holding = false;
|
let holding = false;
|
||||||
|
|
||||||
const parse = (source , specificationVersion , multilineStringJoiner , bigint , x , argsMode ) => {
|
const parse = (source , specificationVersion , multilineStringJoiner , bigint , x , argsMode ) => {
|
||||||
let sourcePath = '';if ( typeof source==='string' ) { assertFulScalar(source); }
|
let sourcePath = '';
|
||||||
|
if ( typeof source==='object' && source ) {
|
||||||
|
if ( isArray$1(source) ) { throw TypeError$1(isLinesFromStringify(source) ? `TOML.parse(array from TOML.stringify(,{newline?}))` : `TOML.parse(array)`); }
|
||||||
|
else if ( isBinaryLike(source) ) { source = binary2string(source); }
|
||||||
|
else {
|
||||||
|
sourcePath = source.path;
|
||||||
|
if ( typeof sourcePath!=='string' ) { throw TypeError$1(`TOML.parse(source.path)`); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( typeof source==='string' ) { assertFulScalar(source); }
|
||||||
else { throw TypeError$1(`TOML.parse(source)`); }
|
else { throw TypeError$1(`TOML.parse(source)`); }
|
||||||
let joiner ;
|
let joiner ;
|
||||||
let keys ;
|
let keys ;
|
||||||
@ -2948,6 +2967,6 @@ const _export = /*#__PURE__*/Default({
|
|||||||
Keys,
|
Keys,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { Keys, LocalDate, LocalDateTime, LocalTime, OffsetDateTime, Section, basic, commentFor, commentForThis, _export as default, inline, isInline, isSection, literal, multiline, parse$1 as parse, stringify, version };
|
return _export;
|
||||||
|
|
||||||
/*¡ j-toml */
|
}));
|
||||||
|
@ -290,6 +290,8 @@ export type WidgetPreviewComponent<T = unknown, F extends BaseField = UnknownFie
|
|||||||
| ReactElement<unknown, string | JSXElementConstructor<any>>
|
| ReactElement<unknown, string | JSXElementConstructor<any>>
|
||||||
| ComponentType<WidgetPreviewProps<T, F>>;
|
| ComponentType<WidgetPreviewProps<T, F>>;
|
||||||
|
|
||||||
|
export type WidgetFor<P = EntryData> = <K extends keyof P>(name: K) => ReactNode;
|
||||||
|
|
||||||
export type WidgetsFor<P = EntryData> = <K extends keyof P>(
|
export type WidgetsFor<P = EntryData> = <K extends keyof P>(
|
||||||
name: K,
|
name: K,
|
||||||
) => P[K] extends Array<infer U>
|
) => P[K] extends Array<infer U>
|
||||||
@ -312,7 +314,7 @@ export interface TemplatePreviewProps<T = EntryData, EF extends BaseField = Unkn
|
|||||||
* @deprecated Should use `useMediaAsset` React hook instead
|
* @deprecated Should use `useMediaAsset` React hook instead
|
||||||
*/
|
*/
|
||||||
getAsset: GetAssetFunction<Field<EF>>;
|
getAsset: GetAssetFunction<Field<EF>>;
|
||||||
widgetFor: (name: T extends EntryData ? string : keyof T) => ReactNode;
|
widgetFor: WidgetFor<T>;
|
||||||
widgetsFor: WidgetsFor<T>;
|
widgetsFor: WidgetsFor<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,6 +323,20 @@ export type TemplatePreviewComponent<
|
|||||||
EF extends BaseField = UnknownField,
|
EF extends BaseField = UnknownField,
|
||||||
> = ComponentType<TemplatePreviewProps<T, EF>>;
|
> = ComponentType<TemplatePreviewProps<T, EF>>;
|
||||||
|
|
||||||
|
export interface TemplatePreviewCardProps<T = EntryData, EF extends BaseField = UnknownField> {
|
||||||
|
collection: Collection<EF>;
|
||||||
|
fields: Field<EF>[];
|
||||||
|
entry: Entry<T>;
|
||||||
|
viewStyle: 'list' | 'grid';
|
||||||
|
widgetFor: WidgetFor<T>;
|
||||||
|
widgetsFor: WidgetsFor<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TemplatePreviewCardComponent<
|
||||||
|
T = EntryData,
|
||||||
|
EF extends BaseField = UnknownField,
|
||||||
|
> = ComponentType<TemplatePreviewCardProps<T, EF>>;
|
||||||
|
|
||||||
export interface WidgetOptions<T = unknown, F extends BaseField = UnknownField> {
|
export interface WidgetOptions<T = unknown, F extends BaseField = UnknownField> {
|
||||||
validator?: Widget<T, F>['validator'];
|
validator?: Widget<T, F>['validator'];
|
||||||
getValidValue?: Widget<T, F>['getValidValue'];
|
getValidValue?: Widget<T, F>['getValidValue'];
|
||||||
@ -912,3 +928,12 @@ export type DeepPartial<T> = T extends object
|
|||||||
[P in keyof T]?: DeepPartial<T[P]>;
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
}
|
}
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
|
export interface InferredField {
|
||||||
|
type: string;
|
||||||
|
secondaryTypes: string[];
|
||||||
|
synonyms: string[];
|
||||||
|
defaultPreview: (value: string | boolean | number) => JSX.Element | ReactNode;
|
||||||
|
fallbackToFirstField: boolean;
|
||||||
|
showError: boolean;
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import type {
|
|||||||
PreviewStyle,
|
PreviewStyle,
|
||||||
PreviewStyleOptions,
|
PreviewStyleOptions,
|
||||||
ShortcodeConfig,
|
ShortcodeConfig,
|
||||||
|
TemplatePreviewCardComponent,
|
||||||
TemplatePreviewComponent,
|
TemplatePreviewComponent,
|
||||||
UnknownField,
|
UnknownField,
|
||||||
Widget,
|
Widget,
|
||||||
@ -38,6 +39,7 @@ const eventHandlers = allowedEvents.reduce((acc, e) => {
|
|||||||
interface Registry {
|
interface Registry {
|
||||||
backends: Record<string, BackendInitializer>;
|
backends: Record<string, BackendInitializer>;
|
||||||
templates: Record<string, TemplatePreviewComponent<EntryData>>;
|
templates: Record<string, TemplatePreviewComponent<EntryData>>;
|
||||||
|
cards: Record<string, TemplatePreviewCardComponent<EntryData>>;
|
||||||
widgets: Record<string, Widget>;
|
widgets: Record<string, Widget>;
|
||||||
icons: Record<string, CustomIcon>;
|
icons: Record<string, CustomIcon>;
|
||||||
additionalLinks: Record<string, AdditionalLink>;
|
additionalLinks: Record<string, AdditionalLink>;
|
||||||
@ -57,6 +59,7 @@ interface Registry {
|
|||||||
const registry: Registry = {
|
const registry: Registry = {
|
||||||
backends: {},
|
backends: {},
|
||||||
templates: {},
|
templates: {},
|
||||||
|
cards: {},
|
||||||
widgets: {},
|
widgets: {},
|
||||||
icons: {},
|
icons: {},
|
||||||
additionalLinks: {},
|
additionalLinks: {},
|
||||||
@ -71,6 +74,8 @@ const registry: Registry = {
|
|||||||
export default {
|
export default {
|
||||||
registerPreviewTemplate,
|
registerPreviewTemplate,
|
||||||
getPreviewTemplate,
|
getPreviewTemplate,
|
||||||
|
registerPreviewCard,
|
||||||
|
getPreviewCard,
|
||||||
registerWidget,
|
registerWidget,
|
||||||
getWidget,
|
getWidget,
|
||||||
getWidgets,
|
getWidgets,
|
||||||
@ -123,6 +128,17 @@ export function getPreviewTemplate(name: string): TemplatePreviewComponent<Entry
|
|||||||
return registry.templates[name];
|
return registry.templates[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview Cards
|
||||||
|
*/
|
||||||
|
export function registerPreviewCard<T>(name: string, component: TemplatePreviewCardComponent<T>) {
|
||||||
|
registry.cards[name] = component as TemplatePreviewCardComponent<EntryData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPreviewCard(name: string): TemplatePreviewCardComponent<EntryData> {
|
||||||
|
return registry.cards[name];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor Widgets
|
* Editor Widgets
|
||||||
*/
|
*/
|
||||||
|
@ -15,13 +15,13 @@ import { selectField } from './field.util';
|
|||||||
import { selectMediaFolder } from './media.util';
|
import { selectMediaFolder } from './media.util';
|
||||||
|
|
||||||
import type { Backend } from '@staticcms/core/backend';
|
import type { Backend } from '@staticcms/core/backend';
|
||||||
import type { InferredField } from '@staticcms/core/constants/fieldInference';
|
|
||||||
import type {
|
import type {
|
||||||
Collection,
|
Collection,
|
||||||
Config,
|
Config,
|
||||||
Entry,
|
Entry,
|
||||||
Field,
|
Field,
|
||||||
FilesCollection,
|
FilesCollection,
|
||||||
|
InferredField,
|
||||||
ObjectField,
|
ObjectField,
|
||||||
SortableField,
|
SortableField,
|
||||||
} from '@staticcms/core/interface';
|
} from '@staticcms/core/interface';
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
import type { Config } from '@staticcms/core/interface';
|
import type { Config } from '@staticcms/core/interface';
|
||||||
|
import type { RootState } from '@staticcms/core/store';
|
||||||
|
|
||||||
export function selectLocale(config?: Config) {
|
export function selectLocale(config?: Config) {
|
||||||
return config?.locale || 'en';
|
return config?.locale || 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectConfig(state: RootState) {
|
||||||
|
return state.config.config;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
'2015-02-16-this-is-a-toml-frontmatter-post.md': {
|
'2015-02-16-this-is-a-toml-frontmatter-post.md': {
|
||||||
content:
|
content:
|
||||||
'+++\ntitle = "This is a TOML front matter post"\nimage = "/assets/uploads/moby-dick.jpg"\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',
|
'+++\ntitle = This is a TOML front matter post\nimage = "/assets/uploads/moby-dick.jpg"\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': {
|
'2015-02-14-this-is-a-post-with-a-different-extension.other': {
|
||||||
content:
|
content:
|
||||||
|
@ -18,6 +18,51 @@ const PostPreview = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%" }}>
|
||||||
|
{viewStyle === "grid" ? widgetFor("image") : null}
|
||||||
|
<div style={{ padding: "16px", width: "100%" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: viewStyle === "grid" ? "column" : "row",
|
||||||
|
alignItems: "baseline",
|
||||||
|
gap: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ fontSize: "24px" }}>{entry.data.title}</strong>
|
||||||
|
<span style={{ fontSize: "16px" }}>{entry.data.date}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: entry.data.draft === true ? "blue" : "green",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
padding: "4px 8px",
|
||||||
|
textAlign: "center",
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "inline-block",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.data.draft === true ? "Draft" : "Published"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const GeneralPreview = ({ widgetsFor, entry }) => {
|
const GeneralPreview = ({ widgetsFor, entry }) => {
|
||||||
const title = entry.data.site_title;
|
const title = entry.data.site_title;
|
||||||
const posts = entry.data.posts;
|
const posts = entry.data.posts;
|
||||||
@ -88,6 +133,7 @@ const CustomPage = () => {
|
|||||||
|
|
||||||
cms.registerPreviewStyle(".toastui-editor-contents h1 { color: blue }", { raw: true });
|
cms.registerPreviewStyle(".toastui-editor-contents h1 { color: blue }", { raw: true });
|
||||||
cms.registerPreviewTemplate("posts", PostPreview);
|
cms.registerPreviewTemplate("posts", PostPreview);
|
||||||
|
CMS.registerPreviewCard("posts", PostPreviewCard);
|
||||||
cms.registerPreviewTemplate("general", GeneralPreview);
|
cms.registerPreviewTemplate("general", GeneralPreview);
|
||||||
cms.registerPreviewTemplate("authors", AuthorsPreview);
|
cms.registerPreviewTemplate("authors", AuthorsPreview);
|
||||||
// Pass the name of a registered control to reuse with a new widget preview.
|
// Pass the name of a registered control to reuse with a new widget preview.
|
||||||
|
@ -4,17 +4,21 @@ title: Creating Custom Previews
|
|||||||
weight: 50
|
weight: 50
|
||||||
---
|
---
|
||||||
|
|
||||||
The Static CMS exposes a `window.CMS` global object that you can use to register custom previews for an entire collection (or file within a file collection) via `registerPreviewTemplate`.
|
The Static CMS exposes a `window.CMS` global object that you can use to register custom previews for an entire collection (or file within a file collection) via `registerPreviewTemplate` (editor view) and `registerPreviewCard` (collection view).
|
||||||
|
|
||||||
### React Components Inline
|
### React Components Inline
|
||||||
|
|
||||||
The `registerPreviewTemplate` requires you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.
|
The `registerPreviewTemplate` and `registerPreviewCard` require you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.
|
||||||
|
|
||||||
However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, Static CMS exposes some constructs globally to allow you to create components inline: `h` (alias for React.createElement) as well some basic hooks (`useState`, `useMemo`, `useEffect`, `useCallback`).
|
However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, Static CMS exposes some constructs globally to allow you to create components inline: `h` (alias for React.createElement) as well some basic hooks (`useState`, `useMemo`, `useEffect`, `useCallback`).
|
||||||
|
|
||||||
**NOTE**: `createClass` is still provided, allowing for the creation of react class components. However it has now been deprecated and will be removed in `v2.0.0`.
|
**NOTE**: `createClass` is still provided, allowing for the creation of react class components. However it has now been deprecated and will be removed in `v2.0.0`.
|
||||||
|
|
||||||
## Params
|
## Editor Preview
|
||||||
|
|
||||||
|
`registerPreviewTemplate` allows you to create a template that overrides the entire editor preview for a given collection.
|
||||||
|
|
||||||
|
### `registerPreviewTemplate` Params
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
@ -26,13 +30,15 @@ The following parameters will be passed to your `react_component` during render:
|
|||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| ---------- | -------------- | ---------------------------------------------------------------------------------------------------- |
|
| ---------- | -------------- | ---------------------------------------------------------------------------------------------------- |
|
||||||
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
|
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
|
||||||
|
| collection | object | Collection configuration |
|
||||||
|
| fields | object | The fields for the given collection |
|
||||||
| document | Document | The document object the preview is within. If rendered with a frame, it will be the frame's document |
|
| document | Document | The document object the preview is within. If rendered with a frame, it will be the frame's document |
|
||||||
| window | Window | The window object the preview is within. If rendered with a frame, it will be the frame's window |
|
| window | Window | The window object the preview is within. If rendered with a frame, it will be the frame's window |
|
||||||
| getAsset | Async function | Function that given a url returns (as a promise) a loaded asset |
|
| getAsset | Async function | Function that given a url returns (as a promise) a loaded asset |
|
||||||
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
|
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
|
||||||
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
|
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
|
||||||
|
|
||||||
### Example
|
#### `registerPreviewTemplate` Example
|
||||||
|
|
||||||
<CodeTabs>
|
<CodeTabs>
|
||||||
|
|
||||||
@ -62,7 +68,7 @@ const PostPreview = ({ widgetFor, getAsset, entry, collection, field }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h1>{entry.data.title}</h1>
|
<h1>{entry.data.title}</h1>
|
||||||
<img src={imageUrl} />
|
<img src={imageUrl} />
|
||||||
<div className='text'>{widgetFor('body')}</div>
|
<div className="text">{widgetFor('body')}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -84,14 +90,20 @@ interface Post {
|
|||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostPreview = ({ widgetFor, getAsset, entry, collection, field }: TemplatePreviewProps<Post>) => {
|
const PostPreview = ({
|
||||||
|
widgetFor,
|
||||||
|
getAsset,
|
||||||
|
entry,
|
||||||
|
collection,
|
||||||
|
field,
|
||||||
|
}: TemplatePreviewProps<Post>) => {
|
||||||
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
|
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{entry.data.title}</h1>
|
<h1>{entry.data.title}</h1>
|
||||||
<img src={imageUrl} />
|
<img src={imageUrl} />
|
||||||
<div className='text'>{widgetFor('body')}</div>
|
<div className="text">{widgetFor('body')}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -101,11 +113,11 @@ CMS.registerPreviewTemplate('posts', PostPreview);
|
|||||||
|
|
||||||
</CodeTabs>
|
</CodeTabs>
|
||||||
|
|
||||||
### Lists and Objects
|
#### Lists and Objects
|
||||||
|
|
||||||
The API for accessing the individual fields of list- and object-type entries is similar to the API for accessing fields in standard entries, but there are a few key differences. Access to these nested fields is facilitated through the `widgetsFor` function, which is passed to the preview template component during render.
|
The API for accessing the individual fields of list- and object-type entries is similar to the API for accessing fields in standard entries, but there are a few key differences. Access to these nested fields is facilitated through the `widgetsFor` function, which is passed to the preview template component during render.
|
||||||
|
|
||||||
#### List Template Example
|
##### List Template Example
|
||||||
|
|
||||||
For list fields, the widgetFor function returns an array of objects that you can map over in your template. If your field is a list of authors containing two entries, with fields `name` and `description`, the return value of `widgetsFor` would look like this:
|
For list fields, the widgetFor function returns an array of objects that you can map over in your template. If your field is a list of authors containing two entries, with fields `name` and `description`, the return value of `widgetsFor` would look like this:
|
||||||
|
|
||||||
@ -210,7 +222,7 @@ CMS.registerPreviewTemplate('authors', AuthorsPreview);
|
|||||||
|
|
||||||
</CodeTabs>
|
</CodeTabs>
|
||||||
|
|
||||||
#### Object Example
|
##### Object Example
|
||||||
|
|
||||||
Object fields are simpler than lists - instead of `widgetsFor` returning an array of objects, it returns a single object. Accessing the shape of that object is the same as the shape of objects returned for list fields:
|
Object fields are simpler than lists - instead of `widgetsFor` returning an array of objects, it returns a single object. Accessing the shape of that object is the same as the shape of objects returned for list fields:
|
||||||
|
|
||||||
@ -309,3 +321,205 @@ CMS.registerPreviewTemplate('general', GeneralPreview);
|
|||||||
```
|
```
|
||||||
|
|
||||||
</CodeTabs>
|
</CodeTabs>
|
||||||
|
|
||||||
|
## Collection Card Preview
|
||||||
|
|
||||||
|
`registerPreviewCard` allows you to create a card template that overrides the cards displayed in the collection view.
|
||||||
|
|
||||||
|
### `registerPreviewCard` Params
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| name | string | The name of the collection (or file for file collections) which this preview component will be used for<br /><ul><li>Folder collections: Use the name of the collection</li><li>File collections: Use the name of the file</li></ul> |
|
||||||
|
| react_component | [React Function Component](https://reactjs.org/docs/components-and-props.html) | A React functional component that renders a preview card for a given entry in your collection |
|
||||||
|
|
||||||
|
The following parameters will be passed to your `react_component` during render:
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| ---------- | --------------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
|
| viewStyle | 'list'<br />\| 'grid' | The current view style being displayed |
|
||||||
|
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
|
||||||
|
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
|
||||||
|
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
|
||||||
|
|
||||||
|
#### `registerPreviewTemplate` Example
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```js
|
||||||
|
const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ style: { width: '100%' } },
|
||||||
|
viewStyle === 'grid' ? widgetFor('image') : null,
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ style: { padding: '16px', width: '100%' } },
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: viewStyle === 'grid' ? 'column' : 'row',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h('strong', { style: { fontSize: '24px' } }, entry.data.title),
|
||||||
|
h('span', { style: { fontSize: '16px' } }, entry.data.date),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
padding: '4px 8px',
|
||||||
|
textAlign: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entry.data.draft === true ? 'Draft' : 'Published',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CMS.registerPreviewCard('posts', PostPreviewCard);
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import CMS from '@staticcms/core';
|
||||||
|
|
||||||
|
const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
{viewStyle === 'grid' ? widgetFor('image') : null}
|
||||||
|
<div style={{ padding: '16px', width: '100%' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: viewStyle === 'grid' ? 'column' : 'row',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ fontSize: '24px' }}>{entry.data.title}</strong>
|
||||||
|
<span style={{ fontSize: '16px' }}>{entry.data.date}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
padding: '4px 8px',
|
||||||
|
textAlign: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.data.draft === true ? 'Draft' : 'Published'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import CMS from '@staticcms/core';
|
||||||
|
|
||||||
|
import type { TemplatePreviewCardProps } from '@staticcms/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type for 'entry.data'
|
||||||
|
*/
|
||||||
|
interface Post {
|
||||||
|
image: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostPreviewCard = ({ entry, widgetFor, viewStyle }: TemplatePreviewCardProps<Post>) => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
{viewStyle === 'grid' ? widgetFor('image') : null}
|
||||||
|
<div style={{ padding: '16px', width: '100%' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: viewStyle === 'grid' ? 'column' : 'row',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ fontSize: '24px' }}>{entry.data.title}</strong>
|
||||||
|
<span style={{ fontSize: '16px' }}>{entry.data.date}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
padding: '4px 8px',
|
||||||
|
textAlign: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.data.draft === true ? 'Draft' : 'Published'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CMS.registerPreviewTemplate('posts', PostPreview);
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
##### List View
|
||||||
|
|
||||||
|
![Post Preview Card List View](/img/preview_card_list.png)
|
||||||
|
|
||||||
|
##### Grid View
|
||||||
|
|
||||||
|
![Post Preview Card List View](/img/preview_card_grid.png)
|
||||||
|
BIN
packages/docs/public/img/preview_card_grid.png
Normal file
BIN
packages/docs/public/img/preview_card_grid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 241 KiB |
BIN
packages/docs/public/img/preview_card_list.png
Normal file
BIN
packages/docs/public/img/preview_card_list.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Loading…
x
Reference in New Issue
Block a user