Skip to content

Commit d24c705

Browse files
tea-artistteable-bot
andauthored
fix: table collaborator do not sync (#993) (#2402)
Synced from teableio/teable-ee@4ef88fa Co-authored-by: teable-bot <bot@teable.io>
1 parent 7fe7141 commit d24c705

File tree

15 files changed

+534
-29
lines changed

15 files changed

+534
-29
lines changed

apps/nestjs-backend/src/types/i18n.generated.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,6 +2948,22 @@ export type I18nTranslations = {
29482948
"urlCopiedForDiscord": string;
29492949
"publishSuccessDescription": string;
29502950
"shareWith": string;
2951+
"unpublishedApps": {
2952+
"title": string;
2953+
"description": string;
2954+
"publishAll": string;
2955+
"publish": string;
2956+
"published": string;
2957+
"publishing": string;
2958+
"publishFailed": string;
2959+
"publishFailedTip1": string;
2960+
"publishFailedTip2": string;
2961+
"notPublished": string;
2962+
"ignoreAndContinue": string;
2963+
"goToFix": string;
2964+
"redeploy": string;
2965+
"unnamedApp": string;
2966+
};
29512967
};
29522968
"collaborators": string;
29532969
"more": string;

apps/nextjs-app/src/features/app/blocks/table/table-header/Collaborators.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { ColorUtils, contractColorForTheme, getCollaboratorsChannel } from '@tea
22
import { useTheme } from '@teable/next-themes';
33
import type { ICollaboratorUser } from '@teable/sdk';
44
import { useSession, CollaboratorWithHoverCard } from '@teable/sdk';
5-
import { useConnection } from '@teable/sdk/hooks';
5+
import { useConnection, useTableId } from '@teable/sdk/hooks';
66
import { cn, Popover, PopoverContent, PopoverTrigger } from '@teable/ui-lib/shadcn';
77
import { chunk, isEmpty } from 'lodash';
8-
import { useRouter } from 'next/router';
98
import React, { useEffect, useMemo, useState } from 'react';
109
import type { Presence } from 'sharedb/lib/client';
1110

@@ -15,9 +14,8 @@ interface CollaboratorsProps {
1514
}
1615

1716
export const Collaborators: React.FC<CollaboratorsProps> = ({ className, maxAvatarLen = 3 }) => {
18-
const router = useRouter();
1917
const { connection } = useConnection();
20-
const { tableId } = router.query;
18+
const tableId = useTableId();
2119
const { user: sessionUser } = useSession();
2220
const { resolvedTheme } = useTheme();
2321

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createContext, useContext } from 'react';
2+
import type { IUnpublishedApp } from './UnpublishedAppsDialog';
3+
4+
export interface IAppPublishContextValue {
5+
publishApp?: (app: IUnpublishedApp) => Promise<void>;
6+
onAppStateChange?: (callback: (apps: IUnpublishedApp[]) => void) => void;
7+
onPublishComplete?: (callback: () => void) => void;
8+
}
9+
10+
export const AppPublishContext = createContext<IAppPublishContextValue>({});
11+
12+
export const useAppPublishContext = () => {
13+
return useContext(AppPublishContext);
14+
};

apps/nextjs-app/src/features/app/blocks/table/table-header/publish-base/PublishBaseDialog.tsx

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@ import {
3737
} from '@teable/ui-lib/shadcn';
3838
import { toast } from '@teable/ui-lib/shadcn/ui/sonner';
3939
import confetti from 'canvas-confetti';
40-
import { Camera, Send, Copy } from 'lucide-react';
40+
import { Camera, Send, Copy, ExternalLink } from 'lucide-react';
4141
import { useTranslation } from 'next-i18next';
42-
import { useState, useRef, useEffect, useMemo } from 'react';
42+
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
4343
import { useIsCloud } from '@/features/app/hooks/useIsCloud';
4444
import { ROOT_ID } from '../../../base/base-node/hooks';
4545
import { useBaseNodeContext } from '../../../base/base-node/hooks/useBaseNodeContext';
46+
import { useAppPublishContext } from './AppPublishContext';
4647
import { NodeSelect } from './NodeSelect';
4748
import { NodeTreeSelect } from './NodeTreeSelect';
49+
import type { IUnpublishedApp } from './UnpublishedAppsDialog';
50+
import { UnpublishedAppsDialog, getUnpublishedAppNodes } from './UnpublishedAppsDialog';
4851

4952
const attachmentManager = new AttachmentManager(1);
5053

@@ -73,6 +76,7 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
7376
const baseId = base?.id;
7477
const { treeItems } = useBaseNodeContext();
7578
const isCloud = useIsCloud();
79+
const appPublishContext = useAppPublishContext();
7680

7781
const queryClient = useQueryClient();
7882

@@ -210,6 +214,9 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
210214
const [hasLoadedTemplate, setHasLoadedTemplate] = useState(false);
211215
const [successDialogOpen, setSuccessDialogOpen] = useState(false);
212216
const [shareUrl, setShareUrl] = useState('');
217+
const [unpublishedAppsDialogOpen, setUnpublishedAppsDialogOpen] = useState(false);
218+
const [unpublishedApps, setUnpublishedApps] = useState<IUnpublishedApp[]>([]);
219+
const [externalApps, setExternalApps] = useState<IUnpublishedApp[] | undefined>(undefined);
213220

214221
// Initialize selected nodes on first load
215222
useEffect(() => {
@@ -348,6 +355,35 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
348355
});
349356
};
350357

358+
const handlePublishClick = useCallback(() => {
359+
if (!title || !description) {
360+
toast.error(t('publishBase.tips.publishValidation'));
361+
return;
362+
}
363+
364+
if (selectedNodeIds.length === 0) {
365+
toast.error(t('publishBase.tips.atLeastOneNode'));
366+
return;
367+
}
368+
369+
// Check for unpublished app nodes
370+
const unpublishedAppNodes = getUnpublishedAppNodes(selectedNodeIds, treeItems);
371+
if (unpublishedAppNodes.length > 0) {
372+
setUnpublishedApps(unpublishedAppNodes);
373+
setExternalApps(undefined); // Reset external apps state
374+
setUnpublishedAppsDialogOpen(true);
375+
return;
376+
}
377+
378+
// No unpublished apps, proceed with publishing
379+
publishBaseMutate({ title, description: description || '' });
380+
}, [title, description, selectedNodeIds, treeItems, publishBaseMutate, t]);
381+
382+
const handleContinuePublish = useCallback(() => {
383+
setUnpublishedAppsDialogOpen(false);
384+
publishBaseMutate({ title, description: description || '' });
385+
}, [title, description, publishBaseMutate]);
386+
351387
return (
352388
<>
353389
<Dialog open={open} onOpenChange={setOpen}>
@@ -457,19 +493,7 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
457493
)}
458494
<Button
459495
className="flex w-full items-center gap-2"
460-
onClick={() => {
461-
if (!title || !description) {
462-
toast.error(t('publishBase.tips.publishValidation'));
463-
return;
464-
}
465-
466-
if (selectedNodeIds.length === 0) {
467-
toast.error(t('publishBase.tips.atLeastOneNode'));
468-
return;
469-
}
470-
471-
publishBaseMutate({ title, description: description || '' });
472-
}}
496+
onClick={handlePublishClick}
473497
disabled={publishBaseLoading}
474498
>
475499
<Send className="size-4" />
@@ -576,6 +600,14 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
576600
>
577601
<Copy className="size-4" />
578602
</Button>
603+
<Button
604+
size="icon"
605+
variant="ghost"
606+
className="size-9 shrink-0 rounded-none border-l p-0"
607+
onClick={() => window.open(shareUrl, '_blank')}
608+
>
609+
<ExternalLink className="size-4" />
610+
</Button>
579611
</div>
580612
)}
581613
</div>
@@ -614,6 +646,14 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
614646
<Button size="sm" variant="outline" className="size-9 p-0" onClick={handleCopyUrl}>
615647
<Copy className="size-4" />
616648
</Button>
649+
<Button
650+
size="sm"
651+
variant="outline"
652+
className="size-9 p-0"
653+
onClick={() => window.open(shareUrl, '_blank')}
654+
>
655+
<ExternalLink className="size-4" />
656+
</Button>
617657
</div>
618658

619659
{isCloud && (
@@ -650,6 +690,16 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
650690
</div>
651691
</DialogContent>
652692
</Dialog>
693+
694+
<UnpublishedAppsDialog
695+
open={unpublishedAppsDialogOpen}
696+
onOpenChange={setUnpublishedAppsDialogOpen}
697+
unpublishedApps={unpublishedApps}
698+
treeItems={treeItems}
699+
onContinue={handleContinuePublish}
700+
onPublishApp={appPublishContext.publishApp}
701+
externalApps={externalApps}
702+
/>
653703
</>
654704
);
655705
};

0 commit comments

Comments
 (0)