@lark-base-open/base-database-cli 1.1.1 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/LICENSE +6 -0
- package/README.md +47 -27
- package/dist/ai-functions.d.ts +53 -0
- package/dist/ai-functions.d.ts.map +1 -0
- package/dist/ai-functions.js +436 -0
- package/dist/ai-functions.js.map +1 -0
- package/dist/archive.d.ts +21 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +69 -10
- package/dist/archive.js.map +1 -1
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +26 -3
- package/dist/auth.js.map +1 -1
- package/dist/check.d.ts +74 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +283 -0
- package/dist/check.js.map +1 -0
- package/dist/dependency-policy.d.ts +10 -0
- package/dist/dependency-policy.d.ts.map +1 -0
- package/dist/dependency-policy.js +162 -0
- package/dist/dependency-policy.js.map +1 -0
- package/dist/deploy.d.ts +5 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +141 -176
- package/dist/deploy.js.map +1 -1
- package/dist/environment-variables.d.ts +52 -0
- package/dist/environment-variables.d.ts.map +1 -0
- package/dist/environment-variables.js +563 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/environment.js.map +1 -1
- package/dist/function-input-types.d.ts +10 -0
- package/dist/function-input-types.d.ts.map +1 -0
- package/dist/function-input-types.js +160 -0
- package/dist/function-input-types.js.map +1 -0
- package/dist/function-schema.d.ts.map +1 -1
- package/dist/function-schema.js +89 -36
- package/dist/function-schema.js.map +1 -1
- package/dist/init-field-retry.d.ts +17 -0
- package/dist/init-field-retry.d.ts.map +1 -0
- package/dist/init-field-retry.js +127 -0
- package/dist/init-field-retry.js.map +1 -0
- package/dist/init-journal.d.ts +98 -0
- package/dist/init-journal.d.ts.map +1 -0
- package/dist/init-journal.js +492 -0
- package/dist/init-journal.js.map +1 -0
- package/dist/init-schema.d.ts +8 -0
- package/dist/init-schema.d.ts.map +1 -0
- package/dist/init-schema.js +423 -0
- package/dist/init-schema.js.map +1 -0
- package/dist/init.d.ts +15 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +884 -261
- package/dist/init.js.map +1 -1
- package/dist/lark-cli-error.d.ts +47 -0
- package/dist/lark-cli-error.d.ts.map +1 -0
- package/dist/lark-cli-error.js +201 -0
- package/dist/lark-cli-error.js.map +1 -0
- package/dist/lark-cli.d.ts +1 -1
- package/dist/lark-cli.d.ts.map +1 -1
- package/dist/lark-cli.js +78 -18
- package/dist/lark-cli.js.map +1 -1
- package/dist/mutation-schema.d.ts +146 -0
- package/dist/mutation-schema.d.ts.map +1 -0
- package/dist/mutation-schema.js +153 -0
- package/dist/mutation-schema.js.map +1 -0
- package/dist/mutation.d.ts +10 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +131 -0
- package/dist/mutation.js.map +1 -0
- package/dist/npm.d.ts +1 -0
- package/dist/npm.d.ts.map +1 -1
- package/dist/npm.js +2 -2
- package/dist/npm.js.map +1 -1
- package/dist/preview-receipt.d.ts.map +1 -1
- package/dist/preview-receipt.js +15 -5
- package/dist/preview-receipt.js.map +1 -1
- package/dist/process.d.ts +3 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +16 -4
- package/dist/process.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +246 -42
- package/dist/program.js.map +1 -1
- package/dist/project-build.d.ts +25 -0
- package/dist/project-build.d.ts.map +1 -0
- package/dist/project-build.js +229 -0
- package/dist/project-build.js.map +1 -0
- package/dist/project-context.d.ts +344 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +485 -0
- package/dist/project-context.js.map +1 -0
- package/dist/project-layout.d.ts +5 -0
- package/dist/project-layout.d.ts.map +1 -0
- package/dist/project-layout.js +24 -0
- package/dist/project-layout.js.map +1 -0
- package/dist/public-sql-error.d.ts +25 -0
- package/dist/public-sql-error.d.ts.map +1 -0
- package/dist/public-sql-error.js +65 -0
- package/dist/public-sql-error.js.map +1 -0
- package/dist/pull.d.ts +1 -0
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +67 -15
- package/dist/pull.js.map +1 -1
- package/dist/site-readme.d.ts +3 -0
- package/dist/site-readme.d.ts.map +1 -0
- package/dist/site-readme.js +83 -0
- package/dist/site-readme.js.map +1 -0
- package/dist/workspace-fingerprint.d.ts +21 -1
- package/dist/workspace-fingerprint.d.ts.map +1 -1
- package/dist/workspace-fingerprint.js +81 -2
- package/dist/workspace-fingerprint.js.map +1 -1
- package/package.json +2 -1
- package/skills/lark-base-database/SKILL.md +86 -52
- package/skills/lark-base-database/references/ai-function.md +479 -0
- package/skills/lark-base-database/references/base-capability-routing.md +7 -7
- package/skills/lark-base-database/references/base-sql-errors.generated.md +403 -0
- package/skills/lark-base-database/references/database-function-limitations.md +29 -0
- package/skills/lark-base-database/references/database-function.md +323 -0
- package/skills/lark-base-database/references/development.md +166 -33
- package/skills/lark-base-database/references/environment-variables.md +55 -0
- package/skills/lark-base-database/references/faas.md +46 -53
- package/skills/lark-base-database/references/file-transfer.md +25 -25
- package/skills/lark-base-database/references/frontend-design-guide.md +151 -65
- package/skills/lark-base-database/references/mutations.md +295 -0
- package/skills/lark-base-database/references/notifications.md +9 -9
- package/skills/lark-base-database/references/permission-design.md +24 -24
- package/skills/lark-base-database/references/release.md +57 -30
- package/skills/lark-base-database/references/style-guide.md +10 -67
- package/skills/lark-base-database/references/verification.md +19 -13
- package/skills/lark-base-database/references/workspace.md +57 -17
- package/templates/database-starter/README.md +34 -4
- package/templates/database-starter/database-functions/README.md +8 -0
- package/templates/database-starter/gitignore +7 -0
- package/templates/database-starter/mutations/README.md +19 -0
- package/templates/database-starter/page/README.md +25 -19
- package/templates/database-starter/page/package-lock.json +8 -16
- package/templates/database-starter/page/package.json +2 -3
- package/templates/database-starter/page/src/App.tsx +23 -3
- package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
- package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
- package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
- package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
- package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
- package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/toast-close.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/toast-info.svg +5 -0
- package/templates/database-starter/page/src/components/floating-button/assets/tooltip-caret.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/index.tsx +193 -113
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
- package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
- package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
- package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
- package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
- package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
- package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
- package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
- package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
- package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
- package/templates/database-starter/page/src/lib/date-time.ts +61 -0
- package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
- package/templates/database-starter/page/src/main.tsx +1 -1
- package/templates/database-starter/page/tsconfig.json +2 -2
- package/templates/database-starter/page/vite.config.ts +1 -2
- package/templates/database-starter/prettierignore +1 -1
- package/templates/database-starter/server-functions/README.md +2 -2
- package/templates/database-starter/server-functions/package-lock.json +4 -4
- package/templates/database-starter/server-functions/package.json +1 -1
- package/dist/agent-guide.d.ts +0 -19
- package/dist/agent-guide.d.ts.map +0 -1
- package/dist/agent-guide.js +0 -31
- package/dist/agent-guide.js.map +0 -1
- package/dist/database-skill.d.ts +0 -24
- package/dist/database-skill.d.ts.map +0 -1
- package/dist/database-skill.js +0 -148
- package/dist/database-skill.js.map +0 -1
- package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
- package/skills/lark-base-database/references/database-capability-skill.md +0 -40
- package/skills/lark-base-database/references/sql-function.md +0 -289
- package/templates/database-capability/SKILL.md.tmpl +0 -58
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
- package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
- package/templates/database-starter/sql/README.md +0 -8
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M13.5043 13.5042C13.7646 13.2439 13.7665 12.8237 13.5062 12.5633L8.94298 8.00013L13.5061 3.43697C13.7665 3.17662 13.7646 2.7564 13.5043 2.49605C13.2439 2.2357 12.8237 2.23382 12.5633 2.49416C12.303 2.75451 8.00017 7.05732 8.00017 7.05732L3.43697 2.49413C3.17662 2.23378 2.7564 2.23566 2.49605 2.49601C2.2357 2.75636 2.23381 3.17658 2.49416 3.43693L7.05736 8.00013L2.49413 12.5634C2.23378 12.8237 2.23566 13.2439 2.49601 13.5043C2.75636 13.7646 3.17659 13.7665 3.43693 13.5062L8.00017 8.94294L12.5634 13.5061C12.8237 13.7665 13.2439 13.7646 13.5043 13.5042Z" fill="#646A73"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle cx="8.00008" cy="8.00033" r="7.33333" fill="#1456F0"/>
|
|
3
|
+
<path d="M8 5.33333C8.36819 5.33333 8.66667 5.03486 8.66667 4.66667C8.66667 4.29848 8.36819 4 8 4C7.63181 4 7.33333 4.29848 7.33333 4.66667C7.33333 5.03486 7.63181 5.33333 8 5.33333Z" fill="white"/>
|
|
4
|
+
<path d="M7.33333 6C6.96514 6 6.66667 6.29848 6.66667 6.66667C6.66667 7.03486 6.96514 7.33333 7.33333 7.33333V10H6.66667C6.29848 10 6 10.2985 6 10.6667C6 11.0349 6.29848 11.3333 6.66667 11.3333H9.33333C9.70152 11.3333 10 11.0349 10 10.6667C10 10.2985 9.70152 10 9.33333 10H8.66667V6.66667C8.66667 6.29848 8.36819 6 8 6H7.33333Z" fill="white"/>
|
|
5
|
+
</svg>
|
package/templates/database-starter/page/src/components/floating-button/assets/tooltip-caret.svg
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="6" viewBox="0 0 24 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M8.4383 1.99309L11.2526 5.15916C11.6504 5.60668 12.3496 5.60668 12.7474 5.15916L15.5617 1.99309C16.6886 0.725344 18.3038 0 20 0L12 0L4 0C5.69619 0 7.31142 0.725345 8.4383 1.99309Z" fill="#1F2329"/>
|
|
3
|
+
</svg>
|
|
@@ -1,84 +1,131 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import editDefaultIcon from './assets/edit-default.svg'
|
|
9
|
+
import editActiveIcon from './assets/edit-active.svg'
|
|
10
|
+
import tooltipCaretIcon from './assets/tooltip-caret.svg'
|
|
11
|
+
import toastInfoIcon from './assets/toast-info.svg'
|
|
12
|
+
import toastCloseIcon from './assets/toast-close.svg'
|
|
2
13
|
import { fetchProjectMeta, type ProjectMeta } from './utils/project-meta'
|
|
3
|
-
import { ChatbotEntry, resolveChatbotDisplayName } from './chatbot-entry'
|
|
4
14
|
import {
|
|
5
15
|
getDatabaseContext,
|
|
6
16
|
resolveDatabaseContext,
|
|
7
17
|
siteRuntime,
|
|
8
18
|
} from '../../lib/database-runtime'
|
|
9
|
-
|
|
10
|
-
export type FloatingButtonLanguage = 'en' | 'zh'
|
|
11
|
-
|
|
12
|
-
interface FloatingButtonProps {
|
|
13
|
-
language?: FloatingButtonLanguage
|
|
14
|
-
showChatbotEntry?: boolean
|
|
15
|
-
}
|
|
19
|
+
import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
|
|
16
20
|
|
|
17
21
|
interface FloatingButtonI18n {
|
|
18
22
|
editSite: string
|
|
23
|
+
hidePanel: string
|
|
24
|
+
hiddenConfirmation: string
|
|
25
|
+
dismissNotification: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TooltipPosition {
|
|
29
|
+
left: number
|
|
30
|
+
top: number
|
|
31
|
+
arrowLeft: number
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
const BASE_DATABASE_OVERVIEW_URL = 'https://feishu.cn/base/site/detail/overview'
|
|
22
|
-
const CHATBOT_SIDEBAR_DEFAULT_WIDTH = 420
|
|
23
35
|
const FLOATING_BUTTON_PAGE_GAP = 24
|
|
36
|
+
const TOOLTIP_VIEWPORT_GAP = 8
|
|
37
|
+
const HIDDEN_CONFIRMATION_MS = 3000
|
|
24
38
|
const EMPTY_PROJECT_META: ProjectMeta = {
|
|
25
39
|
baseBindings: [],
|
|
26
|
-
chatList: [],
|
|
27
40
|
description: '',
|
|
28
41
|
isOwner: false,
|
|
42
|
+
allowEdit: false,
|
|
29
43
|
name: '',
|
|
30
44
|
organization: '',
|
|
31
45
|
}
|
|
32
|
-
const FLOATING_BUTTON_I18N: Record<
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
const FLOATING_BUTTON_I18N: Record<UiLanguage, FloatingButtonI18n> = {
|
|
47
|
+
en: {
|
|
48
|
+
editSite: 'Edit and manage your apps',
|
|
49
|
+
hidePanel: 'Hide until next visit',
|
|
50
|
+
hiddenConfirmation: 'Edit button hidden until you reopen the app',
|
|
51
|
+
dismissNotification: 'Dismiss notification',
|
|
52
|
+
},
|
|
53
|
+
zh: {
|
|
54
|
+
editSite: '编辑与管理应用',
|
|
55
|
+
hidePanel: '隐藏面板',
|
|
56
|
+
hiddenConfirmation: '编辑按钮已隐藏,直至下次重新打开应用',
|
|
57
|
+
dismissNotification: '关闭提示',
|
|
58
|
+
},
|
|
59
|
+
ja: {
|
|
60
|
+
editSite: 'アプリを編集・管理',
|
|
61
|
+
hidePanel: '次回のアクセスまで非表示',
|
|
62
|
+
hiddenConfirmation:
|
|
63
|
+
'編集ボタンを非表示にしました。アプリを再度開くと表示されます',
|
|
64
|
+
dismissNotification: '通知を閉じる',
|
|
65
|
+
},
|
|
66
|
+
}
|
|
41
67
|
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
* 原来这里还有一个「三个点」菜单,承载当前账号 / 组织 / 退出登录。它已经下掉了:
|
|
45
|
-
* 悬浮控件是应用之外的一层浮层,用户不会去那里找账号。身份信息改由应用自己的导航
|
|
46
|
-
* 承载,见 `page/src/components/database-user-menu`。
|
|
47
|
-
*
|
|
48
|
-
* @param props.language 界面语言,支持 en 和 zh,默认 en。
|
|
49
|
-
* @param props.showChatbotEntry 是否显示 Chatbot 入口,默认 true。
|
|
50
|
-
* @returns 悬浮按钮组。
|
|
68
|
+
/** 渲染有管理权限的访问者在正式环境中的编辑入口。
|
|
69
|
+
* @returns 编辑按钮;Preview 或无管理权限时不显示。
|
|
51
70
|
*/
|
|
52
|
-
export function FloatingButton({
|
|
53
|
-
language = 'en',
|
|
54
|
-
showChatbotEntry = true,
|
|
55
|
-
}: FloatingButtonProps) {
|
|
71
|
+
export function FloatingButton() {
|
|
56
72
|
const [projectMeta, setProjectMeta] = useState<ProjectMeta | null>(null)
|
|
57
|
-
const [
|
|
58
|
-
const [
|
|
59
|
-
const [
|
|
60
|
-
|
|
73
|
+
const [isHidden, setIsHidden] = useState(false)
|
|
74
|
+
const [showHiddenConfirmation, setShowHiddenConfirmation] = useState(false)
|
|
75
|
+
const [activeTooltip, setActiveTooltip] = useState<'edit' | 'hide' | null>(
|
|
76
|
+
null,
|
|
61
77
|
)
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
const [tooltipPosition, setTooltipPosition] =
|
|
79
|
+
useState<TooltipPosition | null>(null)
|
|
80
|
+
const editButtonRef = useRef<HTMLButtonElement>(null)
|
|
81
|
+
const hideButtonRef = useRef<HTMLButtonElement>(null)
|
|
82
|
+
const tooltipRef = useRef<HTMLDivElement>(null)
|
|
83
|
+
// Preview 不展示返回编辑态入口;按钮显隐不替代宿主点击时的鉴权。
|
|
64
84
|
const shouldShowEditButton =
|
|
65
|
-
projectMeta?.
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
projectMeta?.allowEdit === true && getDatabaseContext().env === 'prod'
|
|
86
|
+
const i18n = FLOATING_BUTTON_I18N[useUiLanguage()]
|
|
87
|
+
|
|
88
|
+
useLayoutEffect(() => {
|
|
89
|
+
if (!activeTooltip) return
|
|
90
|
+
|
|
91
|
+
const updateTooltipPosition = () => {
|
|
92
|
+
const trigger =
|
|
93
|
+
activeTooltip === 'edit' ? editButtonRef.current : hideButtonRef.current
|
|
94
|
+
const tooltip = tooltipRef.current
|
|
95
|
+
if (!trigger || !tooltip) return
|
|
96
|
+
|
|
97
|
+
const triggerRect = trigger.getBoundingClientRect()
|
|
98
|
+
const tooltipWidth = tooltip.offsetWidth
|
|
99
|
+
const left = Math.max(
|
|
100
|
+
TOOLTIP_VIEWPORT_GAP,
|
|
101
|
+
Math.min(
|
|
102
|
+
triggerRect.left + triggerRect.width / 2 - tooltipWidth / 2,
|
|
103
|
+
window.innerWidth - tooltipWidth - TOOLTIP_VIEWPORT_GAP,
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
setTooltipPosition({
|
|
107
|
+
left,
|
|
108
|
+
top: triggerRect.top - tooltip.offsetHeight - 14,
|
|
109
|
+
arrowLeft: triggerRect.left + triggerRect.width / 2 - left,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
updateTooltipPosition()
|
|
114
|
+
window.addEventListener('resize', updateTooltipPosition)
|
|
115
|
+
return () => window.removeEventListener('resize', updateTooltipPosition)
|
|
116
|
+
}, [activeTooltip, i18n])
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!showHiddenConfirmation) return
|
|
120
|
+
const timeout = window.setTimeout(
|
|
121
|
+
() => setShowHiddenConfirmation(false),
|
|
122
|
+
HIDDEN_CONFIRMATION_MS,
|
|
123
|
+
)
|
|
124
|
+
return () => window.clearTimeout(timeout)
|
|
125
|
+
}, [showHiddenConfirmation])
|
|
78
126
|
|
|
79
127
|
useEffect(() => {
|
|
80
128
|
let isCurrentRequest = true
|
|
81
|
-
setIsFloatingDataLoaded(false)
|
|
82
129
|
|
|
83
130
|
/** 读取悬浮控件所需的项目资源数据。 */
|
|
84
131
|
async function loadFloatingData(): Promise<void> {
|
|
@@ -91,7 +138,6 @@ export function FloatingButton({
|
|
|
91
138
|
}
|
|
92
139
|
|
|
93
140
|
setProjectMeta(nextProjectMeta)
|
|
94
|
-
setIsFloatingDataLoaded(true)
|
|
95
141
|
}
|
|
96
142
|
|
|
97
143
|
void loadFloatingData()
|
|
@@ -101,18 +147,6 @@ export function FloatingButton({
|
|
|
101
147
|
}
|
|
102
148
|
}, [])
|
|
103
149
|
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
if (showChatbotEntry) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
setIsChatbotOpen(false)
|
|
110
|
-
}, [showChatbotEntry])
|
|
111
|
-
|
|
112
|
-
const handleChatbotOpenChange = useCallback((nextIsOpen: boolean) => {
|
|
113
|
-
setIsChatbotOpen(nextIsOpen)
|
|
114
|
-
}, [])
|
|
115
|
-
|
|
116
150
|
// 编辑按钮走 site.v1 契约:应用发 request-edit,宿主切编辑态后广播 mode-changed。
|
|
117
151
|
// 应用被独立访问(无宿主)时事件无人消费,回落为打开 Site 管理页。
|
|
118
152
|
const requestEdit = useCallback(() => {
|
|
@@ -124,41 +158,109 @@ export function FloatingButton({
|
|
|
124
158
|
})
|
|
125
159
|
}, [])
|
|
126
160
|
|
|
127
|
-
if (!
|
|
161
|
+
if (!shouldShowEditButton) {
|
|
128
162
|
return null
|
|
129
163
|
}
|
|
130
164
|
|
|
165
|
+
if (isHidden) {
|
|
166
|
+
return showHiddenConfirmation ? (
|
|
167
|
+
<div
|
|
168
|
+
role="status"
|
|
169
|
+
className="fixed top-8 left-1/2 z-50 flex min-h-[54px] w-[352px] max-w-[calc(100vw-32px)] -translate-x-1/2 items-center gap-2 rounded-md bg-[#f0f4ff] px-5 py-4 text-sm leading-[22px] font-medium text-[#1f2329]"
|
|
170
|
+
style={{
|
|
171
|
+
// Figma 的内描边与三层阴影需合并,避免 ring 被行内 box-shadow 覆盖。
|
|
172
|
+
boxShadow:
|
|
173
|
+
'inset 0 0 0 1px #1456f0, 0 8px 24px 8px rgb(31 35 41 / 4%), 0 6px 12px rgb(31 35 41 / 4%), 0 4px 8px -8px rgb(31 35 41 / 6%)',
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
<img src={toastInfoIcon} alt="" className="size-4 shrink-0" />
|
|
177
|
+
<span className="min-w-0 flex-1 pl-1 pr-2">
|
|
178
|
+
{i18n.hiddenConfirmation}
|
|
179
|
+
</span>
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
aria-label={i18n.dismissNotification}
|
|
183
|
+
className="flex size-4 shrink-0 cursor-pointer items-center justify-center"
|
|
184
|
+
onClick={() => setShowHiddenConfirmation(false)}
|
|
185
|
+
>
|
|
186
|
+
<img src={toastCloseIcon} alt="" className="size-4" />
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
) : null
|
|
190
|
+
}
|
|
191
|
+
|
|
131
192
|
return (
|
|
132
193
|
<div
|
|
133
|
-
className=
|
|
134
|
-
style={{ right:
|
|
194
|
+
className="group fixed bottom-10 z-40 flex items-center"
|
|
195
|
+
style={{ right: FLOATING_BUTTON_PAGE_GAP }}
|
|
135
196
|
>
|
|
197
|
+
{activeTooltip ? (
|
|
198
|
+
<div
|
|
199
|
+
ref={tooltipRef}
|
|
200
|
+
role="tooltip"
|
|
201
|
+
className="pointer-events-none fixed z-50 w-max max-w-[calc(100vw-16px)] rounded-lg bg-[#1f2329] px-3 py-2 text-xs leading-5 whitespace-normal text-white"
|
|
202
|
+
style={{
|
|
203
|
+
boxShadow:
|
|
204
|
+
'0 8px 24px 8px rgb(31 35 41 / 4%), 0 6px 12px rgb(31 35 41 / 4%), 0 4px 8px -8px rgb(31 35 41 / 6%)',
|
|
205
|
+
...(tooltipPosition
|
|
206
|
+
? { left: tooltipPosition.left, top: tooltipPosition.top }
|
|
207
|
+
: { visibility: 'hidden' as const }),
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{activeTooltip === 'edit' ? i18n.editSite : i18n.hidePanel}
|
|
211
|
+
<img
|
|
212
|
+
src={tooltipCaretIcon}
|
|
213
|
+
alt=""
|
|
214
|
+
className="pointer-events-none absolute top-full h-1.5 w-6 -translate-x-1/2"
|
|
215
|
+
style={{ left: tooltipPosition?.arrowLeft }}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
) : null}
|
|
136
219
|
<div
|
|
137
|
-
className=
|
|
220
|
+
className="relative flex items-center rounded-full bg-white p-0.5 shadow-[0_4px_4px_rgba(31,35,41,0.06),0_6px_6px_rgba(31,35,41,0.04),0_8px_12px_rgba(31,35,41,0.04)]"
|
|
138
221
|
data-floating-controls="true"
|
|
139
222
|
>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
223
|
+
<button
|
|
224
|
+
ref={editButtonRef}
|
|
225
|
+
type="button"
|
|
226
|
+
aria-label={i18n.editSite}
|
|
227
|
+
className="group/edit flex size-12 cursor-pointer items-center justify-center rounded-full bg-transparent outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
228
|
+
onMouseEnter={() => setActiveTooltip('edit')}
|
|
229
|
+
onMouseLeave={() => setActiveTooltip(null)}
|
|
230
|
+
onFocus={() => setActiveTooltip('edit')}
|
|
231
|
+
onBlur={() => setActiveTooltip(null)}
|
|
232
|
+
onClick={requestEdit}
|
|
233
|
+
>
|
|
234
|
+
<img
|
|
235
|
+
src={editDefaultIcon}
|
|
236
|
+
alt=""
|
|
237
|
+
className="size-5 group-hover/edit:hidden group-focus-visible/edit:hidden"
|
|
238
|
+
/>
|
|
239
|
+
<img
|
|
240
|
+
src={editActiveIcon}
|
|
241
|
+
alt=""
|
|
242
|
+
className="hidden size-5 group-hover/edit:block group-focus-visible/edit:block"
|
|
160
243
|
/>
|
|
161
|
-
|
|
244
|
+
</button>
|
|
245
|
+
<button
|
|
246
|
+
ref={hideButtonRef}
|
|
247
|
+
type="button"
|
|
248
|
+
aria-label={i18n.hidePanel}
|
|
249
|
+
className="absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full bg-[#c8c9cc] text-[#646a73] opacity-0 transition-opacity group-hover:opacity-100 hover:bg-[#b7b9bd] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[#1456f0]"
|
|
250
|
+
onMouseEnter={() => setActiveTooltip('hide')}
|
|
251
|
+
onMouseLeave={() => setActiveTooltip(null)}
|
|
252
|
+
onFocus={() => setActiveTooltip('hide')}
|
|
253
|
+
onBlur={() => setActiveTooltip(null)}
|
|
254
|
+
onClick={() => {
|
|
255
|
+
setIsHidden(true)
|
|
256
|
+
setActiveTooltip(null)
|
|
257
|
+
setShowHiddenConfirmation(true)
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
<span aria-hidden="true" className="text-[14px] leading-none">
|
|
261
|
+
×
|
|
262
|
+
</span>
|
|
263
|
+
</button>
|
|
162
264
|
</div>
|
|
163
265
|
</div>
|
|
164
266
|
)
|
|
@@ -171,26 +273,4 @@ function openExternalUrl(url: string): void {
|
|
|
171
273
|
window.open(url, '_blank', 'noopener,noreferrer')
|
|
172
274
|
}
|
|
173
275
|
|
|
174
|
-
/** 编辑应用图标。
|
|
175
|
-
* @returns 铅笔图标节点。
|
|
176
|
-
*/
|
|
177
|
-
function EditIcon() {
|
|
178
|
-
return (
|
|
179
|
-
<svg
|
|
180
|
-
aria-hidden="true"
|
|
181
|
-
width="20"
|
|
182
|
-
height="20"
|
|
183
|
-
viewBox="0 0 24 24"
|
|
184
|
-
fill="none"
|
|
185
|
-
stroke="currentColor"
|
|
186
|
-
strokeWidth="1.8"
|
|
187
|
-
strokeLinecap="round"
|
|
188
|
-
strokeLinejoin="round"
|
|
189
|
-
>
|
|
190
|
-
<path d="M12 20h9" />
|
|
191
|
-
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
|
|
192
|
-
</svg>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
276
|
export default FloatingButton
|
package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { parseProjectMetaResponse } from './project-meta'
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { fetchProjectMeta, parseProjectMetaResponse } from './project-meta'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const runtime = vi.hoisted(() => ({
|
|
5
|
+
fetch: vi.fn(),
|
|
6
|
+
resolveDatabaseContext: vi.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
vi.mock('../../../lib/database-runtime', async (importOriginal) => ({
|
|
10
|
+
...(await importOriginal<object>()),
|
|
11
|
+
...runtime,
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.unstubAllGlobals()
|
|
16
|
+
vi.clearAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function projectResponse(isOwner: unknown, allowEdit?: unknown) {
|
|
5
20
|
return {
|
|
6
21
|
code: 0,
|
|
7
22
|
data: {
|
|
8
23
|
isOwner,
|
|
9
24
|
site: {
|
|
10
25
|
baseBindings: [],
|
|
11
|
-
chatbots: [],
|
|
12
26
|
description: 'Owner visibility test',
|
|
13
27
|
name: 'Owner visibility test',
|
|
28
|
+
...(allowEdit === undefined ? {} : { allowEdit }),
|
|
14
29
|
},
|
|
15
30
|
},
|
|
16
31
|
}
|
|
@@ -32,48 +47,103 @@ describe('project owner visibility', () => {
|
|
|
32
47
|
})
|
|
33
48
|
})
|
|
34
49
|
|
|
35
|
-
describe('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
describe('edit entry permission', () => {
|
|
51
|
+
it('shows admins and owners when the server allows editing', () => {
|
|
52
|
+
expect(
|
|
53
|
+
parseProjectMetaResponse(projectResponse(false, true)).allowEdit,
|
|
54
|
+
).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('respects explicit denial and falls back to owner only for old responses', () => {
|
|
58
|
+
expect(
|
|
59
|
+
parseProjectMetaResponse(projectResponse(true, false)).allowEdit,
|
|
60
|
+
).toBe(false)
|
|
61
|
+
expect(parseProjectMetaResponse(projectResponse(true)).allowEdit).toBe(true)
|
|
62
|
+
expect(parseProjectMetaResponse(projectResponse(false)).allowEdit).toBe(
|
|
63
|
+
false,
|
|
64
|
+
)
|
|
65
|
+
expect(
|
|
66
|
+
parseProjectMetaResponse(projectResponse(false, 'true')).allowEdit,
|
|
67
|
+
).toBe(false)
|
|
68
|
+
expect(
|
|
69
|
+
parseProjectMetaResponse(projectResponse(true, 'true')).allowEdit,
|
|
70
|
+
).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('project metadata', () => {
|
|
75
|
+
it('preserves page identity and valid Base bindings', () => {
|
|
76
|
+
const meta = parseProjectMetaResponse({
|
|
39
77
|
code: 0,
|
|
40
78
|
data: {
|
|
41
79
|
isOwner: true,
|
|
42
80
|
site: {
|
|
43
|
-
|
|
44
|
-
chatbots,
|
|
81
|
+
name: ' 应用名称 ',
|
|
45
82
|
description: '应用描述',
|
|
46
|
-
|
|
83
|
+
organization: '所属组织',
|
|
84
|
+
baseBindings: [
|
|
85
|
+
null,
|
|
86
|
+
{ tableIds: ['missing-token'] },
|
|
87
|
+
{ baseToken: ' bascnBase ', tableIds: ['tbl_a', 42] },
|
|
88
|
+
],
|
|
47
89
|
},
|
|
48
90
|
},
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
91
|
+
})
|
|
92
|
+
expect(meta).toEqual({
|
|
93
|
+
isOwner: true,
|
|
94
|
+
allowEdit: true,
|
|
95
|
+
name: '应用名称',
|
|
96
|
+
description: '应用描述',
|
|
97
|
+
organization: '所属组织',
|
|
98
|
+
baseBindings: [{ baseToken: 'bascnBase', tableIds: ['tbl_a'] }],
|
|
99
|
+
})
|
|
100
|
+
})
|
|
56
101
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(
|
|
102
|
+
it.each([
|
|
103
|
+
null,
|
|
104
|
+
{ code: 1 },
|
|
105
|
+
{ code: 0, data: { site: null, isOwner: true } },
|
|
106
|
+
])('fails closed for an invalid response', (response) => {
|
|
107
|
+
expect(parseProjectMetaResponse(response)).toEqual({
|
|
108
|
+
isOwner: false,
|
|
109
|
+
allowEdit: false,
|
|
110
|
+
name: '',
|
|
111
|
+
description: '',
|
|
112
|
+
organization: '',
|
|
113
|
+
baseBindings: [],
|
|
114
|
+
})
|
|
63
115
|
})
|
|
64
116
|
|
|
65
|
-
it('
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
it.each(['prod', 'preview'])(
|
|
118
|
+
'fetches only application metadata in %s',
|
|
119
|
+
async (env) => {
|
|
120
|
+
runtime.resolveDatabaseContext.mockResolvedValue({
|
|
121
|
+
env,
|
|
122
|
+
gatewayOrigin: '',
|
|
123
|
+
token: 'meta_test',
|
|
124
|
+
})
|
|
125
|
+
runtime.fetch.mockResolvedValue({
|
|
126
|
+
ok: true,
|
|
127
|
+
json: async () => projectResponse(true),
|
|
128
|
+
})
|
|
129
|
+
class MetaElement {
|
|
130
|
+
content = ''
|
|
131
|
+
}
|
|
132
|
+
const description = new MetaElement()
|
|
133
|
+
const document = { title: '', querySelector: vi.fn(() => description) }
|
|
134
|
+
vi.stubGlobal('document', document)
|
|
135
|
+
vi.stubGlobal('HTMLMetaElement', MetaElement)
|
|
73
136
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
137
|
+
const meta = await fetchProjectMeta()
|
|
138
|
+
expect(runtime.fetch).toHaveBeenCalledWith(
|
|
139
|
+
`/space/api/bitable/site/v1/meta_test/invoke_get${env === 'preview' ? '?env=preview' : ''}`,
|
|
140
|
+
{ method: 'GET' },
|
|
141
|
+
)
|
|
142
|
+
expect(meta.isOwner).toBe(true)
|
|
143
|
+
expect(document.title).toBe('Owner visibility test')
|
|
144
|
+
expect(description.content).toBe('Owner visibility test')
|
|
145
|
+
expect(await fetchProjectMeta()).toBe(meta)
|
|
146
|
+
expect(runtime.fetch).toHaveBeenCalledTimes(1)
|
|
147
|
+
},
|
|
148
|
+
)
|
|
79
149
|
})
|