From 95a596e6511a845b9bd07afcf3423e24305609c6 Mon Sep 17 00:00:00 2001
From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com>
Date: Wed, 26 Nov 2025 15:50:40 +0800
Subject: [PATCH] Fix namespace popover (#172)
* fix: open publish manage modal after closing share popover
* chore: update log
---
cypress/e2e/page/publish-page.cy.ts | 39 +++++++++++
cypress/support/selectors.ts | 3 +
deploy/server.ts | 28 +++++---
.../app/publish-manage/PublishManage.tsx | 2 +-
.../app/share/PublishLinkPreview.tsx | 33 +---------
src/components/app/share/PublishPanel.tsx | 14 +++-
src/components/app/share/ShareButton.tsx | 64 ++++++++++++++-----
src/components/app/share/ShareTabs.tsx | 38 ++++++++---
vite.config.ts | 64 ++++++++++++++++++-
9 files changed, 220 insertions(+), 65 deletions(-)
diff --git a/cypress/e2e/page/publish-page.cy.ts b/cypress/e2e/page/publish-page.cy.ts
index 972255e1..6102d8b8 100644
--- a/cypress/e2e/page/publish-page.cy.ts
+++ b/cypress/e2e/page/publish-page.cy.ts
@@ -693,6 +693,45 @@ describe('Publish Page Test', () => {
});
});
+ it('opens publish manage modal from namespace caret and closes share popover first', () => {
+ cy.on('uncaught:exception', (err: Error) => {
+ if (err.message.includes('No workspace or service found') ||
+ err.message.includes('createThemeNoVars_default is not a function') ||
+ err.message.includes('View not found')) {
+ return false;
+ }
+ return true;
+ });
+
+ cy.visit('/login', { failOnStatusCode: false });
+ cy.wait(1000);
+ const authUtils = new AuthTestUtils();
+ authUtils.signInWithTestUrl(testEmail).then(() => {
+ cy.url().should('include', '/app');
+ SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
+ PageSelectors.names().should('exist', { timeout: 30000 });
+ cy.wait(2000);
+
+ TestTool.openSharePopover();
+ cy.contains('Publish').should('exist').click({ force: true });
+ cy.wait(1000);
+
+ ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true });
+ cy.wait(5000);
+ ShareSelectors.publishNamespace().should('be.visible', { timeout: 10000 });
+
+ ShareSelectors.sharePopover().should('exist');
+ ShareSelectors.openPublishSettingsButton().should('be.visible').click({ force: true });
+
+ ShareSelectors.sharePopover().should('not.exist');
+ ShareSelectors.publishManageModal().should('be.visible');
+ ShareSelectors.publishManagePanel().should('be.visible').contains('Namespace');
+
+ cy.get('body').type('{esc}');
+ ShareSelectors.publishManageModal().should('not.exist');
+ });
+ });
+
it('publish database (To-dos) and visit published link', () => {
cy.on('uncaught:exception', (err: Error) => {
if (err.message.includes('No workspace or service found') ||
diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts
index f8a1aab5..37fb4832 100644
--- a/cypress/support/selectors.ts
+++ b/cypress/support/selectors.ts
@@ -177,6 +177,7 @@ export const ShareSelectors = {
// Publish namespace and name inputs
publishNamespace: () => cy.get(byTestId('publish-namespace')),
publishNameInput: () => cy.get(byTestId('publish-name-input')),
+ openPublishSettingsButton: () => cy.get(byTestId('open-publish-settings')),
// Page settings button
pageSettingsButton: () => cy.get(byTestId('page-settings-button')),
@@ -195,6 +196,8 @@ export const ShareSelectors = {
// Visit Site button
visitSiteButton: () => cy.get(byTestId('visit-site-button')),
+ publishManageModal: () => cy.get(byTestId('publish-manage-modal')),
+ publishManagePanel: () => cy.get(byTestId('publish-manage-panel')),
};
/**
diff --git a/deploy/server.ts b/deploy/server.ts
index b6195826..36485c85 100644
--- a/deploy/server.ts
+++ b/deploy/server.ts
@@ -26,10 +26,9 @@ const logger = pino({
options: {
colorize: true,
translateTime: 'SYS:standard',
- destination: `${__dirname}/pino-logger.log`,
},
},
- level: 'info',
+ level: process.env.LOG_LEVEL || 'info',
});
const logRequestTimer = (req: Request) => {
@@ -51,7 +50,7 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
url = `${baseURL}/api/workspace/v1/published/${namespace}/${publishName}`;
}
- logger.info(`Fetching meta data from ${url}`);
+ logger.debug(`Fetching meta data from ${url}`);
try {
const response = await fetch(url, {
verbose: true,
@@ -63,11 +62,11 @@ const fetchMetaData = async (namespace: string, publishName?: string) => {
const data = await response.json();
- logger.info(`Fetched meta data from ${url}: ${JSON.stringify(data)}`);
+ logger.debug(`Fetched meta data from ${url}: ${JSON.stringify(data)}`);
return data;
} catch (error) {
- logger.error(`Error fetching meta data ${error}`);
+ logger.error(`Failed to fetch meta data from ${url}: ${error}`);
return null;
}
};
@@ -130,24 +129,27 @@ const createServer = async (req: Request) => {
}
let metaData;
+ let redirectAttempted = false;
try {
const data = await fetchMetaData(namespace, publishName);
if (publishName) {
- if (data.code === 0) {
+ if (data && data.code === 0) {
metaData = data.data;
} else {
- logger.error(`Error fetching meta data: ${JSON.stringify(data)}`);
+ logger.error(
+ `Publish view lookup failed for namespace="${namespace}" publishName="${publishName}" response=${JSON.stringify(data)}`
+ );
}
} else {
-
const publishInfo = data?.data?.info;
- if (publishInfo) {
+ if (publishInfo?.namespace && publishInfo?.publish_name) {
const newURL = `/${encodeURIComponent(publishInfo.namespace)}/${encodeURIComponent(publishInfo.publish_name)}`;
logger.info(`Redirecting to default page in: ${JSON.stringify(publishInfo)}`);
+ redirectAttempted = true;
timer();
return new Response(null, {
status: 302,
@@ -155,6 +157,8 @@ const createServer = async (req: Request) => {
Location: newURL,
},
});
+ } else {
+ logger.warn(`Namespace "${namespace}" has no default publish page. response=${JSON.stringify(data)}`);
}
}
} catch (error) {
@@ -219,6 +223,12 @@ const createServer = async (req: Request) => {
logger.error(`Error injecting meta data: ${error}`);
}
+ if (!metaData) {
+ logger.warn(
+ `Serving fallback landing page for namespace="${namespace}" publishName="${publishName ?? ''}". redirectAttempted=${redirectAttempted}`
+ );
+ }
+
$('title').text(title);
$('link[rel="icon"]').attr('href', favicon);
$('link[rel="canonical"]').attr('href', url);
diff --git a/src/components/app/publish-manage/PublishManage.tsx b/src/components/app/publish-manage/PublishManage.tsx
index b07442f5..e0092ec0 100644
--- a/src/components/app/publish-manage/PublishManage.tsx
+++ b/src/components/app/publish-manage/PublishManage.tsx
@@ -215,7 +215,7 @@ export function PublishManage({ onClose }: { onClose?: () => void }) {
const url = `${window.location.origin}/${namespace}`;
return (
-
+
{t('namespace')}
{t('manageNamespaceDescription')}
diff --git a/src/components/app/share/PublishLinkPreview.tsx b/src/components/app/share/PublishLinkPreview.tsx
index b410c7ce..3d6fc1df 100644
--- a/src/components/app/share/PublishLinkPreview.tsx
+++ b/src/components/app/share/PublishLinkPreview.tsx
@@ -5,9 +5,7 @@ import { useTranslation } from 'react-i18next';
import { UpdatePublishConfigPayload } from '@/application/types';
import { ReactComponent as LinkIcon } from '@/assets/icons/link.svg';
import { ReactComponent as DownIcon } from '@/assets/icons/toggle_list.svg';
-import { NormalModal } from '@/components/_shared/modal';
import { notify } from '@/components/_shared/notify';
-import { PublishManage } from '@/components/app/publish-manage';
import { PublishNameSetting } from '@/components/app/publish-manage/PublishNameSetting';
import { copyTextToClipboard } from '@/utils/copy';
@@ -20,6 +18,7 @@ function PublishLinkPreview({
isOwner,
isPublisher,
onClose,
+ onOpenPublishManage,
}: {
viewId: string;
publishInfo: { namespace: string; publishName: string };
@@ -29,8 +28,8 @@ function PublishLinkPreview({
isOwner: boolean;
isPublisher: boolean;
onClose?: () => void;
+ onOpenPublishManage?: () => void;
}) {
- const [siteOpen, setSiteOpen] = React.useState
(false);
const [renameOpen, setRenameOpen] = React.useState(false);
const { t } = useTranslation();
const [publishName, setPublishName] = React.useState(publishInfo.publishName);
@@ -78,8 +77,8 @@ function PublishLinkPreview({
{
- setSiteOpen(true);
onClose?.();
+ onOpenPublishManage?.();
}}
data-testid={'open-publish-settings'}
>
@@ -161,32 +160,6 @@ function PublishLinkPreview({
url={url}
/>
)}
- {
- setSiteOpen(false);
- }}
- scroll={'paper'}
- open={siteOpen}
- title={{t('settings.sites.title')}
}
- >
-
-
{
- setSiteOpen(false);
- }}
- />
-
-
>
);
diff --git a/src/components/app/share/PublishPanel.tsx b/src/components/app/share/PublishPanel.tsx
index 90aa088f..1bfaaccd 100644
--- a/src/components/app/share/PublishPanel.tsx
+++ b/src/components/app/share/PublishPanel.tsx
@@ -13,7 +13,17 @@ import { useAppHandlers } from '@/components/app/app.hooks';
import { useLoadPublishInfo } from '@/components/app/share/publish.hooks';
import PublishLinkPreview from '@/components/app/share/PublishLinkPreview';
-function PublishPanel({ viewId, opened, onClose }: { viewId: string; onClose: () => void; opened: boolean }) {
+function PublishPanel({
+ viewId,
+ opened,
+ onClose,
+ onOpenPublishManage,
+}: {
+ viewId: string;
+ onClose: () => void;
+ opened: boolean;
+ onOpenPublishManage?: () => void;
+}) {
const { t } = useTranslation();
const { publish, unpublish } = useAppHandlers();
const { url, loadPublishInfo, view, publishInfo, loading, isOwner, isPublisher, updatePublishConfig } =
@@ -92,6 +102,7 @@ function PublishPanel({ viewId, opened, onClose }: { viewId: string; onClose: ()
isOwner={isOwner}
isPublisher={isPublisher}
onClose={onClose}
+ onOpenPublishManage={onOpenPublishManage}
/>