feat: live updates
This commit is contained in:
@ -25,7 +25,7 @@
|
|||||||
"prepublishOnly": "yarn build ",
|
"prepublishOnly": "yarn build ",
|
||||||
"prepack": "cp ../../README.md ./",
|
"prepack": "cp ../../README.md ./",
|
||||||
"postpack": "rm ./README.md",
|
"postpack": "rm ./README.md",
|
||||||
"serve": "webpack serve",
|
"serve": "webpack serve --config-name configMain",
|
||||||
"test": "cross-env NODE_ENV=test jest",
|
"test": "cross-env NODE_ENV=test jest",
|
||||||
"test:integration": "cross-env NODE_ENV=test jest -c jest.config.integration.js",
|
"test:integration": "cross-env NODE_ENV=test jest -c jest.config.integration.js",
|
||||||
"test:ci": "cross-env NODE_ENV=test jest --maxWorkers=2 --coverage",
|
"test:ci": "cross-env NODE_ENV=test jest --maxWorkers=2 --coverage",
|
||||||
|
@ -212,7 +212,6 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
}, [collection, createBackup, entryDraft.entry, hasChanged]);
|
}, [collection, createBackup, entryDraft.entry, hasChanged]);
|
||||||
|
|
||||||
useEntryCallback({
|
useEntryCallback({
|
||||||
hasChanged,
|
|
||||||
collection,
|
collection,
|
||||||
slug,
|
slug,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
|
@ -186,7 +186,10 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
|||||||
const livePreviewIframe = useRef<HTMLIFrameElement>(null);
|
const livePreviewIframe = useRef<HTMLIFrameElement>(null);
|
||||||
const passEventToIframe = useCallback((event: DataUpdateEvent) => {
|
const passEventToIframe = useCallback((event: DataUpdateEvent) => {
|
||||||
if (livePreviewIframe.current) {
|
if (livePreviewIframe.current) {
|
||||||
livePreviewIframe.current.contentWindow?.postMessage(event);
|
livePreviewIframe.current.contentWindow?.postMessage({
|
||||||
|
message: 'data:update',
|
||||||
|
value: { fieldPath: event.detail.fieldPath, value: event.detail.value },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from './live';
|
|
@ -16,7 +16,6 @@ import Registry from './lib/registry';
|
|||||||
|
|
||||||
export * from './backends';
|
export * from './backends';
|
||||||
export * from './interface';
|
export * from './interface';
|
||||||
export * from './components';
|
|
||||||
export * from './lib';
|
export * from './lib';
|
||||||
export { default as locales } from './locales';
|
export { default as locales } from './locales';
|
||||||
export * from './widgets';
|
export * from './widgets';
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { ValueOrNestedValue } from '@staticcms/core/interface';
|
import type { ValueOrNestedValue } from '@staticcms/core/interface';
|
||||||
import type DataUpdateEvent from '../util/events/DataEvent';
|
|
||||||
|
|
||||||
export default function useData(value: ValueOrNestedValue, path: string) {
|
export default function useData(value: ValueOrNestedValue, path: string) {
|
||||||
const [data, setData] = useState(value);
|
const [data, setData] = useState(value);
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const isCms = useMemo(() => {
|
||||||
const isCms = searchParams.get('useCmsData') === 'true';
|
if (!window) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
return searchParams.get('useCmsData') === 'true';
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onDataChange = useCallback(
|
const onDataChange = useCallback(
|
||||||
(event: DataUpdateEvent) => {
|
(event: MessageEvent) => {
|
||||||
if (!isCms) {
|
if (!isCms || event.data.message !== 'data:update') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.detail.fieldPath === path) {
|
const { fieldPath, value } = event.data.value;
|
||||||
setData(event.detail.value);
|
|
||||||
|
if (fieldPath === path) {
|
||||||
|
setData(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isCms, path],
|
[isCms, path],
|
||||||
@ -28,10 +34,10 @@ export default function useData(value: ValueOrNestedValue, path: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('data:update', onDataChange);
|
window?.addEventListener('message', onDataChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('data:update', onDataChange);
|
window?.removeEventListener('message', onDataChange);
|
||||||
};
|
};
|
||||||
}, [isCms, onDataChange]);
|
}, [isCms, onDataChange]);
|
||||||
|
|
||||||
|
@ -63,18 +63,12 @@ async function handleChange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EntryCallbackProps {
|
interface EntryCallbackProps {
|
||||||
hasChanged: boolean;
|
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
slug: string | undefined;
|
slug: string | undefined;
|
||||||
callback: () => void;
|
callback: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useEntryCallback({
|
export default function useEntryCallback({ slug, collection, callback }: EntryCallbackProps) {
|
||||||
hasChanged,
|
|
||||||
slug,
|
|
||||||
collection,
|
|
||||||
callback,
|
|
||||||
}: EntryCallbackProps) {
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const entry = useAppSelector(selectEditingDraft);
|
const entry = useAppSelector(selectEditingDraft);
|
||||||
@ -85,7 +79,7 @@ export default function useEntryCallback({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChanged && entry) {
|
if (entry) {
|
||||||
const file = fileForEntry(collection, slug);
|
const file = fileForEntry(collection, slug);
|
||||||
let updatedEntryData = entry.data;
|
let updatedEntryData = entry.data;
|
||||||
|
|
||||||
@ -121,9 +115,9 @@ export default function useEntryCallback({
|
|||||||
setLastEntryData(entry?.data);
|
setLastEntryData(entry?.data);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [entry, hasChanged]);
|
}, [entry]);
|
||||||
|
|
||||||
const debouncedRunUpdateCheck = useDebouncedCallback(runUpdateCheck, 500);
|
const debouncedRunUpdateCheck = useDebouncedCallback(runUpdateCheck, 200);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedRunUpdateCheck();
|
debouncedRunUpdateCheck();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useData } from '@staticcms/core/lib';
|
import useData from '@staticcms/core/lib/hooks/useData';
|
||||||
|
|
||||||
import type { ValueOrNestedValue } from '@staticcms/core/interface';
|
import type { ValueOrNestedValue } from '@staticcms/core/interface';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
@ -12,7 +12,6 @@ export interface DataProps {
|
|||||||
|
|
||||||
const Data: FC<DataProps> = ({ path, value }) => {
|
const Data: FC<DataProps> = ({ path, value }) => {
|
||||||
const data = useData(value, path);
|
const data = useData(value, path);
|
||||||
|
|
||||||
return <>{data}</>;
|
return <>{data}</>;
|
||||||
};
|
};
|
||||||
|
|
@ -12,8 +12,7 @@ function moduleNameToPath(libName) {
|
|||||||
return path.resolve(__dirname, '..', '..', 'node_modules', libName);
|
return path.resolve(__dirname, '..', '..', 'node_modules', libName);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
const config = {
|
||||||
entry: './src/index.ts',
|
|
||||||
mode: isProduction ? 'production' : 'development',
|
mode: isProduction ? 'production' : 'development',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
externals: isProduction
|
externals: isProduction
|
||||||
@ -107,15 +106,6 @@ module.exports = {
|
|||||||
STATIC_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
|
STATIC_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
|
||||||
}),
|
}),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
output: {
|
|
||||||
publicPath: '',
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
filename: 'static-cms-core.js',
|
|
||||||
library: {
|
|
||||||
name: 'StaticCmsCore',
|
|
||||||
type: 'umd',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devServer: {
|
devServer: {
|
||||||
static: {
|
static: {
|
||||||
directory: './dev-test',
|
directory: './dev-test',
|
||||||
@ -126,3 +116,33 @@ module.exports = {
|
|||||||
hot: true,
|
hot: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const configLive = Object.assign({}, config, {
|
||||||
|
name: "configLive",
|
||||||
|
entry: './src/live/index.ts',
|
||||||
|
output: {
|
||||||
|
publicPath: '',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'live.js',
|
||||||
|
library: {
|
||||||
|
name: 'StaticCmsCoreLive',
|
||||||
|
type: 'umd',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const configMain = Object.assign({}, config, {
|
||||||
|
name: "configMain",
|
||||||
|
entry: './src/index.ts',
|
||||||
|
output: {
|
||||||
|
publicPath: '',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'static-cms-core.js',
|
||||||
|
library: {
|
||||||
|
name: 'StaticCmsCore',
|
||||||
|
type: 'umd',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [configMain, configLive]
|
||||||
|
Reference in New Issue
Block a user