@lark-base-open/base-database-cli 1.1.0 → 1.3.7
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 +86 -0
- package/LICENSE +6 -0
- package/README.md +55 -31
- package/dist/ai-functions.d.ts +42 -0
- package/dist/ai-functions.d.ts.map +1 -0
- package/dist/ai-functions.js +327 -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 +65 -10
- package/dist/archive.js.map +1 -1
- package/dist/auth.d.ts +8 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +60 -6
- 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 +1 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +122 -175
- 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 +14 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +886 -243
- package/dist/init.js.map +1 -1
- package/dist/lark-cli-error.d.ts +46 -0
- package/dist/lark-cli-error.d.ts.map +1 -0
- package/dist/lark-cli-error.js +171 -0
- package/dist/lark-cli-error.js.map +1 -0
- package/dist/lark-cli.d.ts +2 -1
- package/dist/lark-cli.d.ts.map +1 -1
- package/dist/lark-cli.js +292 -29
- 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 +241 -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 +81 -0
- package/dist/public-sql-error.js.map +1 -0
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +54 -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 +70 -53
- package/skills/lark-base-database/references/ai-function.md +471 -0
- package/skills/lark-base-database/references/base-capability-routing.md +3 -3
- package/skills/lark-base-database/references/database-function-limitations.md +46 -0
- package/skills/lark-base-database/references/database-function.md +349 -0
- package/skills/lark-base-database/references/design/components/actions-fields.md +16 -0
- package/skills/lark-base-database/references/design/components/code/controls.css +122 -0
- package/skills/lark-base-database/references/design/components/code/controls.tsx +73 -0
- package/skills/lark-base-database/references/design/components/code/date-picker.tsx +42 -0
- package/skills/lark-base-database/references/design/components/code/floating.tsx +41 -0
- package/skills/lark-base-database/references/design/components/code/overlays.tsx +19 -0
- package/skills/lark-base-database/references/design/components/code/select.tsx +34 -0
- package/skills/lark-base-database/references/design/components/code/styles.d.ts +1 -0
- package/skills/lark-base-database/references/design/components/code/theme.css +49 -0
- package/skills/lark-base-database/references/design/components/code/usage.tsx +38 -0
- package/skills/lark-base-database/references/design/components/date-time-number.md +9 -0
- package/skills/lark-base-database/references/design/components/feedback-navigation.md +9 -0
- package/skills/lark-base-database/references/design/components/index.md +18 -0
- package/skills/lark-base-database/references/design/components/overlays.md +9 -0
- package/skills/lark-base-database/references/design/components/patterns/flat-business.md +7 -0
- package/skills/lark-base-database/references/design/components/selection.md +24 -0
- package/skills/lark-base-database/references/design/components.md +91 -0
- package/skills/lark-base-database/references/design/composition.md +90 -0
- package/skills/lark-base-database/references/design/control-recipes.md +107 -0
- package/skills/lark-base-database/references/design/examples.md +172 -0
- package/skills/lark-base-database/references/design/foundations.md +126 -0
- package/skills/lark-base-database/references/design/review.md +82 -0
- package/skills/lark-base-database/references/design/scenes/analysis-report.md +66 -0
- package/skills/lark-base-database/references/design/scenes/brand-showcase.md +70 -0
- package/skills/lark-base-database/references/design/scenes/business-operations.md +85 -0
- package/skills/lark-base-database/references/design/scenes/campaign-report.md +66 -0
- package/skills/lark-base-database/references/design/scenes/dashboard.md +77 -0
- package/skills/lark-base-database/references/design/scenes/festive-campaign.md +69 -0
- package/skills/lark-base-database/references/design/theme.md +27 -0
- package/skills/lark-base-database/references/design/typography.md +56 -0
- package/skills/lark-base-database/references/development.md +165 -32
- package/skills/lark-base-database/references/environment-variables.md +55 -0
- package/skills/lark-base-database/references/faas.md +41 -48
- package/skills/lark-base-database/references/file-transfer.md +25 -25
- package/skills/lark-base-database/references/frontend-design-guide.md +33 -77
- package/skills/lark-base-database/references/mutations.md +295 -0
- package/skills/lark-base-database/references/permission-design.md +24 -24
- package/skills/lark-base-database/references/release.md +54 -27
- package/skills/lark-base-database/references/style-guide.md +10 -67
- package/skills/lark-base-database/references/verification.md +17 -13
- package/skills/lark-base-database/references/workspace.md +71 -22
- package/templates/database-starter/README.md +34 -4
- package/templates/database-starter/database-functions/README.md +8 -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/index.tsx +75 -112
- 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 -151
- 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
|
@@ -1,84 +1,60 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import editDefaultIcon from './assets/edit-default.svg'
|
|
3
|
+
import editActiveIcon from './assets/edit-active.svg'
|
|
2
4
|
import { fetchProjectMeta, type ProjectMeta } from './utils/project-meta'
|
|
3
|
-
import { ChatbotEntry, resolveChatbotDisplayName } from './chatbot-entry'
|
|
4
5
|
import {
|
|
5
6
|
getDatabaseContext,
|
|
6
7
|
resolveDatabaseContext,
|
|
7
8
|
siteRuntime,
|
|
8
9
|
} from '../../lib/database-runtime'
|
|
9
|
-
|
|
10
|
-
export type FloatingButtonLanguage = 'en' | 'zh'
|
|
11
|
-
|
|
12
|
-
interface FloatingButtonProps {
|
|
13
|
-
language?: FloatingButtonLanguage
|
|
14
|
-
showChatbotEntry?: boolean
|
|
15
|
-
}
|
|
10
|
+
import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
|
|
16
11
|
|
|
17
12
|
interface FloatingButtonI18n {
|
|
18
13
|
editSite: string
|
|
14
|
+
hidePanel: string
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
const BASE_DATABASE_OVERVIEW_URL = 'https://feishu.cn/base/site/detail/overview'
|
|
22
|
-
const CHATBOT_SIDEBAR_DEFAULT_WIDTH = 420
|
|
23
18
|
const FLOATING_BUTTON_PAGE_GAP = 24
|
|
24
19
|
const EMPTY_PROJECT_META: ProjectMeta = {
|
|
25
20
|
baseBindings: [],
|
|
26
|
-
chatList: [],
|
|
27
21
|
description: '',
|
|
28
22
|
isOwner: false,
|
|
23
|
+
allowEdit: false,
|
|
29
24
|
name: '',
|
|
30
25
|
organization: '',
|
|
31
26
|
}
|
|
32
|
-
const FLOATING_BUTTON_I18N: Record<
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
27
|
+
const FLOATING_BUTTON_I18N: Record<UiLanguage, FloatingButtonI18n> = {
|
|
28
|
+
en: {
|
|
29
|
+
editSite: 'Edit and manage your apps',
|
|
30
|
+
hidePanel: 'Hide until next visit',
|
|
31
|
+
},
|
|
32
|
+
zh: {
|
|
33
|
+
editSite: '编辑与管理应用',
|
|
34
|
+
hidePanel: '隐藏面板',
|
|
35
|
+
},
|
|
36
|
+
ja: {
|
|
37
|
+
editSite: 'アプリを編集・管理',
|
|
38
|
+
hidePanel: '次回のアクセスまで非表示',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
41
|
|
|
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 悬浮按钮组。
|
|
42
|
+
/** 渲染有管理权限的访问者在正式环境中的编辑入口。
|
|
43
|
+
* @returns 编辑按钮;Preview 或无管理权限时不显示。
|
|
51
44
|
*/
|
|
52
|
-
export function FloatingButton({
|
|
53
|
-
language = 'en',
|
|
54
|
-
showChatbotEntry = true,
|
|
55
|
-
}: FloatingButtonProps) {
|
|
45
|
+
export function FloatingButton() {
|
|
56
46
|
const [projectMeta, setProjectMeta] = useState<ProjectMeta | null>(null)
|
|
57
|
-
const [
|
|
58
|
-
const [
|
|
59
|
-
|
|
60
|
-
CHATBOT_SIDEBAR_DEFAULT_WIDTH,
|
|
47
|
+
const [isHidden, setIsHidden] = useState(false)
|
|
48
|
+
const [activeTooltip, setActiveTooltip] = useState<'edit' | 'hide' | null>(
|
|
49
|
+
null,
|
|
61
50
|
)
|
|
62
|
-
//
|
|
63
|
-
// 靠它永远隐藏不掉,必须显式按产物环境判断。
|
|
51
|
+
// Preview 不展示返回编辑态入口;按钮显隐不替代宿主点击时的鉴权。
|
|
64
52
|
const shouldShowEditButton =
|
|
65
|
-
projectMeta?.
|
|
66
|
-
const
|
|
67
|
-
const shouldShowFloatingControls =
|
|
68
|
-
shouldShowEditButton || (showChatbotEntry && !isChatbotOpen)
|
|
69
|
-
const floatingButtonRight = shouldOffsetForChatbot
|
|
70
|
-
? chatbotSidebarWidth + FLOATING_BUTTON_PAGE_GAP
|
|
71
|
-
: FLOATING_BUTTON_PAGE_GAP
|
|
72
|
-
const i18n = FLOATING_BUTTON_I18N[language]
|
|
73
|
-
const genericChatbotName = language === 'zh' ? '智能助手' : 'AI assistant'
|
|
74
|
-
const chatbotFallbackName = resolveChatbotDisplayName(
|
|
75
|
-
projectMeta?.name ?? '',
|
|
76
|
-
genericChatbotName,
|
|
77
|
-
)
|
|
53
|
+
projectMeta?.allowEdit === true && getDatabaseContext().env === 'prod'
|
|
54
|
+
const i18n = FLOATING_BUTTON_I18N[useUiLanguage()]
|
|
78
55
|
|
|
79
56
|
useEffect(() => {
|
|
80
57
|
let isCurrentRequest = true
|
|
81
|
-
setIsFloatingDataLoaded(false)
|
|
82
58
|
|
|
83
59
|
/** 读取悬浮控件所需的项目资源数据。 */
|
|
84
60
|
async function loadFloatingData(): Promise<void> {
|
|
@@ -91,7 +67,6 @@ export function FloatingButton({
|
|
|
91
67
|
}
|
|
92
68
|
|
|
93
69
|
setProjectMeta(nextProjectMeta)
|
|
94
|
-
setIsFloatingDataLoaded(true)
|
|
95
70
|
}
|
|
96
71
|
|
|
97
72
|
void loadFloatingData()
|
|
@@ -101,18 +76,6 @@ export function FloatingButton({
|
|
|
101
76
|
}
|
|
102
77
|
}, [])
|
|
103
78
|
|
|
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
79
|
// 编辑按钮走 site.v1 契约:应用发 request-edit,宿主切编辑态后广播 mode-changed。
|
|
117
80
|
// 应用被独立访问(无宿主)时事件无人消费,回落为打开 Site 管理页。
|
|
118
81
|
const requestEdit = useCallback(() => {
|
|
@@ -124,41 +87,63 @@ export function FloatingButton({
|
|
|
124
87
|
})
|
|
125
88
|
}, [])
|
|
126
89
|
|
|
127
|
-
if (!
|
|
90
|
+
if (!shouldShowEditButton || isHidden) {
|
|
128
91
|
return null
|
|
129
92
|
}
|
|
130
93
|
|
|
131
94
|
return (
|
|
132
95
|
<div
|
|
133
|
-
className=
|
|
134
|
-
style={{ right:
|
|
96
|
+
className="group fixed bottom-10 z-40 flex items-center"
|
|
97
|
+
style={{ right: FLOATING_BUTTON_PAGE_GAP }}
|
|
135
98
|
>
|
|
99
|
+
{activeTooltip ? (
|
|
100
|
+
<div
|
|
101
|
+
role="tooltip"
|
|
102
|
+
className="pointer-events-none absolute right-0 bottom-[calc(100%+14px)] z-10 whitespace-nowrap rounded-lg bg-[#1f2329] px-3 py-2 text-sm text-white shadow-lg"
|
|
103
|
+
>
|
|
104
|
+
{activeTooltip === 'edit' ? i18n.editSite : i18n.hidePanel}
|
|
105
|
+
<span className="absolute -bottom-1 right-[22px] size-2 rotate-45 bg-[#1f2329]" />
|
|
106
|
+
</div>
|
|
107
|
+
) : null}
|
|
136
108
|
<div
|
|
137
|
-
className=
|
|
109
|
+
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
110
|
data-floating-controls="true"
|
|
139
111
|
>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
chatbots={projectMeta.chatList}
|
|
155
|
-
baseId={projectMeta.baseBindings[0]?.baseToken}
|
|
156
|
-
className="shrink-0"
|
|
157
|
-
fallbackName={chatbotFallbackName}
|
|
158
|
-
onOpenChange={handleChatbotOpenChange}
|
|
159
|
-
onSidebarWidthChange={setChatbotSidebarWidth}
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
aria-label={i18n.editSite}
|
|
115
|
+
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"
|
|
116
|
+
onMouseEnter={() => setActiveTooltip('edit')}
|
|
117
|
+
onMouseLeave={() => setActiveTooltip(null)}
|
|
118
|
+
onFocus={() => setActiveTooltip('edit')}
|
|
119
|
+
onBlur={() => setActiveTooltip(null)}
|
|
120
|
+
onClick={requestEdit}
|
|
121
|
+
>
|
|
122
|
+
<img
|
|
123
|
+
src={editDefaultIcon}
|
|
124
|
+
alt=""
|
|
125
|
+
className="size-5 group-hover/edit:hidden group-focus-visible/edit:hidden"
|
|
160
126
|
/>
|
|
161
|
-
|
|
127
|
+
<img
|
|
128
|
+
src={editActiveIcon}
|
|
129
|
+
alt=""
|
|
130
|
+
className="hidden size-5 group-hover/edit:block group-focus-visible/edit:block"
|
|
131
|
+
/>
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
aria-label={i18n.hidePanel}
|
|
136
|
+
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]"
|
|
137
|
+
onMouseEnter={() => setActiveTooltip('hide')}
|
|
138
|
+
onMouseLeave={() => setActiveTooltip(null)}
|
|
139
|
+
onFocus={() => setActiveTooltip('hide')}
|
|
140
|
+
onBlur={() => setActiveTooltip(null)}
|
|
141
|
+
onClick={() => setIsHidden(true)}
|
|
142
|
+
>
|
|
143
|
+
<span aria-hidden="true" className="text-[14px] leading-none">
|
|
144
|
+
×
|
|
145
|
+
</span>
|
|
146
|
+
</button>
|
|
162
147
|
</div>
|
|
163
148
|
</div>
|
|
164
149
|
)
|
|
@@ -171,26 +156,4 @@ function openExternalUrl(url: string): void {
|
|
|
171
156
|
window.open(url, '_blank', 'noopener,noreferrer')
|
|
172
157
|
}
|
|
173
158
|
|
|
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
159
|
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
|
})
|
package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts
CHANGED
|
@@ -5,12 +5,6 @@ import {
|
|
|
5
5
|
type DatabaseContext,
|
|
6
6
|
} from '../../../lib/database-runtime'
|
|
7
7
|
|
|
8
|
-
interface ProjectChat {
|
|
9
|
-
id: string
|
|
10
|
-
name: string
|
|
11
|
-
iconInfo: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
export interface ProjectBaseBinding {
|
|
15
9
|
baseToken: string
|
|
16
10
|
tableIds: string[]
|
|
@@ -18,11 +12,11 @@ export interface ProjectBaseBinding {
|
|
|
18
12
|
|
|
19
13
|
export interface ProjectMeta {
|
|
20
14
|
baseBindings: ProjectBaseBinding[]
|
|
21
|
-
chatList: ProjectChat[]
|
|
22
15
|
name: string
|
|
23
16
|
description: string
|
|
24
17
|
isOwner: boolean
|
|
25
|
-
|
|
18
|
+
allowEdit: boolean
|
|
19
|
+
/** 应用所属组织(企业)名,账号菜单展示用;服务端未下发时为空串。 */
|
|
26
20
|
organization: string
|
|
27
21
|
}
|
|
28
22
|
|
|
@@ -60,15 +54,15 @@ function readStringArray(value: unknown): string[] {
|
|
|
60
54
|
}
|
|
61
55
|
|
|
62
56
|
/** 构造空项目 meta。
|
|
63
|
-
* @returns
|
|
57
|
+
* @returns 不包含所有者权限的空项目 meta。
|
|
64
58
|
*/
|
|
65
59
|
function createEmptyProjectMeta(): ProjectMeta {
|
|
66
60
|
return {
|
|
67
61
|
baseBindings: [],
|
|
68
|
-
chatList: [],
|
|
69
62
|
name: '',
|
|
70
63
|
description: '',
|
|
71
64
|
isOwner: false,
|
|
65
|
+
allowEdit: false,
|
|
72
66
|
organization: '',
|
|
73
67
|
}
|
|
74
68
|
}
|
|
@@ -130,41 +124,9 @@ function parseProjectBaseBindings(
|
|
|
130
124
|
}, [])
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
/** 解析 Site 绑定的 chatbot 列表。
|
|
134
|
-
*
|
|
135
|
-
* 接口的 chatbot 字段全部可选:缺 id 的条目无法跳转,直接丢弃;名称与图标缺失时
|
|
136
|
-
* 留空交由展示层兜底。单个 chatbot 字段不全不能影响整个项目 meta 的解析。
|
|
137
|
-
*
|
|
138
|
-
* @param rawChatbots 接口返回的 chatbots 字段。
|
|
139
|
-
* @returns 可用于展示的 chatbot 列表。
|
|
140
|
-
*/
|
|
141
|
-
function parseProjectChats(rawChatbots: unknown): ProjectChat[] {
|
|
142
|
-
if (!Array.isArray(rawChatbots)) {
|
|
143
|
-
return []
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return rawChatbots.reduce<ProjectChat[]>((chats, chatbot) => {
|
|
147
|
-
if (!isRecord(chatbot)) {
|
|
148
|
-
return chats
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const id = readOptionalString(chatbot, 'id')
|
|
152
|
-
if (!id) {
|
|
153
|
-
return chats
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
chats.push({
|
|
157
|
-
id,
|
|
158
|
-
name: readOptionalString(chatbot, 'name'),
|
|
159
|
-
iconInfo: readOptionalString(chatbot, 'iconInfo'),
|
|
160
|
-
})
|
|
161
|
-
return chats
|
|
162
|
-
}, [])
|
|
163
|
-
}
|
|
164
|
-
|
|
165
127
|
/** 将资源接口响应转换为页面需要的项目 meta。
|
|
166
128
|
* @param rawResponse 接口原始响应。
|
|
167
|
-
* @returns
|
|
129
|
+
* @returns 提取后的应用名称、描述、所属组织、Base 绑定及所有者状态;接口或结构异常时返回空数据。
|
|
168
130
|
*/
|
|
169
131
|
export function parseProjectMetaResponse(rawResponse: unknown): ProjectMeta {
|
|
170
132
|
if (!isRecord(rawResponse)) {
|
|
@@ -187,10 +149,14 @@ export function parseProjectMetaResponse(rawResponse: unknown): ProjectMeta {
|
|
|
187
149
|
|
|
188
150
|
return {
|
|
189
151
|
baseBindings: parseProjectBaseBindings(site.baseBindings),
|
|
190
|
-
chatList: parseProjectChats(site.chatbots),
|
|
191
152
|
description: readOptionalString(site, 'description'),
|
|
192
153
|
name: readOptionalString(site, 'name'),
|
|
193
154
|
isOwner: data.isOwner === true,
|
|
155
|
+
// 老服务端尚未下发 allowEdit 时保留 Owner 入口;显式 false 必须优先。
|
|
156
|
+
allowEdit:
|
|
157
|
+
site.allowEdit === undefined
|
|
158
|
+
? data.isOwner === true
|
|
159
|
+
: site.allowEdit === true,
|
|
194
160
|
organization: readOptionalString(site, 'organization'),
|
|
195
161
|
}
|
|
196
162
|
}
|
|
@@ -206,12 +172,9 @@ async function requestProjectMeta(
|
|
|
206
172
|
context: DatabaseContext,
|
|
207
173
|
): Promise<ProjectMeta> {
|
|
208
174
|
try {
|
|
209
|
-
const response = await databaseFetch(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
method: 'GET',
|
|
213
|
-
},
|
|
214
|
-
)
|
|
175
|
+
const response = await databaseFetch(buildDatabaseInvokeGetPath(context), {
|
|
176
|
+
method: 'GET',
|
|
177
|
+
})
|
|
215
178
|
|
|
216
179
|
if (!response.ok) {
|
|
217
180
|
return createEmptyProjectMeta()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
|
|
4
|
+
const runtime = vi.hoisted(() => ({ getRuntimeContext: vi.fn() }))
|
|
5
|
+
vi.mock('../lib/database-runtime', () => ({ siteRuntime: runtime }))
|
|
6
|
+
|
|
7
|
+
import UserSelectorEditor from './UserSelectorEditor'
|
|
8
|
+
import UserSelectorPanel from './UserSelectorPanel'
|
|
9
|
+
import { PageState } from './feedback/page-state'
|
|
10
|
+
import { AttachmentImage } from '../lib/attachment-image'
|
|
11
|
+
import App from '../App'
|
|
12
|
+
import {
|
|
13
|
+
getUiLanguage,
|
|
14
|
+
getUiLocale,
|
|
15
|
+
selectUiLanguage,
|
|
16
|
+
} from '../lib/ui-language'
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.unstubAllGlobals()
|
|
20
|
+
runtime.getRuntimeContext.mockReset()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('built-in component language', () => {
|
|
24
|
+
it('prefers supported host locales, then browser locales, then English', () => {
|
|
25
|
+
expect(selectUiLanguage('ja-JP', 'zh-CN')).toBe('ja')
|
|
26
|
+
expect(selectUiLanguage('zh_CN', 'ja-JP')).toBe('zh')
|
|
27
|
+
expect(selectUiLanguage('zh-TW', 'ja-JP')).toBe('zh')
|
|
28
|
+
expect(getUiLocale('zh')).toBe('zh-CN')
|
|
29
|
+
expect(selectUiLanguage('fr-FR', 'ja-JP')).toBe('ja')
|
|
30
|
+
expect(selectUiLanguage(undefined, 'fr-FR')).toBe('en')
|
|
31
|
+
expect(selectUiLanguage(undefined, undefined)).toBe('en')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('uses the browser language before the host context resolves', () => {
|
|
35
|
+
vi.stubGlobal('navigator', { language: 'ja-JP' })
|
|
36
|
+
vi.stubGlobal('document', { documentElement: { lang: 'en' } })
|
|
37
|
+
|
|
38
|
+
expect(getUiLanguage()).toBe('ja')
|
|
39
|
+
expect(getUiLocale()).toBe('ja-JP')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('uses English on server render until the browser resolves the locale', () => {
|
|
43
|
+
expect(renderToStaticMarkup(<App />)).toContain(
|
|
44
|
+
'Start with a real business workflow',
|
|
45
|
+
)
|
|
46
|
+
expect(renderToStaticMarkup(<UserSelectorEditor value={null} />)).toContain(
|
|
47
|
+
'Select members',
|
|
48
|
+
)
|
|
49
|
+
expect(renderToStaticMarkup(<UserSelectorPanel />)).toContain(
|
|
50
|
+
'No recommended members yet',
|
|
51
|
+
)
|
|
52
|
+
expect(renderToStaticMarkup(<PageState status="error" />)).toContain(
|
|
53
|
+
'Please try again later',
|
|
54
|
+
)
|
|
55
|
+
expect(
|
|
56
|
+
renderToStaticMarkup(
|
|
57
|
+
<AttachmentImage
|
|
58
|
+
attachment={{
|
|
59
|
+
tableId: 'tblA',
|
|
60
|
+
fieldId: 'fldA',
|
|
61
|
+
recordId: 'recA',
|
|
62
|
+
fileToken: 'fileA',
|
|
63
|
+
}}
|
|
64
|
+
alt="attachment"
|
|
65
|
+
/>,
|
|
66
|
+
),
|
|
67
|
+
).toContain('Loading image')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('uses browser lang until the host locale arrives, then applies the host locale', async () => {
|
|
71
|
+
vi.resetModules()
|
|
72
|
+
vi.stubGlobal('navigator', { language: 'zh-CN' })
|
|
73
|
+
vi.stubGlobal('document', { documentElement: { lang: 'en' } })
|
|
74
|
+
let resolveContext!: (value: { locale: string }) => void
|
|
75
|
+
runtime.getRuntimeContext.mockReturnValue(
|
|
76
|
+
new Promise((resolve) => {
|
|
77
|
+
resolveContext = resolve
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
const language = await import('../lib/ui-language')
|
|
81
|
+
const request = language.initializeUiLanguage()
|
|
82
|
+
|
|
83
|
+
expect(document.documentElement.lang).toBe('zh-CN')
|
|
84
|
+
resolveContext({ locale: 'ja-JP' })
|
|
85
|
+
expect(await request).toBe('ja')
|
|
86
|
+
expect(language.getUiLanguage()).toBe('ja')
|
|
87
|
+
expect(document.documentElement.lang).toBe('ja-JP')
|
|
88
|
+
expect(runtime.getRuntimeContext).toHaveBeenCalledTimes(1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('falls back to the browser when the host request fails', async () => {
|
|
92
|
+
vi.resetModules()
|
|
93
|
+
vi.stubGlobal('navigator', { language: 'ja-JP' })
|
|
94
|
+
vi.stubGlobal('document', { documentElement: { lang: 'en' } })
|
|
95
|
+
runtime.getRuntimeContext.mockRejectedValue(new Error('unavailable'))
|
|
96
|
+
const language = await import('../lib/ui-language')
|
|
97
|
+
|
|
98
|
+
expect(await language.initializeUiLanguage()).toBe('ja')
|
|
99
|
+
expect(document.documentElement.lang).toBe('ja-JP')
|
|
100
|
+
})
|
|
101
|
+
})
|