feat: add login and logout event, clean up collection page styles (#798)
This commit is contained in:
parent
a66068ca03
commit
80a5e11722
@ -387,6 +387,28 @@ collections:
|
|||||||
- label: Description
|
- label: Description
|
||||||
name: description
|
name: description
|
||||||
widget: text
|
widget: text
|
||||||
|
- name: list_with_object_child
|
||||||
|
label: List With Object Child
|
||||||
|
widget: list
|
||||||
|
fields:
|
||||||
|
- label: Name
|
||||||
|
name: name
|
||||||
|
widget: string
|
||||||
|
hint: First and Last
|
||||||
|
- label: Description
|
||||||
|
name: description
|
||||||
|
widget: text
|
||||||
|
- label: Object
|
||||||
|
name: object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- label: Name
|
||||||
|
name: name
|
||||||
|
widget: string
|
||||||
|
hint: First and Last
|
||||||
|
- label: Description
|
||||||
|
name: description
|
||||||
|
widget: text
|
||||||
- name: string_list
|
- name: string_list
|
||||||
label: String List
|
label: String List
|
||||||
widget: list
|
widget: list
|
||||||
|
@ -184,16 +184,15 @@
|
|||||||
var slug = dateString + '-post-number-' + i + '.md';
|
var slug = dateString + '-post-number-' + i + '.md';
|
||||||
|
|
||||||
window.repoFiles._posts[slug] = {
|
window.repoFiles._posts[slug] = {
|
||||||
content:
|
content: `---
|
||||||
'---\ntitle: "This is post # ' +
|
title: "This is post # ${i}"
|
||||||
i +
|
draft: ${i % 2 === 0}
|
||||||
`\"\ndraft: ${i % 2 === 0}` +
|
image: /assets/uploads/lobby.jpg
|
||||||
'\nimage: /assets/uploads/lobby.jpg' +
|
date: ${dateString}T00:00:00.000Z
|
||||||
'\ndate: ' +
|
---
|
||||||
dateString +
|
# The post is number ${i}
|
||||||
'T00:00:00.000Z\n---\n# The post is number ' +
|
|
||||||
i +
|
![Static CMS](https://raw.githubusercontent.com/StaticJsCMS/static-cms/main/static-cms-logo.png)
|
||||||
`\n\n![Static CMS](https://raw.githubusercontent.com/StaticJsCMS/static-cms/main/static-cms-logo.png)
|
|
||||||
|
|
||||||
# Awesome Editor!
|
# Awesome Editor!
|
||||||
|
|
||||||
|
@ -11,15 +11,31 @@ const PostPreview = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostPreviewCard = ({ entry, theme, hasLocalBackup }) => {
|
const PostPreviewCard = ({ entry, theme, hasLocalBackup, widgetFor }) => {
|
||||||
const date = new Date(entry.data.date);
|
const date = new Date(entry.data.date);
|
||||||
|
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
const day = date.getDate();
|
const day = date.getDate();
|
||||||
|
|
||||||
|
const image = entry.data.image;
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
{ style: { width: '100%' } },
|
{ style: { width: '100%' } },
|
||||||
|
h('div', {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
borderTopLeftRadius: '8px',
|
||||||
|
borderTopRightRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '140px',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundRepat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
objectFit: 'cover',
|
||||||
|
backgroundImage: `url('${image}')`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{ style: { padding: '16px', width: '100%' } },
|
{ style: { padding: '16px', width: '100%' } },
|
||||||
@ -52,7 +68,6 @@ const PostPreviewCard = ({ entry, theme, hasLocalBackup }) => {
|
|||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'rgb(107, 114, 128)',
|
color: 'rgb(107, 114, 128)',
|
||||||
fontSize: '14px',
|
|
||||||
lineHeight: '18px',
|
lineHeight: '18px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -93,7 +108,7 @@ const PostPreviewCard = ({ entry, theme, hasLocalBackup }) => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
title: 'Has local backup'
|
title: 'Has local backup',
|
||||||
},
|
},
|
||||||
'i',
|
'i',
|
||||||
)
|
)
|
||||||
@ -151,6 +166,8 @@ const PostDraftFieldPreview = ({ value }) => {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
|
lineHeight: '16px',
|
||||||
|
height: '20px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value === true ? 'Draft' : 'Published',
|
value === true ? 'Draft' : 'Published',
|
||||||
@ -226,7 +243,7 @@ const CustomPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CMS.registerPreviewTemplate('posts', PostPreview);
|
CMS.registerPreviewTemplate('posts', PostPreview);
|
||||||
CMS.registerPreviewCard('posts', PostPreviewCard);
|
CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
|
||||||
CMS.registerFieldPreview('posts', 'date', PostDateFieldPreview);
|
CMS.registerFieldPreview('posts', 'date', PostDateFieldPreview);
|
||||||
CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);
|
CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);
|
||||||
CMS.registerPreviewTemplate('general', GeneralPreview);
|
CMS.registerPreviewTemplate('general', GeneralPreview);
|
||||||
|
@ -150,15 +150,6 @@ export default class BitbucketBackend implements BackendClass {
|
|||||||
return AuthenticationPage;
|
return AuthenticationPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(user: { token: string }) {
|
|
||||||
this.token = user.token;
|
|
||||||
this.api = new API({
|
|
||||||
requestFunction: this.apiRequestFunction,
|
|
||||||
branch: this.branch,
|
|
||||||
repo: this.repo,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
requestFunction = async (req: ApiRequest) => {
|
requestFunction = async (req: ApiRequest) => {
|
||||||
const token = await this.getToken();
|
const token = await this.getToken();
|
||||||
const authorizedRequest = unsentRequest.withHeaders({ Authorization: `Bearer ${token}` }, req);
|
const authorizedRequest = unsentRequest.withHeaders({ Authorization: `Bearer ${token}` }, req);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
@ -185,6 +185,17 @@ const App = ({
|
|||||||
}
|
}
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const [prevUser, setPrevUser] = useState(user);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!prevUser && user) {
|
||||||
|
invokeEvent('login', {
|
||||||
|
login: user.login,
|
||||||
|
name: user.name ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setPrevUser(user);
|
||||||
|
}, [prevUser, user]);
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return authenticationPage;
|
return authenticationPage;
|
||||||
|
@ -13,7 +13,7 @@ const MultiSearchCollectionPage: FC = () => {
|
|||||||
const filterTerm = params['*'];
|
const filterTerm = params['*'];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainView breadcrumbs={[{ name: 'Search' }]} showQuickCreate showLeftNav>
|
<MainView breadcrumbs={[{ name: 'Search' }]} showQuickCreate showLeftNav noScroll noMargin>
|
||||||
<CollectionView
|
<CollectionView
|
||||||
name={name}
|
name={name}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
@ -42,7 +42,7 @@ const SingleCollectionPage: FC<SingleCollectionPageProps> = ({
|
|||||||
const breadcrumbs = useBreadcrumbs(collection, filterTerm);
|
const breadcrumbs = useBreadcrumbs(collection, filterTerm);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainView breadcrumbs={breadcrumbs} showQuickCreate showLeftNav noScroll>
|
<MainView breadcrumbs={breadcrumbs} showQuickCreate showLeftNav noScroll noMargin>
|
||||||
<CollectionView
|
<CollectionView
|
||||||
name={name}
|
name={name}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
|
@ -196,7 +196,7 @@ const CollectionView = ({
|
|||||||
const collectionDescription = collection?.description;
|
const collectionDescription = collection?.description;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full px-5 pt-4">
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
{isSearchResults ? (
|
{isSearchResults ? (
|
||||||
<>
|
<>
|
||||||
|
@ -79,7 +79,10 @@ const EntryCard: FC<TranslatedProps<EntryCardProps>> = ({
|
|||||||
[collection, entry.slug],
|
[collection, entry.slug],
|
||||||
);
|
);
|
||||||
|
|
||||||
const PreviewCardComponent = useMemo(() => getPreviewCard(templateName) ?? null, [templateName]);
|
const PreviewCardComponent = useMemo(
|
||||||
|
() => getPreviewCard(templateName)?.component ?? null,
|
||||||
|
[templateName],
|
||||||
|
);
|
||||||
|
|
||||||
const theme = useAppSelector(selectTheme);
|
const theme = useAppSelector(selectTheme);
|
||||||
|
|
||||||
|
@ -152,16 +152,18 @@ const EntryListing: FC<TranslatedProps<EntryListingProps>> = ({
|
|||||||
|
|
||||||
if (viewStyle === VIEW_STYLE_TABLE) {
|
if (viewStyle === VIEW_STYLE_TABLE) {
|
||||||
return (
|
return (
|
||||||
<EntryListingTable
|
<div className="pb-3 overflow-hidden">
|
||||||
key="table"
|
<EntryListingTable
|
||||||
entryData={entryData}
|
key="table"
|
||||||
isSingleCollectionInList={isSingleCollectionInList}
|
entryData={entryData}
|
||||||
summaryFieldHeaders={summaryFieldHeaders}
|
isSingleCollectionInList={isSingleCollectionInList}
|
||||||
loadNext={handleLoadMore}
|
summaryFieldHeaders={summaryFieldHeaders}
|
||||||
canLoadMore={Boolean(hasMore && handleLoadMore)}
|
loadNext={handleLoadMore}
|
||||||
isLoadingEntries={isLoadingEntries}
|
canLoadMore={Boolean(hasMore && handleLoadMore)}
|
||||||
t={t}
|
isLoadingEntries={isLoadingEntries}
|
||||||
/>
|
t={t}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { VariableSizeGrid as Grid } from 'react-window';
|
import { VariableSizeGrid as Grid } from 'react-window';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MEDIA_CARD_HEIGHT,
|
COLLECTION_CARD_HEIGHT,
|
||||||
MEDIA_CARD_MARGIN,
|
COLLECTION_CARD_HEIGHT_WITHOUT_IMAGE,
|
||||||
MEDIA_CARD_WIDTH,
|
COLLECTION_CARD_MARGIN,
|
||||||
MEDIA_LIBRARY_PADDING,
|
COLLECTION_CARD_WIDTH,
|
||||||
} from '@staticcms/core/constants/mediaLibrary';
|
} from '@staticcms/core/constants/views';
|
||||||
|
import { getPreviewCard } from '@staticcms/core/lib/registry';
|
||||||
import classNames from '@staticcms/core/lib/util/classNames.util';
|
import classNames from '@staticcms/core/lib/util/classNames.util';
|
||||||
|
import { selectTemplateName } from '@staticcms/core/lib/util/collection.util';
|
||||||
|
import { isNotNullish } from '@staticcms/core/lib/util/null.util';
|
||||||
import EntryCard from './EntryCard';
|
import EntryCard from './EntryCard';
|
||||||
|
|
||||||
import type { CollectionEntryData } from '@staticcms/core/interface';
|
import type { CollectionEntryData } from '@staticcms/core/interface';
|
||||||
@ -40,7 +43,7 @@ const CardWrapper = ({
|
|||||||
parseFloat(
|
parseFloat(
|
||||||
`${
|
`${
|
||||||
typeof style.left === 'number'
|
typeof style.left === 'number'
|
||||||
? style.left ?? MEDIA_CARD_MARGIN * columnIndex
|
? style.left ?? COLLECTION_CARD_MARGIN * columnIndex
|
||||||
: style.left
|
: style.left
|
||||||
}`,
|
}`,
|
||||||
),
|
),
|
||||||
@ -66,8 +69,8 @@ const CardWrapper = ({
|
|||||||
top,
|
top,
|
||||||
width: style.width,
|
width: style.width,
|
||||||
height: style.height,
|
height: style.height,
|
||||||
paddingRight: `${columnIndex + 1 === columnCount ? 0 : MEDIA_CARD_MARGIN}px`,
|
paddingRight: `${columnIndex + 1 === columnCount ? 0 : COLLECTION_CARD_MARGIN}px`,
|
||||||
paddingBottom: `${MEDIA_CARD_MARGIN}px`,
|
paddingBottom: `${COLLECTION_CARD_MARGIN}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EntryCard
|
<EntryCard
|
||||||
@ -93,14 +96,51 @@ const EntryListingCardGrid: FC<EntryListingCardGridProps> = ({
|
|||||||
setVersion(oldVersion => oldVersion + 1);
|
setVersion(oldVersion => oldVersion + 1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getHeightFn = useCallback((data: CollectionEntryData) => {
|
||||||
|
const templateName = selectTemplateName(data.collection, data.entry.slug);
|
||||||
|
|
||||||
|
return getPreviewCard(templateName)?.getHeight ?? null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getDefaultHeight = useCallback((data?: CollectionEntryData) => {
|
||||||
|
return isNotNullish(data?.imageFieldName)
|
||||||
|
? COLLECTION_CARD_HEIGHT
|
||||||
|
: COLLECTION_CARD_HEIGHT_WITHOUT_IMAGE;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [prevCardHeights, setPrevCardHeight] = useState<number[]>([]);
|
||||||
|
const cardHeights: number[] = useMemo(() => {
|
||||||
|
const newCardHeights = [...prevCardHeights];
|
||||||
|
const startIndex = newCardHeights.length;
|
||||||
|
const endIndex = entryData.length;
|
||||||
|
|
||||||
|
for (let i = startIndex; i < endIndex; i++) {
|
||||||
|
const data = entryData[i];
|
||||||
|
const getHeight = getHeightFn(data);
|
||||||
|
if (getHeight) {
|
||||||
|
newCardHeights.push(getHeight({ collection: data.collection, entry: data.entry }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newCardHeights.push(getDefaultHeight(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCardHeights;
|
||||||
|
}, [entryData, getDefaultHeight, getHeightFn, prevCardHeights]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cardHeights.length !== prevCardHeights.length) {
|
||||||
|
setPrevCardHeight(cardHeights);
|
||||||
|
}
|
||||||
|
}, [cardHeights, prevCardHeights.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
<AutoSizer onResize={handleResize}>
|
<AutoSizer onResize={handleResize}>
|
||||||
{({ height = 0, width = 0 }) => {
|
{({ height = 0, width = 0 }) => {
|
||||||
const columnWidthWithGutter = MEDIA_CARD_WIDTH + MEDIA_CARD_MARGIN;
|
const columnWidthWithGutter = COLLECTION_CARD_WIDTH + COLLECTION_CARD_MARGIN;
|
||||||
const rowHeightWithGutter = MEDIA_CARD_HEIGHT + MEDIA_CARD_MARGIN;
|
|
||||||
const columnCount = Math.floor(width / columnWidthWithGutter);
|
const columnCount = Math.floor(width / columnWidthWithGutter);
|
||||||
const nonGutterSpace = (width - MEDIA_CARD_MARGIN * columnCount) / width;
|
const nonGutterSpace = (width - COLLECTION_CARD_MARGIN * columnCount) / width;
|
||||||
const columnWidth = (1 / columnCount) * nonGutterSpace;
|
const columnWidth = (1 / columnCount) * nonGutterSpace;
|
||||||
|
|
||||||
const rowCount = Math.ceil(entryData.length / columnCount);
|
const rowCount = Math.ceil(entryData.length / columnCount);
|
||||||
@ -123,12 +163,15 @@ const EntryListingCardGrid: FC<EntryListingCardGridProps> = ({
|
|||||||
columnWidth={index =>
|
columnWidth={index =>
|
||||||
index + 1 === columnCount
|
index + 1 === columnCount
|
||||||
? width * columnWidth
|
? width * columnWidth
|
||||||
: width * columnWidth + MEDIA_CARD_MARGIN
|
: width * columnWidth + COLLECTION_CARD_MARGIN
|
||||||
}
|
}
|
||||||
rowCount={rowCount}
|
rowCount={rowCount}
|
||||||
rowHeight={() => rowHeightWithGutter}
|
rowHeight={index =>
|
||||||
|
(cardHeights.length > index ? cardHeights[index] : getDefaultHeight()) +
|
||||||
|
COLLECTION_CARD_MARGIN
|
||||||
|
}
|
||||||
width={width}
|
width={width}
|
||||||
height={height - MEDIA_LIBRARY_PADDING}
|
height={height}
|
||||||
itemData={
|
itemData={
|
||||||
{
|
{
|
||||||
entryData,
|
entryData,
|
||||||
@ -146,6 +189,7 @@ const EntryListingCardGrid: FC<EntryListingCardGridProps> = ({
|
|||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
style={{ position: 'unset' }}
|
style={{ position: 'unset' }}
|
||||||
|
overscanRowCount={5}
|
||||||
>
|
>
|
||||||
{CardWrapper}
|
{CardWrapper}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -58,7 +58,7 @@ const EntryListingGrid: FC<EntryListingGridProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full overflow-hidden">
|
<div className="relative h-full overflow-hidden">
|
||||||
<div ref={gridContainerRef} className="relative h-full overflow-auto styled-scrollbars">
|
<div ref={gridContainerRef} className="relative h-full overflow-hidden">
|
||||||
<EntryListingCardGrid
|
<EntryListingCardGrid
|
||||||
key="grid"
|
key="grid"
|
||||||
entryData={entryData}
|
entryData={entryData}
|
||||||
|
@ -69,8 +69,28 @@ const EntryListingTable: FC<EntryListingTableProps> = ({
|
|||||||
}, [clientHeight, fetchMoreOnBottomReached, scrollHeight, scrollTop]);
|
}, [clientHeight, fetchMoreOnBottomReached, scrollHeight, scrollTop]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full overflow-hidden">
|
<div
|
||||||
<div ref={tableContainerRef} className="relative h-full overflow-auto styled-scrollbars">
|
className="
|
||||||
|
relative
|
||||||
|
max-h-full
|
||||||
|
h-full
|
||||||
|
overflow-hidden
|
||||||
|
p-1.5
|
||||||
|
bg-white
|
||||||
|
dark:bg-slate-800
|
||||||
|
rounded-xl
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={tableContainerRef}
|
||||||
|
className="
|
||||||
|
relative
|
||||||
|
h-full
|
||||||
|
overflow-auto
|
||||||
|
styled-scrollbars
|
||||||
|
styled-scrollbars-secondary
|
||||||
|
"
|
||||||
|
>
|
||||||
<Table
|
<Table
|
||||||
columns={
|
columns={
|
||||||
!isSingleCollectionInList
|
!isSingleCollectionInList
|
||||||
|
@ -22,6 +22,7 @@ const Card = ({ children, className }: CardProps) => {
|
|||||||
dark:border-gray-700/40
|
dark:border-gray-700/40
|
||||||
flex
|
flex
|
||||||
flex-col
|
flex-col
|
||||||
|
h-full
|
||||||
`,
|
`,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
@ -11,18 +11,17 @@ interface TableCellProps {
|
|||||||
|
|
||||||
const TableCell = ({ columns, children }: TableCellProps) => {
|
const TableCell = ({ columns, children }: TableCellProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="shadow-md">
|
<div
|
||||||
|
className="
|
||||||
|
shadow-md
|
||||||
|
z-[2]
|
||||||
|
"
|
||||||
|
>
|
||||||
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-300">
|
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-300">
|
||||||
<thead className="text-xs">
|
<thead className="text-xs">
|
||||||
<tr>
|
<tr>
|
||||||
{columns.map((column, index) => (
|
{columns.map((column, index) => (
|
||||||
<TableHeaderCell
|
<TableHeaderCell key={index}>{column}</TableHeaderCell>
|
||||||
key={index}
|
|
||||||
isFirst={index === 0}
|
|
||||||
isLast={index + 1 === columns.length}
|
|
||||||
>
|
|
||||||
{column}
|
|
||||||
</TableHeaderCell>
|
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -38,12 +38,21 @@ const TableCell = ({ children, emphasis = false, to, shrink = false }: TableCell
|
|||||||
<td
|
<td
|
||||||
className={classNames(
|
className={classNames(
|
||||||
!to ? 'px-4 py-3' : 'p-0',
|
!to ? 'px-4 py-3' : 'p-0',
|
||||||
'text-gray-500 dark:text-gray-300',
|
`
|
||||||
|
text-gray-500
|
||||||
|
dark:text-gray-300
|
||||||
|
`,
|
||||||
emphasis && 'font-medium text-gray-900 whitespace-nowrap dark:text-white',
|
emphasis && 'font-medium text-gray-900 whitespace-nowrap dark:text-white',
|
||||||
shrink && 'w-0',
|
shrink && 'w-0',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{content}
|
<div
|
||||||
|
className="
|
||||||
|
h-[44px]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,37 +7,36 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
interface TableHeaderCellProps {
|
interface TableHeaderCellProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
isFirst: boolean;
|
|
||||||
isLast: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableHeaderCell = ({ children, isFirst, isLast }: TableHeaderCellProps) => {
|
const TableHeaderCell = ({ children }: TableHeaderCellProps) => {
|
||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
className="
|
className={classNames(
|
||||||
text-gray-500
|
`
|
||||||
bg-slate-50
|
font-bold
|
||||||
dark:text-gray-400
|
sticky
|
||||||
dark:bg-slate-900
|
top-0
|
||||||
sticky
|
border-0
|
||||||
top-0
|
p-0
|
||||||
p-0
|
`,
|
||||||
"
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className="
|
||||||
`
|
px-4
|
||||||
bg-gray-100
|
py-3
|
||||||
border-slate-200
|
text-gray-900
|
||||||
dark:bg-slate-700
|
border-gray-100
|
||||||
dark:border-gray-700
|
border-b
|
||||||
px-4
|
bg-white
|
||||||
py-3
|
dark:text-white
|
||||||
`,
|
dark:border-gray-700
|
||||||
isFirst && 'rounded-tl-lg',
|
dark:bg-slate-800
|
||||||
isLast && 'rounded-tr-lg',
|
shadow-sm
|
||||||
)}
|
text-[14px]
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{typeof children === 'string' && isEmpty(children) ? <> </> : children}
|
{typeof children === 'string' && isEmpty(children) ? <> </> : children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,11 +14,14 @@ const TableRow = ({ children, className }: TableRowProps) => {
|
|||||||
<tr
|
<tr
|
||||||
className={classNames(
|
className={classNames(
|
||||||
`
|
`
|
||||||
border-b
|
border-t
|
||||||
|
first:border-t-0
|
||||||
|
border-gray-100
|
||||||
|
dark:border-gray-700
|
||||||
bg-white
|
bg-white
|
||||||
hover:bg-slate-50
|
hover:bg-slate-50
|
||||||
dark:bg-slate-800
|
dark:bg-slate-800
|
||||||
dark:border-gray-700
|
dark:hover:bg-slate-700
|
||||||
`,
|
`,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
@ -2,3 +2,9 @@ export const VIEW_STYLE_TABLE = 'table';
|
|||||||
export const VIEW_STYLE_GRID = 'grid';
|
export const VIEW_STYLE_GRID = 'grid';
|
||||||
|
|
||||||
export type ViewStyle = typeof VIEW_STYLE_TABLE | typeof VIEW_STYLE_GRID;
|
export type ViewStyle = typeof VIEW_STYLE_TABLE | typeof VIEW_STYLE_GRID;
|
||||||
|
|
||||||
|
export const COLLECTION_CARD_WIDTH = 240;
|
||||||
|
export const COLLECTION_CARD_HEIGHT = 204;
|
||||||
|
export const COLLECTION_CARD_IMAGE_HEIGHT = 140;
|
||||||
|
export const COLLECTION_CARD_HEIGHT_WITHOUT_IMAGE = 64;
|
||||||
|
export const COLLECTION_CARD_MARGIN = 10;
|
||||||
|
@ -887,9 +887,14 @@ export interface BackendInitializer<EF extends BaseField = UnknownField> {
|
|||||||
init: (config: Config<EF>, options: BackendInitializerOptions) => BackendClass;
|
init: (config: Config<EF>, options: BackendInitializerOptions) => BackendClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthorData {
|
||||||
|
login: string | undefined;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EventData {
|
export interface EventData {
|
||||||
entry: Entry;
|
entry: Entry;
|
||||||
author: { login: string | undefined; name: string };
|
author: AuthorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PreSaveEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
|
export type PreSaveEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
|
||||||
@ -906,10 +911,21 @@ export type MountedEventHandler<O extends Record<string, unknown> = Record<strin
|
|||||||
options: O,
|
options: O,
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
|
|
||||||
|
export type LoginEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
|
||||||
|
data: AuthorData,
|
||||||
|
options: O,
|
||||||
|
) => void | Promise<void>;
|
||||||
|
|
||||||
|
export type LogoutEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
|
||||||
|
options: O,
|
||||||
|
) => void | Promise<void>;
|
||||||
|
|
||||||
export type EventHandlers<O extends Record<string, unknown> = Record<string, unknown>> = {
|
export type EventHandlers<O extends Record<string, unknown> = Record<string, unknown>> = {
|
||||||
preSave: PreSaveEventHandler<O>;
|
preSave: PreSaveEventHandler<O>;
|
||||||
postSave: PostSaveEventHandler<O>;
|
postSave: PostSaveEventHandler<O>;
|
||||||
mounted: MountedEventHandler<O>;
|
mounted: MountedEventHandler<O>;
|
||||||
|
login: LoginEventHandler<O>;
|
||||||
|
logout: LogoutEventHandler<O>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EventListener<
|
export interface EventListener<
|
||||||
|
@ -96,6 +96,10 @@ export function slugFormatter<EF extends BaseField = UnknownField>(
|
|||||||
entryData: EntryData,
|
entryData: EntryData,
|
||||||
slugConfig?: Slug,
|
slugConfig?: Slug,
|
||||||
): string {
|
): string {
|
||||||
|
if (!('fields' in collection)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const slugTemplate = collection.slug || '{{slug}}';
|
const slugTemplate = collection.slug || '{{slug}}';
|
||||||
|
|
||||||
const identifierField = selectIdentifier(collection);
|
const identifierField = selectIdentifier(collection);
|
||||||
@ -152,12 +156,12 @@ export function summaryFormatter<EF extends BaseField>(
|
|||||||
export function folderFormatter<EF extends BaseField>(
|
export function folderFormatter<EF extends BaseField>(
|
||||||
folderTemplate: string,
|
folderTemplate: string,
|
||||||
entry: Entry | null | undefined,
|
entry: Entry | null | undefined,
|
||||||
collection: Collection<EF>,
|
collection: Collection<EF> | undefined,
|
||||||
defaultFolder: string,
|
defaultFolder: string,
|
||||||
folderKey: string,
|
folderKey: string,
|
||||||
slugConfig?: Slug,
|
slugConfig?: Slug,
|
||||||
) {
|
) {
|
||||||
if (!entry || !entry.data) {
|
if (!entry || !entry.data || !collection) {
|
||||||
return folderTemplate;
|
return folderTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@ import { oneLine } from 'common-tags';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
AdditionalLink,
|
AdditionalLink,
|
||||||
|
AuthorData,
|
||||||
BackendClass,
|
BackendClass,
|
||||||
BackendInitializer,
|
BackendInitializer,
|
||||||
BackendInitializerOptions,
|
BackendInitializerOptions,
|
||||||
BaseField,
|
BaseField,
|
||||||
|
Collection,
|
||||||
Config,
|
Config,
|
||||||
CustomIcon,
|
CustomIcon,
|
||||||
Entry,
|
Entry,
|
||||||
@ -14,6 +16,8 @@ import type {
|
|||||||
EventListener,
|
EventListener,
|
||||||
FieldPreviewComponent,
|
FieldPreviewComponent,
|
||||||
LocalePhrasesRoot,
|
LocalePhrasesRoot,
|
||||||
|
LoginEventHandler,
|
||||||
|
LogoutEventHandler,
|
||||||
MountedEventHandler,
|
MountedEventHandler,
|
||||||
ObjectValue,
|
ObjectValue,
|
||||||
PostSaveEventHandler,
|
PostSaveEventHandler,
|
||||||
@ -30,13 +34,15 @@ import type {
|
|||||||
WidgetValueSerializer,
|
WidgetValueSerializer,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
|
|
||||||
export const allowedEvents = ['mounted', 'preSave', 'postSave'] as const;
|
export const allowedEvents = ['mounted', 'login', 'logout', 'preSave', 'postSave'] as const;
|
||||||
export type AllowedEvent = (typeof allowedEvents)[number];
|
export type AllowedEvent = (typeof allowedEvents)[number];
|
||||||
|
|
||||||
type EventHandlerRegistry = {
|
type EventHandlerRegistry = {
|
||||||
preSave: { handler: PreSaveEventHandler; options: Record<string, unknown> }[];
|
preSave: { handler: PreSaveEventHandler; options: Record<string, unknown> }[];
|
||||||
postSave: { handler: PostSaveEventHandler; options: Record<string, unknown> }[];
|
postSave: { handler: PostSaveEventHandler; options: Record<string, unknown> }[];
|
||||||
mounted: { handler: MountedEventHandler; options: Record<string, unknown> }[];
|
mounted: { handler: MountedEventHandler; options: Record<string, unknown> }[];
|
||||||
|
login: { handler: LoginEventHandler; options: Record<string, unknown> }[];
|
||||||
|
logout: { handler: LogoutEventHandler; options: Record<string, unknown> }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventHandlers = allowedEvents.reduce((acc, e) => {
|
const eventHandlers = allowedEvents.reduce((acc, e) => {
|
||||||
@ -44,10 +50,15 @@ const eventHandlers = allowedEvents.reduce((acc, e) => {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as EventHandlerRegistry);
|
}, {} as EventHandlerRegistry);
|
||||||
|
|
||||||
|
interface CardPreviews {
|
||||||
|
component: TemplatePreviewCardComponent<ObjectValue>;
|
||||||
|
getHeight?: (data: { collection: Collection; entry: Entry }) => number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Registry {
|
interface Registry {
|
||||||
backends: Record<string, BackendInitializer>;
|
backends: Record<string, BackendInitializer>;
|
||||||
templates: Record<string, TemplatePreviewComponent<ObjectValue>>;
|
templates: Record<string, TemplatePreviewComponent<ObjectValue>>;
|
||||||
cards: Record<string, TemplatePreviewCardComponent<ObjectValue>>;
|
cards: Record<string, CardPreviews>;
|
||||||
fieldPreviews: Record<string, Record<string, FieldPreviewComponent>>;
|
fieldPreviews: Record<string, Record<string, FieldPreviewComponent>>;
|
||||||
widgets: Record<string, Widget>;
|
widgets: Record<string, Widget>;
|
||||||
icons: Record<string, CustomIcon>;
|
icons: Record<string, CustomIcon>;
|
||||||
@ -145,11 +156,15 @@ export function getPreviewTemplate(name: string): TemplatePreviewComponent<Objec
|
|||||||
export function registerPreviewCard<T, EF extends BaseField = UnknownField>(
|
export function registerPreviewCard<T, EF extends BaseField = UnknownField>(
|
||||||
name: string,
|
name: string,
|
||||||
component: TemplatePreviewCardComponent<T, EF>,
|
component: TemplatePreviewCardComponent<T, EF>,
|
||||||
|
getHeight?: () => number,
|
||||||
) {
|
) {
|
||||||
registry.cards[name] = component as TemplatePreviewCardComponent<ObjectValue>;
|
registry.cards[name] = {
|
||||||
|
component: component as TemplatePreviewCardComponent<ObjectValue>,
|
||||||
|
getHeight,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreviewCard(name: string): TemplatePreviewCardComponent<ObjectValue> | null {
|
export function getPreviewCard(name: string): CardPreviews | null {
|
||||||
return registry.cards[name] ?? null;
|
return registry.cards[name] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,19 +351,27 @@ export function registerEventListener<
|
|||||||
>({ name, handler }: EventListener<E, O>, options?: O) {
|
>({ name, handler }: EventListener<E, O>, options?: O) {
|
||||||
validateEventName(name);
|
validateEventName(name);
|
||||||
registry.eventHandlers[name].push({
|
registry.eventHandlers[name].push({
|
||||||
handler: handler as MountedEventHandler & PreSaveEventHandler & PostSaveEventHandler,
|
handler: handler as MountedEventHandler &
|
||||||
|
LoginEventHandler &
|
||||||
|
PreSaveEventHandler &
|
||||||
|
PostSaveEventHandler,
|
||||||
options: options ?? {},
|
options: options ?? {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function invokeEvent(name: 'login', data: AuthorData): Promise<void>;
|
||||||
|
export async function invokeEvent(name: 'logout'): Promise<void>;
|
||||||
export async function invokeEvent(name: 'preSave', data: EventData): Promise<EntryData>;
|
export async function invokeEvent(name: 'preSave', data: EventData): Promise<EntryData>;
|
||||||
export async function invokeEvent(name: 'postSave', data: EventData): Promise<void>;
|
export async function invokeEvent(name: 'postSave', data: EventData): Promise<void>;
|
||||||
export async function invokeEvent(name: 'mounted'): Promise<void>;
|
export async function invokeEvent(name: 'mounted'): Promise<void>;
|
||||||
export async function invokeEvent(name: AllowedEvent, data?: EventData): Promise<void | EntryData> {
|
export async function invokeEvent(
|
||||||
|
name: AllowedEvent,
|
||||||
|
data?: EventData | AuthorData,
|
||||||
|
): Promise<void | EntryData> {
|
||||||
validateEventName(name);
|
validateEventName(name);
|
||||||
|
|
||||||
if (name === 'mounted') {
|
if (name === 'mounted' || name === 'logout') {
|
||||||
console.info('[StaticCMS] Firing mounted event');
|
console.info(`[StaticCMS] Firing ${name} event`);
|
||||||
const handlers = registry.eventHandlers[name];
|
const handlers = registry.eventHandlers[name];
|
||||||
for (const { handler, options } of handlers) {
|
for (const { handler, options } of handlers) {
|
||||||
handler(options);
|
handler(options);
|
||||||
@ -357,11 +380,21 @@ export async function invokeEvent(name: AllowedEvent, data?: EventData): Promise
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'login') {
|
||||||
|
console.info('[StaticCMS] Firing login event', data);
|
||||||
|
const handlers = registry.eventHandlers[name];
|
||||||
|
for (const { handler, options } of handlers) {
|
||||||
|
handler(data as AuthorData, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'postSave') {
|
if (name === 'postSave') {
|
||||||
console.info(`[StaticCMS] Firing post save event`, data);
|
console.info(`[StaticCMS] Firing post save event`, data);
|
||||||
const handlers = registry.eventHandlers[name];
|
const handlers = registry.eventHandlers[name];
|
||||||
for (const { handler, options } of handlers) {
|
for (const { handler, options } of handlers) {
|
||||||
handler(data!, options);
|
handler(data as EventData, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -371,7 +404,7 @@ export async function invokeEvent(name: AllowedEvent, data?: EventData): Promise
|
|||||||
|
|
||||||
console.info(`[StaticCMS] Firing pre save event`, data);
|
console.info(`[StaticCMS] Firing pre save event`, data);
|
||||||
|
|
||||||
let _data = { ...data! };
|
let _data = { ...(data as EventData) };
|
||||||
for (const { handler, options } of handlers) {
|
for (const { handler, options } of handlers) {
|
||||||
const result = await handler(_data, options);
|
const result = await handler(_data, options);
|
||||||
if (_data !== undefined && result !== undefined) {
|
if (_data !== undefined && result !== undefined) {
|
||||||
|
@ -360,7 +360,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .styled-scrollbars.styled-scrollbars-secondary {
|
.dark .styled-scrollbars.styled-scrollbars-secondary {
|
||||||
--scrollbar-foreground: rgba(47, 64, 93, 0.8);
|
--scrollbar-foreground: rgba(47, 64, 84, 0.8);
|
||||||
--scrollbar-background: rgb(30 41 59);
|
--scrollbar-background: rgb(30 41 59);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,3 +383,28 @@
|
|||||||
/* Background */
|
/* Background */
|
||||||
background: var(--scrollbar-background);
|
background: var(--scrollbar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
|
||||||
|
td {
|
||||||
|
&:first-child {
|
||||||
|
& > div {
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
& > div {
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -60,7 +60,6 @@ const ObjectControl: FC<WidgetControlProps<ObjectValue, ObjectField>> = ({
|
|||||||
parentHidden={hidden}
|
parentHidden={hidden}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
forList={forList}
|
|
||||||
forSingleList={forSingleList}
|
forSingleList={forSingleList}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
const { removeModuleScopePlugin } = require("customize-cra");
|
|
||||||
|
|
||||||
module.exports = removeModuleScopePlugin();
|
|
@ -183,15 +183,15 @@
|
|||||||
var slug = dateString + "-post-number-" + i + ".md";
|
var slug = dateString + "-post-number-" + i + ".md";
|
||||||
|
|
||||||
window.repoFiles._posts[slug] = {
|
window.repoFiles._posts[slug] = {
|
||||||
content:
|
content: `---
|
||||||
'---\ntitle: "This is post # ' +
|
title: "This is post # ${i}"
|
||||||
i +
|
draft: ${i % 2 === 0}
|
||||||
`\"\ndraft: ${i % 2 === 0}` +
|
image: /assets/uploads/lobby.jpg
|
||||||
"\ndate: " +
|
date: ${dateString}T00:00:00.000Z
|
||||||
dateString +
|
---
|
||||||
"T00:00:00.000Z\n---\n# The post is number " +
|
# The post is number ${i}
|
||||||
i +
|
|
||||||
`\n\n![Static CMS](https://raw.githubusercontent.com/StaticJsCMS/static-cms/main/static-cms-logo.png)
|
![Static CMS](https://raw.githubusercontent.com/StaticJsCMS/static-cms/main/static-cms-logo.png)
|
||||||
|
|
||||||
# Awesome Editor!
|
# Awesome Editor!
|
||||||
|
|
||||||
@ -312,5 +312,6 @@ widget: 'markdown',
|
|||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -3,8 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "react-app-rewired start",
|
"dev": "vite",
|
||||||
"build": "react-app-rewired build"
|
"build": "vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/eslint-parser": "7.21.3",
|
"@babel/eslint-parser": "7.21.3",
|
||||||
@ -18,9 +19,8 @@
|
|||||||
"@babel/core": "7.21.4",
|
"@babel/core": "7.21.4",
|
||||||
"@babel/plugin-syntax-flow": "7.21.4",
|
"@babel/plugin-syntax-flow": "7.21.4",
|
||||||
"@babel/plugin-transform-react-jsx": "7.21.0",
|
"@babel/plugin-transform-react-jsx": "7.21.0",
|
||||||
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
|
||||||
"customize-cra": "1.0.0",
|
|
||||||
"eslint": "8.39.0",
|
"eslint": "8.39.0",
|
||||||
"eslint-import-resolver-typescript": "3.5.5",
|
"eslint-import-resolver-typescript": "3.5.5",
|
||||||
"eslint-plugin-cypress": "2.13.2",
|
"eslint-plugin-cypress": "2.13.2",
|
||||||
@ -32,9 +32,8 @@
|
|||||||
"postcss": "8.4.23",
|
"postcss": "8.4.23",
|
||||||
"postcss-scss": "4.0.6",
|
"postcss-scss": "4.0.6",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"react-app-rewired": "2.2.1",
|
"vite": "4.3.5",
|
||||||
"react-scripts": "5.0.1",
|
"vite-plugin-svgr": "3.2.0",
|
||||||
"typescript": "5.0.4",
|
|
||||||
"webpack": "5.80.0"
|
"webpack": "5.80.0"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import cms, { useMediaAsset } from "@staticcms/core";
|
import cms, { useMediaAsset } from "@staticcms/core";
|
||||||
|
|
||||||
|
import "@staticcms/core/dist/main.css";
|
||||||
|
|
||||||
// Register all the things
|
// Register all the things
|
||||||
cms.init();
|
cms.init();
|
||||||
|
|
||||||
@ -18,81 +20,111 @@ const PostPreview = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostPreviewCard = ({ entry, theme }) => {
|
const PostPreviewCard = ({ entry, theme, hasLocalBackup }) => {
|
||||||
const date = new Date(entry.data.date);
|
const date = new Date(entry.data.date);
|
||||||
|
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
const day = date.getDate();
|
const day = date.getDate();
|
||||||
|
|
||||||
return h(
|
const image = entry.data.image;
|
||||||
'div',
|
|
||||||
{ style: { width: '100%' } },
|
return (
|
||||||
h(
|
<div style={{ width: "100%" }}>
|
||||||
'div',
|
<div
|
||||||
{ style: { padding: '16px', width: '100%' } },
|
style={{
|
||||||
h(
|
width: "100%",
|
||||||
'div',
|
borderTopLeftRadius: "8px",
|
||||||
{
|
borderTopRightRadius: "8px",
|
||||||
style: {
|
overflow: "hidden",
|
||||||
display: 'flex',
|
height: "140px",
|
||||||
width: '100%',
|
backgroundSize: "cover",
|
||||||
justifyContent: 'space-between',
|
backgroundRepat: "no-repeat",
|
||||||
alignItems: 'start',
|
backgroundPosition: "center",
|
||||||
gap: '4px',
|
objectFit: "cover",
|
||||||
color: theme === 'dark' ? 'white' : 'inherit',
|
backgroundImage: `url('${image}')`,
|
||||||
},
|
}}
|
||||||
},
|
/>
|
||||||
h(
|
<div style={{ padding: "16px", width: "100%" }}>
|
||||||
'div',
|
<div
|
||||||
{
|
style={{
|
||||||
style: {
|
display: "flex",
|
||||||
display: 'flex',
|
width: "100%",
|
||||||
flexDirection: 'column',
|
justifyContent: "space-between",
|
||||||
alignItems: 'baseline',
|
alignItems: "start",
|
||||||
gap: '4px',
|
gap: "4px",
|
||||||
},
|
color: theme === "dark" ? "white" : "inherit",
|
||||||
},
|
}}
|
||||||
h(
|
>
|
||||||
'div',
|
<div
|
||||||
{
|
style={{
|
||||||
style: {
|
display: "flex",
|
||||||
fontSize: '14px',
|
flexDirection: "column",
|
||||||
|
alignItems: "baseline",
|
||||||
|
gap: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "14px",
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'rgb(107, 114, 128)',
|
color: "rgb(107, 114, 128)",
|
||||||
fontSize: '14px',
|
lineHeight: "18px",
|
||||||
lineHeight: '18px',
|
}}
|
||||||
},
|
>
|
||||||
},
|
{entry.data.title}
|
||||||
entry.data.title,
|
</div>
|
||||||
),
|
<span style={{ fontSize: "14px" }}>{`${date.getFullYear()}-${month < 10 ? `0${month}` : month}-${
|
||||||
h(
|
|
||||||
'span',
|
|
||||||
{ style: { fontSize: '14px' } },
|
|
||||||
`${date.getFullYear()}-${month < 10 ? `0${month}` : month}-${
|
|
||||||
day < 10 ? `0${day}` : day
|
day < 10 ? `0${day}` : day
|
||||||
}`,
|
}`}</span>
|
||||||
),
|
</div>
|
||||||
),
|
<div
|
||||||
h(
|
style={{
|
||||||
'div',
|
display: "flex",
|
||||||
{
|
alignItems: "center",
|
||||||
style: {
|
whiteSpace: "no-wrap",
|
||||||
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
gap: "8px",
|
||||||
color: 'white',
|
}}
|
||||||
border: 'none',
|
>
|
||||||
padding: '2px 6px',
|
{hasLocalBackup ? (
|
||||||
textAlign: 'center',
|
<div
|
||||||
textDecoration: 'none',
|
style={{
|
||||||
display: 'inline-block',
|
border: "2px solid rgb(147, 197, 253)",
|
||||||
cursor: 'pointer',
|
borderRadius: "50%",
|
||||||
borderRadius: '4px',
|
color: "rgb(147, 197, 253)",
|
||||||
fontSize: '14px',
|
height: "18px",
|
||||||
},
|
width: "18px",
|
||||||
},
|
fontWeight: "bold",
|
||||||
entry.data.draft === true ? 'Draft' : 'Published',
|
fontSize: "11px",
|
||||||
),
|
display: "flex",
|
||||||
),
|
alignItems: "center",
|
||||||
),
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
title="Has local backup"
|
||||||
|
>
|
||||||
|
i
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: entry.data.draft === true ? "rgb(37, 99, 235)" : "rgb(22, 163, 74)",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
padding: "2px 6px",
|
||||||
|
textAlign: "center",
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "inline-block",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.data.draft === true ? "Draft" : "Published"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,31 +134,29 @@ const PostDateFieldPreview = ({ value }) => {
|
|||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
const day = date.getDate();
|
const day = date.getDate();
|
||||||
|
|
||||||
return h(
|
return <div>{`${date.getFullYear()}-${month < 10 ? `0${month}` : month}-${day < 10 ? `0${day}` : day}`}</div>;
|
||||||
'div',
|
|
||||||
{},
|
|
||||||
`${date.getFullYear()}-${month < 10 ? `0${month}` : month}-${day < 10 ? `0${day}` : day}`,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostDraftFieldPreview = ({ value }) => {
|
const PostDraftFieldPreview = ({ value }) => {
|
||||||
return h(
|
return (
|
||||||
'div',
|
<div
|
||||||
{
|
style={{
|
||||||
style: {
|
backgroundColor: value === true ? "rgb(37 99 235)" : "rgb(22 163 74)",
|
||||||
backgroundColor: value === true ? 'rgb(37 99 235)' : 'rgb(22 163 74)',
|
color: "white",
|
||||||
color: 'white',
|
border: "none",
|
||||||
border: 'none',
|
padding: "2px 6px",
|
||||||
padding: '2px 6px',
|
textAlign: "center",
|
||||||
textAlign: 'center',
|
textDecoration: "none",
|
||||||
textDecoration: 'none',
|
display: "inline-block",
|
||||||
display: 'inline-block',
|
cursor: "pointer",
|
||||||
cursor: 'pointer',
|
borderRadius: "4px",
|
||||||
borderRadius: '4px',
|
fontSize: "14px",
|
||||||
fontSize: '14px',
|
lineHeight: "16px",
|
||||||
},
|
height: "20px",
|
||||||
},
|
}}
|
||||||
value === true ? 'Draft' : 'Published',
|
>
|
||||||
|
{value === true ? "Draft" : "Published"}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,9 +229,9 @@ const CustomPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cms.registerPreviewTemplate("posts", PostPreview);
|
cms.registerPreviewTemplate("posts", PostPreview);
|
||||||
CMS.registerPreviewCard("posts", PostPreviewCard);
|
CMS.registerPreviewCard("posts", PostPreviewCard, () => 240);
|
||||||
CMS.registerFieldPreview('posts', 'date', PostDateFieldPreview);
|
CMS.registerFieldPreview("posts", "date", PostDateFieldPreview);
|
||||||
CMS.registerFieldPreview('posts', 'draft', PostDraftFieldPreview);
|
CMS.registerFieldPreview("posts", "draft", PostDraftFieldPreview);
|
||||||
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.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
|
|
||||||
import "../../core/src/styles/main.css";
|
|
||||||
import "./cms";
|
import "./cms";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
@ -1,7 +0,0 @@
|
|||||||
const baseConfig = require('../../tailwind.base.config');
|
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: ['../core/src/**/*.tsx'],
|
|
||||||
...baseConfig,
|
|
||||||
};
|
|
17
packages/demo/vite.config.ts
Normal file
17
packages/demo/vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import svgrPlugin from "vite-plugin-svgr";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), svgrPlugin()],
|
||||||
|
assetsInclude: ["public/**/*"],
|
||||||
|
optimizeDeps: {
|
||||||
|
force: true,
|
||||||
|
include: ["@staticcms/core"],
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
commonjsOptions: { include: [/core/, /node_modules/] },
|
||||||
|
outDir: "build",
|
||||||
|
},
|
||||||
|
});
|
@ -92,7 +92,7 @@ CMS.registerEventListener({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported events are `mounted`, `preSave` and `postSave`. The `preSave` hook can be used to modify the entry data like so:
|
Supported events are `mounted`, `login`, `preSave` and `postSave`. The `preSave` hook can be used to modify the entry data like so:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
CMS.registerEventListener({
|
CMS.registerEventListener({
|
||||||
|
@ -339,10 +339,11 @@ CMS.registerPreviewStyle('.main { color: blue; border: 1px solid gree; }', { raw
|
|||||||
|
|
||||||
### Params
|
### Params
|
||||||
|
|
||||||
| Param | Type | Description |
|
| 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> |
|
| 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 |
|
| 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 |
|
||||||
|
| getHeight | function | A function that returns the height for your cards. An object containing the current `collection` and `entry` are passed into the function at render. If no `getHeight` function is provided, the height will be `204` if the collection has an image field or `64` if the collection does not have an image field |
|
||||||
|
|
||||||
The following parameters will be passed to your `react_component` during render:
|
The following parameters will be passed to your `react_component` during render:
|
||||||
|
|
||||||
@ -412,7 +413,7 @@ const PostPreviewCard = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CMS.registerPreviewCard('posts', PostPreviewCard);
|
CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
@ -462,6 +463,8 @@ const PostPreviewCard = ({ entry, widgetFor }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
|
||||||
```
|
```
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
@ -523,7 +526,7 @@ const PostPreviewCard = ({ entry, widgetFor }: TemplatePreviewCardProps<Post>) =
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CMS.registerPreviewTemplate('posts', PostPreview);
|
CMS.registerPreviewCard('posts', PostPreviewCard, () => 240);
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeTabs>
|
</CodeTabs>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user