diff --git a/.changeset/opt-drag-clone-array-items.md b/.changeset/opt-drag-clone-array-items.md new file mode 100644 index 000000000..17597eac6 --- /dev/null +++ b/.changeset/opt-drag-clone-array-items.md @@ -0,0 +1,5 @@ +--- +"@blinkk/root-cms": patch +--- + +Add opt+drag to clone array items in the CMS doc editor. diff --git a/docs/scripts/env-init.sh b/docs/scripts/env-init.sh index c116b0d99..ca510385b 100755 --- a/docs/scripts/env-init.sh +++ b/docs/scripts/env-init.sh @@ -10,10 +10,19 @@ ENV_FILE="$DOCS_DIR/.env" PROJECT="rootjs-dev" SECRET_NAME="docs-env" -if [[ -f "$ENV_FILE" ]]; then +if [[ -s "$ENV_FILE" ]]; then exit 0 fi echo ".env file not found, downloading from Google Secrets Manager..." -gcloud secrets versions access latest --secret="$SECRET_NAME" --project="$PROJECT" > "$ENV_FILE" +ENV_CONTENT="$(gcloud secrets versions access latest --secret="$SECRET_NAME" --project="$PROJECT")" || { + echo "ERROR: Failed to download .env from Google Secrets Manager." >&2 + echo "Ensure gcloud is installed, you are authenticated, and have access to project '$PROJECT'." >&2 + exit 1 +} +if [[ -z "$ENV_CONTENT" ]]; then + echo "ERROR: Secret '$SECRET_NAME' returned empty content." >&2 + exit 1 +fi +echo "$ENV_CONTENT" > "$ENV_FILE" echo "Downloaded .env file." diff --git a/packages/root-cms/signin/signin.tsx b/packages/root-cms/signin/signin.tsx index f25c870dc..dc9d88f6e 100644 --- a/packages/root-cms/signin/signin.tsx +++ b/packages/root-cms/signin/signin.tsx @@ -153,9 +153,26 @@ function loginSuccessRedirect() { window.location.replace(redirectUrl); } -const app = initializeApp(window.__ROOT_CTX.firebaseConfig); -const auth = getAuth(app); -window.firebase = {app, auth}; -const root = document.getElementById('root')!; -root.innerHTML = ''; -render(, root); +try { + const app = initializeApp(window.__ROOT_CTX.firebaseConfig); + const auth = getAuth(app); + window.firebase = {app, auth}; + const root = document.getElementById('root')!; + root.innerHTML = ''; + render(, root); +} catch (err) { + console.error(err); + const root = document.getElementById('root')!; + root.innerHTML = ''; + render(, root); +} + +function BootstrapError(props: {message: string}) { + return ( +
+
+ Something went wrong: {props.message} +
+
+ ); +} diff --git a/packages/root-cms/signin/styles/global.css b/packages/root-cms/signin/styles/global.css index 126be471d..905bdbafa 100644 --- a/packages/root-cms/signin/styles/global.css +++ b/packages/root-cms/signin/styles/global.css @@ -102,6 +102,18 @@ body.menu\:open { padding: 0 24px; } +.bootstrap-warning { + max-width: 520px; + padding: 12px; + border: 1px solid #fbbc04; + border-radius: 8px; + background: #fff9db; + color: #5f370e; + font-size: 14px; + line-height: 1.5; + font-weight: 400; +} + @keyframes bootstrap-loading-error { 0% { opacity: 0; diff --git a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx index 65b9073b0..33c2b41ba 100644 --- a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx +++ b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx @@ -760,6 +760,7 @@ interface ArrayDuplicate { draft: DraftDocController; deepKey: string; value: ArrayItemValue; + skipOpen?: boolean; } interface ArrayMoveUp { @@ -906,7 +907,8 @@ function arrayReducer(state: ArrayFieldValue, action: ArrayAction) { ...data, [newKey]: clonedValue, _array: order, - _new: [...newlyAdded, newKey], + _new: action.skipOpen ? newlyAdded : [...newlyAdded, newKey], + _moved: action.skipOpen ? newKey : undefined, }; } case 'moveUp': { @@ -1373,21 +1375,34 @@ DocEditor.ArrayField = (props: FieldProps) => { return ( {(provided, snapshot) => ( -
+
focusFieldHeader(props.deepKey, i)} + onClick={(e: MouseEvent) => { + if (e.altKey) { + dispatch({ + type: 'duplicate', + draft, + deepKey: props.deepKey, + index: i, + value: getItemValue(i), + skipOpen: true, + }); + return; + } + focusFieldHeader(props.deepKey, i); + }} >
diff --git a/packages/root-cms/ui/styles/global.css b/packages/root-cms/ui/styles/global.css index 6cdc29f9d..6de0f8e1f 100644 --- a/packages/root-cms/ui/styles/global.css +++ b/packages/root-cms/ui/styles/global.css @@ -108,6 +108,18 @@ body.menu\:open { padding: 0 24px; } +.bootstrap-warning { + max-width: 520px; + padding: 12px; + border: 1px solid #fbbc04; + border-radius: 8px; + background: #fff9db; + color: #5f370e; + font-size: 14px; + line-height: 1.5; + font-weight: 400; +} + .sr-only { position: absolute; width: 1px; diff --git a/packages/root-cms/ui/ui.tsx b/packages/root-cms/ui/ui.tsx index a2e5439d3..49fc909fc 100644 --- a/packages/root-cms/ui/ui.tsx +++ b/packages/root-cms/ui/ui.tsx @@ -176,7 +176,12 @@ declare global { channel: true | false | 'to-preview' | 'from-preview'; }; /** Checks registered via the CMS plugin config. */ - checks?: Array<{id: string; label: string; description?: string; collections?: string[]}>; + checks?: Array<{ + id: string; + label: string; + description?: string; + collections?: string[]; + }>; }; firebase: FirebaseContextObject; } @@ -316,35 +321,52 @@ function registerDevServerRedirectShortcut() { registerDevServerRedirectShortcut(); -const app = initializeApp(window.__ROOT_CTX.firebaseConfig); -const databaseId = window.__ROOT_CTX.firebaseConfig.databaseId || '(default)'; -// const db = getFirestore(app); -// NOTE(stevenle): the firestore web channel rpc sometimes has issues in -// collections with a large number of docs. Forcing long polling and disabling -// fetch streams seems to work for some people. This may cause performance -// issues however. -const db = initializeFirestore( - app, - { - experimentalForceLongPolling: true, - useFetchStreams: false, - } as any, - databaseId -); -const auth = getAuth(app); -const storage = getStorage(app); -auth.onAuthStateChanged((user) => { - if (!user) { - loginRedirect(); - return; - } - window.firebase = {app, auth, db, storage, user}; +try { + const app = initializeApp(window.__ROOT_CTX.firebaseConfig); + const databaseId = window.__ROOT_CTX.firebaseConfig.databaseId || '(default)'; + // const db = getFirestore(app); + // NOTE(stevenle): the firestore web channel rpc sometimes has issues in + // collections with a large number of docs. Forcing long polling and disabling + // fetch streams seems to work for some people. This may cause performance + // issues however. + const db = initializeFirestore( + app, + { + experimentalForceLongPolling: true, + useFetchStreams: false, + } as any, + databaseId + ); + const auth = getAuth(app); + const storage = getStorage(app); + auth.onAuthStateChanged((user) => { + if (!user) { + loginRedirect(); + return; + } + window.firebase = {app, auth, db, storage, user}; + const root = document.getElementById('root')!; + root.innerHTML = ''; + render(, root); + + updateSession(user); + }); +} catch (err) { + console.error(err); const root = document.getElementById('root')!; root.innerHTML = ''; - render(, root); + render(, root); +} - updateSession(user); -}); +function BootstrapError(props: {message: string}) { + return ( +
+
+ Something went wrong: {props.message} +
+
+ ); +} async function updateSession(user: User) { const idToken = await user.getIdToken();