Skip to content

Commit 95a596e

Browse files
authored
Fix namespace popover (#172)
* fix: open publish manage modal after closing share popover * chore: update log
1 parent ed24f05 commit 95a596e

File tree

9 files changed

+220
-65
lines changed

9 files changed

+220
-65
lines changed

cypress/e2e/page/publish-page.cy.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,45 @@ describe('Publish Page Test', () => {
693693
});
694694
});
695695

696+
it('opens publish manage modal from namespace caret and closes share popover first', () => {
697+
cy.on('uncaught:exception', (err: Error) => {
698+
if (err.message.includes('No workspace or service found') ||
699+
err.message.includes('createThemeNoVars_default is not a function') ||
700+
err.message.includes('View not found')) {
701+
return false;
702+
}
703+
return true;
704+
});
705+
706+
cy.visit('/login', { failOnStatusCode: false });
707+
cy.wait(1000);
708+
const authUtils = new AuthTestUtils();
709+
authUtils.signInWithTestUrl(testEmail).then(() => {
710+
cy.url().should('include', '/app');
711+
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
712+
PageSelectors.names().should('exist', { timeout: 30000 });
713+
cy.wait(2000);
714+
715+
TestTool.openSharePopover();
716+
cy.contains('Publish').should('exist').click({ force: true });
717+
cy.wait(1000);
718+
719+
ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true });
720+
cy.wait(5000);
721+
ShareSelectors.publishNamespace().should('be.visible', { timeout: 10000 });
722+
723+
ShareSelectors.sharePopover().should('exist');
724+
ShareSelectors.openPublishSettingsButton().should('be.visible').click({ force: true });
725+
726+
ShareSelectors.sharePopover().should('not.exist');
727+
ShareSelectors.publishManageModal().should('be.visible');
728+
ShareSelectors.publishManagePanel().should('be.visible').contains('Namespace');
729+
730+
cy.get('body').type('{esc}');
731+
ShareSelectors.publishManageModal().should('not.exist');
732+
});
733+
});
734+
696735
it('publish database (To-dos) and visit published link', () => {
697736
cy.on('uncaught:exception', (err: Error) => {
698737
if (err.message.includes('No workspace or service found') ||

cypress/support/selectors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export const ShareSelectors = {
177177
// Publish namespace and name inputs
178178
publishNamespace: () => cy.get(byTestId('publish-namespace')),
179179
publishNameInput: () => cy.get(byTestId('publish-name-input')),
180+
openPublishSettingsButton: () => cy.get(byTestId('open-publish-settings')),
180181

181182
// Page settings button
182183
pageSettingsButton: () => cy.get(byTestId('page-settings-button')),
@@ -195,6 +196,8 @@ export const ShareSelectors = {
195196

196197
// Visit Site button
197198
visitSiteButton: () => cy.get(byTestId('visit-site-button')),
199+
publishManageModal: () => cy.get(byTestId('publish-manage-modal')),
200+
publishManagePanel: () => cy.get(byTestId('publish-manage-panel')),
198201
};
199202

200203
/**

deploy/server.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ const logger = pino({
2626
options: {
2727
colorize: true,
2828
translateTime: 'SYS:standard',
29-
destination: `${__dirname}/pino-logger.log`,
3029
},
3130
},
32-
level: 'info',
31+
level: process.env.LOG_LEVEL || 'info',
3332
});
3433

3534
const logRequestTimer = (req: Request) => {
@@ -51,7 +50,7 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
5150
url = `${baseURL}/api/workspace/v1/published/${namespace}/${publishName}`;
5251
}
5352

54-
logger.info(`Fetching meta data from ${url}`);
53+
logger.debug(`Fetching meta data from ${url}`);
5554
try {
5655
const response = await fetch(url, {
5756
verbose: true,
@@ -63,11 +62,11 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
6362

6463
const data = await response.json();
6564

66-
logger.info(`Fetched meta data from ${url}: ${JSON.stringify(data)}`);
65+
logger.debug(`Fetched meta data from ${url}: ${JSON.stringify(data)}`);
6766

6867
return data;
6968
} catch (error) {
70-
logger.error(`Error fetching meta data ${error}`);
69+
logger.error(`Failed to fetch meta data from ${url}: ${error}`);
7170
return null;
7271
}
7372
};
@@ -130,31 +129,36 @@ const createServer = async (req: Request) => {
130129
}
131130

132131
let metaData;
132+
let redirectAttempted = false;
133133

134134
try {
135135
const data = await fetchMetaData(namespace, publishName);
136136

137137
if (publishName) {
138-
if (data.code === 0) {
138+
if (data && data.code === 0) {
139139
metaData = data.data;
140140
} else {
141-
logger.error(`Error fetching meta data: ${JSON.stringify(data)}`);
141+
logger.error(
142+
`Publish view lookup failed for namespace="${namespace}" publishName="${publishName}" response=${JSON.stringify(data)}`
143+
);
142144
}
143145
} else {
144-
145146
const publishInfo = data?.data?.info;
146147

147-
if (publishInfo) {
148+
if (publishInfo?.namespace && publishInfo?.publish_name) {
148149
const newURL = `/${encodeURIComponent(publishInfo.namespace)}/${encodeURIComponent(publishInfo.publish_name)}`;
149150

150151
logger.info(`Redirecting to default page in: ${JSON.stringify(publishInfo)}`);
152+
redirectAttempted = true;
151153
timer();
152154
return new Response(null, {
153155
status: 302,
154156
headers: {
155157
Location: newURL,
156158
},
157159
});
160+
} else {
161+
logger.warn(`Namespace "${namespace}" has no default publish page. response=${JSON.stringify(data)}`);
158162
}
159163
}
160164
} catch (error) {
@@ -219,6 +223,12 @@ const createServer = async (req: Request) => {
219223
logger.error(`Error injecting meta data: ${error}`);
220224
}
221225

226+
if (!metaData) {
227+
logger.warn(
228+
`Serving fallback landing page for namespace="${namespace}" publishName="${publishName ?? ''}". redirectAttempted=${redirectAttempted}`
229+
);
230+
}
231+
222232
$('title').text(title);
223233
$('link[rel="icon"]').attr('href', favicon);
224234
$('link[rel="canonical"]').attr('href', url);

src/components/app/publish-manage/PublishManage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export function PublishManage({ onClose }: { onClose?: () => void }) {
215215
const url = `${window.location.origin}/${namespace}`;
216216

217217
return (
218-
<div className={'flex flex-col gap-2'}>
218+
<div className={'flex flex-col gap-2'} data-testid='publish-manage-panel'>
219219
<div className={'px-1 text-base font-medium'}>{t('namespace')}</div>
220220
<div className={'px-1 text-xs text-text-secondary'}>{t('manageNamespaceDescription')}</div>
221221
<Divider className={'mb-2'} />

src/components/app/share/PublishLinkPreview.tsx

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { useTranslation } from 'react-i18next';
55
import { UpdatePublishConfigPayload } from '@/application/types';
66
import { ReactComponent as LinkIcon } from '@/assets/icons/link.svg';
77
import { ReactComponent as DownIcon } from '@/assets/icons/toggle_list.svg';
8-
import { NormalModal } from '@/components/_shared/modal';
98
import { notify } from '@/components/_shared/notify';
10-
import { PublishManage } from '@/components/app/publish-manage';
119
import { PublishNameSetting } from '@/components/app/publish-manage/PublishNameSetting';
1210
import { copyTextToClipboard } from '@/utils/copy';
1311

@@ -20,6 +18,7 @@ function PublishLinkPreview({
2018
isOwner,
2119
isPublisher,
2220
onClose,
21+
onOpenPublishManage,
2322
}: {
2423
viewId: string;
2524
publishInfo: { namespace: string; publishName: string };
@@ -29,8 +28,8 @@ function PublishLinkPreview({
2928
isOwner: boolean;
3029
isPublisher: boolean;
3130
onClose?: () => void;
31+
onOpenPublishManage?: () => void;
3232
}) {
33-
const [siteOpen, setSiteOpen] = React.useState<boolean>(false);
3433
const [renameOpen, setRenameOpen] = React.useState<boolean>(false);
3534
const { t } = useTranslation();
3635
const [publishName, setPublishName] = React.useState<string>(publishInfo.publishName);
@@ -78,8 +77,8 @@ function PublishLinkPreview({
7877
<IconButton
7978
size={'small'}
8079
onClick={() => {
81-
setSiteOpen(true);
8280
onClose?.();
81+
onOpenPublishManage?.();
8382
}}
8483
data-testid={'open-publish-settings'}
8584
>
@@ -161,32 +160,6 @@ function PublishLinkPreview({
161160
url={url}
162161
/>
163162
)}
164-
<NormalModal
165-
okButtonProps={{
166-
className: 'hidden',
167-
}}
168-
cancelButtonProps={{
169-
className: 'hidden',
170-
}}
171-
classes={{
172-
paper: 'w-[700px] appflowy-scroller max-w-[90vw] max-h-[90vh] h-[600px] overflow-hidden',
173-
}}
174-
overflowHidden
175-
onClose={() => {
176-
setSiteOpen(false);
177-
}}
178-
scroll={'paper'}
179-
open={siteOpen}
180-
title={<div className={'flex items-center justify-start'}>{t('settings.sites.title')}</div>}
181-
>
182-
<div className={'h-full w-full overflow-y-auto overflow-x-hidden'}>
183-
<PublishManage
184-
onClose={() => {
185-
setSiteOpen(false);
186-
}}
187-
/>
188-
</div>
189-
</NormalModal>
190163
</div>
191164
</>
192165
);

src/components/app/share/PublishPanel.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ import { useAppHandlers } from '@/components/app/app.hooks';
1313
import { useLoadPublishInfo } from '@/components/app/share/publish.hooks';
1414
import PublishLinkPreview from '@/components/app/share/PublishLinkPreview';
1515

16-
function PublishPanel({ viewId, opened, onClose }: { viewId: string; onClose: () => void; opened: boolean }) {
16+
function PublishPanel({
17+
viewId,
18+
opened,
19+
onClose,
20+
onOpenPublishManage,
21+
}: {
22+
viewId: string;
23+
onClose: () => void;
24+
opened: boolean;
25+
onOpenPublishManage?: () => void;
26+
}) {
1727
const { t } = useTranslation();
1828
const { publish, unpublish } = useAppHandlers();
1929
const { url, loadPublishInfo, view, publishInfo, loading, isOwner, isPublisher, updatePublishConfig } =
@@ -92,6 +102,7 @@ function PublishPanel({ viewId, opened, onClose }: { viewId: string; onClose: ()
92102
isOwner={isOwner}
93103
isPublisher={isPublisher}
94104
onClose={onClose}
105+
onOpenPublishManage={onOpenPublishManage}
95106
/>
96107
<div className={'flex w-full items-center justify-end gap-4'}>
97108
<Button
@@ -157,6 +168,7 @@ function PublishPanel({ viewId, opened, onClose }: { viewId: string; onClose: ()
157168
duplicateEnabled,
158169
updatePublishConfig,
159170
viewId,
171+
onOpenPublishManage,
160172
]);
161173

162174
const layout = view?.layout;

src/components/app/share/ShareButton.tsx

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,69 @@ import { useTranslation } from 'react-i18next';
44
import { ViewLayout } from '@/application/types';
55
import { useAppView } from '@/components/app/app.hooks';
66
import ShareTabs from '@/components/app/share/ShareTabs';
7+
import { PublishManage } from '@/components/app/publish-manage';
78
import { Button } from '@/components/ui/button';
89
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
10+
import { NormalModal } from '@/components/_shared/modal';
911

1012
export function ShareButton({ viewId }: { viewId: string }) {
1113
const { t } = useTranslation();
1214

1315
const view = useAppView(viewId);
1416
const layout = view?.layout;
1517
const [opened, setOpened] = React.useState(false);
18+
const [publishManageOpen, setPublishManageOpen] = React.useState(false);
1619

1720
if (layout === ViewLayout.AIChat) return null;
1821

1922
return (
20-
<Popover open={opened} onOpenChange={setOpened}>
21-
<PopoverTrigger asChild>
22-
<Button className={'mx-2'} data-testid={'share-button'} size={'sm'} variant={'default'}>
23-
{t('shareAction.buttonText')}
24-
</Button>
25-
</PopoverTrigger>
26-
<PopoverContent
27-
side='bottom'
28-
align='end'
29-
alignOffset={-20}
30-
className={'h-fit min-w-[480px] max-w-[480px]'}
31-
data-testid={'share-popover'}
23+
<>
24+
<Popover open={opened} onOpenChange={setOpened}>
25+
<PopoverTrigger asChild>
26+
<Button className={'mx-2'} data-testid={'share-button'} size={'sm'} variant={'default'}>
27+
{t('shareAction.buttonText')}
28+
</Button>
29+
</PopoverTrigger>
30+
<PopoverContent
31+
side='bottom'
32+
align='end'
33+
alignOffset={-20}
34+
className={'h-fit min-w-[480px] max-w-[480px]'}
35+
data-testid={'share-popover'}
36+
>
37+
<ShareTabs
38+
opened={opened}
39+
viewId={viewId}
40+
onClose={() => setOpened(false)}
41+
onOpenPublishManage={() => {
42+
setOpened(false);
43+
setPublishManageOpen(true);
44+
}}
45+
/>
46+
</PopoverContent>
47+
</Popover>
48+
<NormalModal
49+
data-testid='publish-manage-modal'
50+
open={publishManageOpen}
51+
onClose={() => setPublishManageOpen(false)}
52+
scroll='paper'
53+
overflowHidden
54+
okButtonProps={{
55+
className: 'hidden',
56+
}}
57+
cancelButtonProps={{
58+
className: 'hidden',
59+
}}
60+
classes={{
61+
paper: 'w-[700px] appflowy-scroller max-w-[90vw] max-h-[90vh] h-[600px] overflow-hidden',
62+
}}
63+
title={<div className={'flex items-center justify-start'}>{t('settings.sites.title')}</div>}
3264
>
33-
<ShareTabs opened={opened} viewId={viewId} onClose={() => setOpened(false)} />
34-
</PopoverContent>
35-
</Popover>
65+
<div className={'h-full w-full overflow-y-auto overflow-x-hidden'}>
66+
<PublishManage onClose={() => setPublishManageOpen(false)} />
67+
</div>
68+
</NormalModal>
69+
</>
3670
);
3771
}
3872

0 commit comments

Comments
 (0)