@lark-base-open/base-database-cli 1.1.1 → 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 +74 -0
- package/LICENSE +6 -0
- package/README.md +45 -27
- 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 +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 +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 +883 -261
- 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 +1 -1
- package/dist/lark-cli.d.ts.map +1 -1
- package/dist/lark-cli.js +66 -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 +240 -41
- 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 +66 -50
- 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 +51 -24
- package/skills/lark-base-database/references/style-guide.md +10 -67
- package/skills/lark-base-database/references/verification.md +15 -11
- package/skills/lark-base-database/references/workspace.md +46 -8
- 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 -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,8 @@
|
|
|
1
|
+
# Database Functions
|
|
2
|
+
|
|
3
|
+
Place each Database Function in `database-functions/<functionId>/`:
|
|
4
|
+
|
|
5
|
+
- `main.sql`: Write a query using the supported Database Function syntax. Refer to parameters as `$1`, `$2`, and so on.
|
|
6
|
+
- `meta.json`: Declare `name`, `description`, and the parameter contract. Omit `meta` or `meta.params` when the query has no parameters.
|
|
7
|
+
|
|
8
|
+
Database Functions read Base data. Use `server-functions/functions/` for writes. See the `lark-base-database` Skill's `database-function.md` for parameter, response, Catalog, and SQL dialect contracts.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mutation Functions
|
|
2
|
+
|
|
3
|
+
每个声明能完整表达的固定业务写入放在 `<functionId>/meta.json` 和 `<functionId>/spec.json`。表和字段必须来自根目录 `catalog.json`。
|
|
4
|
+
|
|
5
|
+
`meta.json` 示例:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{"name":"更新标题"}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`spec.json` 示例(替换真实 table / field ID):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{"table":{"table_id":"tblTasks"},"operation":"update","params":[{"kind":"RECORD_ID"},{"kind":"TEXT","max_length":100}],"precondition":{"conjunction":"and","conditions":[{"field_id":"rec_id","operator":"is","param":1}]},"set":{"fldTitle":{"param":2}},"returning":["rec_id","fldTitle"]}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
单条调用 `invokeDatabaseFunction('updateTitle', ['recA', '新标题'])`;批量调用 `invokeDatabaseMutationBatch('updateTitle', [['recA', '标题 A'], ['recB', '标题 B']])`。编号从 1 开始;一批最多 200 条。CLI deploy 自动校验、打包并更新 Function,pull 保留声明。
|
|
18
|
+
|
|
19
|
+
Mutation 负责基础单表写入;create 不带 precondition,其他操作通过 precondition 指定记录 ID。需要服务端业务校验、计算或编排时使用 FaaS。字段、附件、权限边界和完整协议见已安装的 lark-base-database Skill 的 `references/mutations.md`。
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
# Page
|
|
2
2
|
|
|
3
|
-
React + TypeScript + Vite
|
|
3
|
+
React + TypeScript + Vite page project. This directory's `package.json` defines its dependencies, styling libraries, and scripts.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See the `lark-base-database` Skill's `development.md` for `invokeDatabaseFunction`, response shapes, and Base read and write boundaries.
|
|
6
6
|
|
|
7
|
-
`vite.config.ts`
|
|
7
|
+
The relative path setting in `vite.config.ts` is required for deployment.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Built-in component language
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
`src/lib/database-runtime.ts` 是与宿主通信的唯一入口,基于
|
|
13
|
-
`@lark-base-open/base-site-channel/runtime` 提供:
|
|
11
|
+
The placeholder `App.tsx` page and the included user selector, account menu, edit button, page state, and attachment image components provide fixed Chinese, English, and Japanese interface text without a translation service. They resolve the language from the host Runtime Context locale, then the browser language, then English. The Page does not pass a language prop. When the host switches language and reloads the Page, they resolve it again. The starter HTML initially uses `lang="en"`; the resolved language updates that attribute. Replace the placeholder page before delivery. Application-specific text and runtime service errors remain the application's responsibility.
|
|
14
12
|
|
|
15
|
-
-
|
|
16
|
-
- `invokeDatabaseFunction(functionId, params)`:Function 调用,打统一网关的
|
|
17
|
-
`POST /space/api/bitable/site/v1/:token/invoke_function/:function_id`,请求体为 `{"params": ...}`
|
|
18
|
-
- `resolveDatabaseContext()`:解析应用 token 与 `prod` / `preview` 环境,**宿主下发优先**,
|
|
19
|
-
宿主未提供时回退 `getDatabaseContext()` 的产物路径推断;业务代码用这个异步版本
|
|
20
|
-
- `getDatabaseContext()`:同步的产物路径推断,仅作兜底,不要在业务代码里直接依赖
|
|
21
|
-
- `siteRuntime`:`ready()` 与 `reportLoadFailed()` 生命周期上报
|
|
13
|
+
Optional AI Function example components use the same language selection for their built-in text. See `src/examples/ai-function/README.md` before reusing or moving them.
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
## Runtime communication
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
The application runs in a BaseApp iframe. It does not hold login credentials or call business domains directly. `src/lib/database-runtime.ts` is the single entry point for communication with the host, using `@lark-base-open/base-site-channel/runtime`:
|
|
26
18
|
|
|
27
|
-
`
|
|
28
|
-
`
|
|
29
|
-
|
|
19
|
+
- `fetch` / `invokeHttp`: The parent page proxies business HTTP requests from its own origin. Business paths must start with a single `/`.
|
|
20
|
+
- `invokeDatabaseFunction(functionId, params)`: Calls `POST /space/api/bitable/site/v1/:token/invoke_function/:function_id` through the unified gateway with a `{"params": ...}` body.
|
|
21
|
+
- `src/lib/database-function-inputs.generated.ts`: The CLI generates Page input types from Database Function and FaaS metadata during `check` and Preview `deploy`. Do not edit this file. Page compilation checks Function IDs, query parameter order, and required FaaS fields and enum values. Callers still declare response types and validate them at runtime.
|
|
22
|
+
- `resolveDatabaseContext()`: Resolves the app token and `prod` or `preview` environment. Host-provided context takes precedence; otherwise it infers values from the artifact path through `getDatabaseContext()`. Use this asynchronous function in business code.
|
|
23
|
+
- `getDatabaseContext()`: Synchronous artifact-path inference for fallback use only.
|
|
24
|
+
- `callSiteAIFunction({ functionKey, capability, variables?, messages?, input? })`: Calls the configured AI Function and returns a discriminated result with `type: 'text' | 'image_task' | 'video_task'`. Poll media tasks by task ID and render by `result.type`.
|
|
25
|
+
- `invokeSiteAIFunctionStream({ functionKey, messages, variables? })`: Streams text or image understanding results. It provides subscribable updates and a `destroy()` method. Consume both `delta.reasoningContent` and `delta.content` and render them separately. See `src/examples/ai-function/TextStreamOperation.tsx`.
|
|
26
|
+
- `createSiteAIFunctionTask(params)` / `getSiteAIFunctionTask(functionKey, taskId)`: Submit and poll image or video generation tasks.
|
|
27
|
+
- `siteRuntime`: Reports lifecycle events through `ready()` and `reportLoadFailed()`.
|
|
28
|
+
|
|
29
|
+
Do not call `postMessage`, use the raw Channel Client, or fetch a business domain directly from business code.
|
|
30
|
+
|
|
31
|
+
Declare the model, System Prompt, reasoning depth, image size, video duration, and related settings in `ai/<functionKey>/meta.json` at the project root. Validate with `base-database-cli ai-functions validate <functionKey>` and sync through `deploy`. The page sends call-specific `variables` and `messages` or `input` for the selected `functionKey`. Preview uses the draft configuration; Prod uses it only after production publication. Before calling a Function, confirm that its `functionKey` has been synced and that `disabled` is not `true`.
|
|
32
|
+
|
|
33
|
+
## Request identity and CSRF
|
|
34
|
+
|
|
35
|
+
The parent page proxies business HTTP requests and handles identity and CSRF. The Page must not inject a CSRF script, read host cookies, or set a CSRF header itself. See the `lark-base-database` Skill's `verification.md` for migration and request troubleshooting.
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"name": "page",
|
|
9
9
|
"version": "0.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@lark-base-open/base-site-channel": "
|
|
12
|
-
"@lark-base-open/base-site-js": "https://lf3-static.bytednsdoc.com/obj/eden-cn/jjjpceh7nulojvhj/@lark-base-open-base-site-js-0.0.1-alpha.25.tgz",
|
|
11
|
+
"@lark-base-open/base-site-channel": "0.1.3",
|
|
13
12
|
"@radix-ui/react-slot": "^1.3.0",
|
|
14
13
|
"class-variance-authority": "^0.7.1",
|
|
15
14
|
"clsx": "^2.1.1",
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
"@types/react-dom": "^19.2.3",
|
|
26
25
|
"@vitejs/plugin-react": "^6.0.3",
|
|
27
26
|
"oxlint": "^1.42.0",
|
|
28
|
-
"prettier": "
|
|
27
|
+
"prettier": "3.6.2",
|
|
29
28
|
"tailwindcss": "^4.3.2",
|
|
30
29
|
"typescript": "~7.0.2",
|
|
31
30
|
"vite": "^8.1.4",
|
|
@@ -120,20 +119,13 @@
|
|
|
120
119
|
}
|
|
121
120
|
},
|
|
122
121
|
"node_modules/@lark-base-open/base-site-channel": {
|
|
123
|
-
"version": "0.
|
|
124
|
-
"resolved": "https://
|
|
125
|
-
"integrity": "sha512-
|
|
126
|
-
"license": "ISC",
|
|
122
|
+
"version": "0.1.3",
|
|
123
|
+
"resolved": "https://registry.npmjs.org/@lark-base-open/base-site-channel/-/base-site-channel-0.1.3.tgz",
|
|
124
|
+
"integrity": "sha512-wdc8zRkAX2P7pwBkyoun7oHrUP95xYCgAhhwsq4cUOg7ozIf9Em1DQken7kUeMc58pzDM5ONroWavoa1/oQiEg==",
|
|
127
125
|
"dependencies": {
|
|
128
126
|
"lodash-es": "^4.17.21"
|
|
129
127
|
}
|
|
130
128
|
},
|
|
131
|
-
"node_modules/@lark-base-open/base-site-js": {
|
|
132
|
-
"version": "0.0.1-alpha.25",
|
|
133
|
-
"resolved": "https://lf3-static.bytednsdoc.com/obj/eden-cn/jjjpceh7nulojvhj/@lark-base-open-base-site-js-0.0.1-alpha.25.tgz",
|
|
134
|
-
"integrity": "sha512-FtdohEY5YrYIRGxL/FIU8dSQ4326nf7QDZHEZm1HE59RIBAvlkPnqZlE+8AuvQZ+3KL+vmg6ZPmxONlghFyhbw==",
|
|
135
|
-
"license": "ISC"
|
|
136
|
-
},
|
|
137
129
|
"node_modules/@napi-rs/wasm-runtime": {
|
|
138
130
|
"version": "1.1.6",
|
|
139
131
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
|
@@ -2254,9 +2246,9 @@
|
|
|
2254
2246
|
}
|
|
2255
2247
|
},
|
|
2256
2248
|
"node_modules/prettier": {
|
|
2257
|
-
"version": "3.
|
|
2258
|
-
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.
|
|
2259
|
-
"integrity": "sha512
|
|
2249
|
+
"version": "3.6.2",
|
|
2250
|
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
|
2251
|
+
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
|
2260
2252
|
"dev": true,
|
|
2261
2253
|
"license": "MIT",
|
|
2262
2254
|
"bin": {
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@lark-base-open/base-site-channel": "
|
|
22
|
-
"@lark-base-open/base-site-js": "https://lf3-static.bytednsdoc.com/obj/eden-cn/jjjpceh7nulojvhj/@lark-base-open-base-site-js-0.0.1-alpha.25.tgz",
|
|
21
|
+
"@lark-base-open/base-site-channel": "0.1.3",
|
|
23
22
|
"@radix-ui/react-slot": "^1.3.0",
|
|
24
23
|
"class-variance-authority": "^0.7.1",
|
|
25
24
|
"clsx": "^2.1.1",
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
"@types/react-dom": "^19.2.3",
|
|
36
35
|
"@vitejs/plugin-react": "^6.0.3",
|
|
37
36
|
"oxlint": "^1.42.0",
|
|
38
|
-
"prettier": "
|
|
37
|
+
"prettier": "3.6.2",
|
|
39
38
|
"tailwindcss": "^4.3.2",
|
|
40
39
|
"typescript": "~7.0.2",
|
|
41
40
|
"vite": "^8.1.4",
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import { useUiLanguage, type UiLanguage } from './lib/ui-language'
|
|
2
|
+
|
|
3
|
+
const APP_COPY = {
|
|
4
|
+
en: {
|
|
5
|
+
heading: 'Start with a real business workflow',
|
|
6
|
+
description:
|
|
7
|
+
'Build the interface with page components. Read Base data through Database Functions and write through FaaS Functions. Replace this starter content before delivery.',
|
|
8
|
+
},
|
|
9
|
+
zh: {
|
|
10
|
+
heading: '从真实业务任务开始构建',
|
|
11
|
+
description:
|
|
12
|
+
'使用页面组件承载交互,通过 Database Function 读取 Base 数据、通过 FaaS Function 写入,并在交付前替换这段起始内容。',
|
|
13
|
+
},
|
|
14
|
+
ja: {
|
|
15
|
+
heading: '実際の業務フローから構築を始めましょう',
|
|
16
|
+
description:
|
|
17
|
+
'ページコンポーネントで操作画面を作成します。Base データは Database Function で読み込み、FaaS Function で書き込みます。公開前にこの初期コンテンツを置き換えてください。',
|
|
18
|
+
},
|
|
19
|
+
} satisfies Record<UiLanguage, { heading: string; description: string }>
|
|
20
|
+
|
|
1
21
|
export default function App() {
|
|
22
|
+
const copy = APP_COPY[useUiLanguage()]
|
|
2
23
|
return (
|
|
3
24
|
<main className="flex min-h-screen items-center justify-center bg-background px-6 py-12 text-foreground">
|
|
4
25
|
<section className="w-full max-w-2xl rounded-3xl border border-border bg-card p-8 shadow-sm sm:p-12">
|
|
@@ -6,11 +27,10 @@ export default function App() {
|
|
|
6
27
|
Base Database
|
|
7
28
|
</p>
|
|
8
29
|
<h1 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-5xl">
|
|
9
|
-
|
|
30
|
+
{copy.heading}
|
|
10
31
|
</h1>
|
|
11
32
|
<p className="mt-5 max-w-xl text-base leading-7 text-muted-foreground sm:text-lg">
|
|
12
|
-
|
|
13
|
-
FaaS,并在交付前替换这段起始内容。
|
|
33
|
+
{copy.description}
|
|
14
34
|
</p>
|
|
15
35
|
</section>
|
|
16
36
|
</main>
|
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
} from 'react'
|
|
10
10
|
|
|
11
11
|
import { cn } from '../../lib/cn'
|
|
12
|
+
import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
|
|
12
13
|
import UserSelectorPanel, {
|
|
13
14
|
type User,
|
|
14
15
|
type UserSelectorAction,
|
|
@@ -38,6 +39,31 @@ export interface UserSelectorEditorProps {
|
|
|
38
39
|
onBlur?: FocusEventHandler<HTMLButtonElement>
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
const USER_SELECTOR_EDITOR_COPY = {
|
|
43
|
+
en: {
|
|
44
|
+
clearSelected: 'Clear selected members',
|
|
45
|
+
removeMember: (name: string) => `Remove member ${name}`,
|
|
46
|
+
selectMembers: 'Select members',
|
|
47
|
+
},
|
|
48
|
+
zh: {
|
|
49
|
+
clearSelected: '清除已选成员',
|
|
50
|
+
removeMember: (name: string) => `移除成员 ${name}`,
|
|
51
|
+
selectMembers: '选择成员',
|
|
52
|
+
},
|
|
53
|
+
ja: {
|
|
54
|
+
clearSelected: '選択したメンバーをクリア',
|
|
55
|
+
removeMember: (name: string) => `メンバー ${name} を削除`,
|
|
56
|
+
selectMembers: 'メンバーを選択',
|
|
57
|
+
},
|
|
58
|
+
} satisfies Record<
|
|
59
|
+
UiLanguage,
|
|
60
|
+
{
|
|
61
|
+
clearSelected: string
|
|
62
|
+
removeMember: (name: string) => string
|
|
63
|
+
selectMembers: string
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
|
|
41
67
|
/**
|
|
42
68
|
* 展示受控成员值,并闭环管理 UserSelectorPanel 的打开、关闭与值回传。
|
|
43
69
|
*/
|
|
@@ -61,6 +87,8 @@ export default function UserSelectorEditor(
|
|
|
61
87
|
onFocus,
|
|
62
88
|
onBlur,
|
|
63
89
|
} = props
|
|
90
|
+
const uiLanguage = useUiLanguage()
|
|
91
|
+
const i18n = USER_SELECTOR_EDITOR_COPY[uiLanguage]
|
|
64
92
|
const [open, setOpen] = useState(false)
|
|
65
93
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
66
94
|
const panelOpen = open
|
|
@@ -175,8 +203,8 @@ export default function UserSelectorEditor(
|
|
|
175
203
|
<button
|
|
176
204
|
aria-label={
|
|
177
205
|
isMultiple
|
|
178
|
-
?
|
|
179
|
-
:
|
|
206
|
+
? i18n.removeMember(selectedUser.name)
|
|
207
|
+
: i18n.clearSelected
|
|
180
208
|
}
|
|
181
209
|
className="relative ml-0 flex size-3.5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 !text-[#646a73] outline-none hover:!text-[#0442d2]"
|
|
182
210
|
onClick={(event) => {
|
|
@@ -214,7 +242,7 @@ export default function UserSelectorEditor(
|
|
|
214
242
|
))
|
|
215
243
|
) : (
|
|
216
244
|
<button
|
|
217
|
-
aria-label=
|
|
245
|
+
aria-label={i18n.selectMembers}
|
|
218
246
|
aria-expanded={panelOpen}
|
|
219
247
|
aria-haspopup="listbox"
|
|
220
248
|
className="flex h-full min-w-0 flex-1 items-center gap-1 border-0 bg-transparent p-0 text-left text-inherit outline-none"
|
|
@@ -223,7 +251,7 @@ export default function UserSelectorEditor(
|
|
|
223
251
|
type="button"
|
|
224
252
|
>
|
|
225
253
|
<span className="min-w-0 flex-1 truncate text-[#8f959e]">
|
|
226
|
-
|
|
254
|
+
{i18n.selectMembers}
|
|
227
255
|
</span>
|
|
228
256
|
</button>
|
|
229
257
|
)}
|
|
@@ -8,6 +8,7 @@ import React, {
|
|
|
8
8
|
} from 'react'
|
|
9
9
|
|
|
10
10
|
import { cn } from '../../lib/cn'
|
|
11
|
+
import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
|
|
11
12
|
import './index.css'
|
|
12
13
|
import { requestUsers, type SearchUsersData, type User } from './user-search'
|
|
13
14
|
|
|
@@ -24,6 +25,57 @@ const SEARCH_CACHE_TTL_MS = 30_000
|
|
|
24
25
|
const RECORD_CREATOR_AVATAR_URL =
|
|
25
26
|
'https://lf-package-cn.feishucdn.com/obj/feishu-static/ccm/static_resource/scm_upload/base_filter_current_user.png'
|
|
26
27
|
|
|
28
|
+
const USER_SELECTOR_PANEL_COPY = {
|
|
29
|
+
en: {
|
|
30
|
+
invalidResponse: 'Member search response has an invalid structure',
|
|
31
|
+
loading: 'Loading...',
|
|
32
|
+
memberList: 'Member list',
|
|
33
|
+
noMatches: 'No matching members. Try another search term.',
|
|
34
|
+
noRecommendations: 'No recommended members yet',
|
|
35
|
+
recordCreator: 'Add the person who created this record',
|
|
36
|
+
searchFailed: 'Member search failed',
|
|
37
|
+
searchFailedCode: (code: number) =>
|
|
38
|
+
`Could not search members (error code ${code})`,
|
|
39
|
+
searchMembers: 'Search members',
|
|
40
|
+
},
|
|
41
|
+
zh: {
|
|
42
|
+
invalidResponse: '成员搜索响应结构无效',
|
|
43
|
+
loading: '加载中...',
|
|
44
|
+
memberList: '成员列表',
|
|
45
|
+
noMatches: '没有匹配的成员,请换个关键词',
|
|
46
|
+
noRecommendations: '暂无推荐成员',
|
|
47
|
+
recordCreator: '添加此记录的人员',
|
|
48
|
+
searchFailed: '搜索成员失败',
|
|
49
|
+
searchFailedCode: (code: number) => `搜索成员失败(错误码 ${code})`,
|
|
50
|
+
searchMembers: '搜索成员',
|
|
51
|
+
},
|
|
52
|
+
ja: {
|
|
53
|
+
invalidResponse: 'メンバー検索の応答形式が正しくありません',
|
|
54
|
+
loading: '読み込み中...',
|
|
55
|
+
memberList: 'メンバー一覧',
|
|
56
|
+
noMatches: '一致するメンバーがいません。別のキーワードで検索してください。',
|
|
57
|
+
noRecommendations: 'おすすめのメンバーがいません',
|
|
58
|
+
recordCreator: 'このレコードの作成者を追加',
|
|
59
|
+
searchFailed: 'メンバーの検索に失敗しました',
|
|
60
|
+
searchFailedCode: (code: number) =>
|
|
61
|
+
`メンバーを検索できませんでした(エラーコード ${code})`,
|
|
62
|
+
searchMembers: 'メンバーを検索',
|
|
63
|
+
},
|
|
64
|
+
} satisfies Record<
|
|
65
|
+
UiLanguage,
|
|
66
|
+
{
|
|
67
|
+
invalidResponse: string
|
|
68
|
+
loading: string
|
|
69
|
+
memberList: string
|
|
70
|
+
noMatches: string
|
|
71
|
+
noRecommendations: string
|
|
72
|
+
recordCreator: string
|
|
73
|
+
searchFailed: string
|
|
74
|
+
searchFailedCode: (code: number) => string
|
|
75
|
+
searchMembers: string
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
|
|
27
79
|
interface SearchUsersCacheEntry {
|
|
28
80
|
expiresAt: number
|
|
29
81
|
request: Promise<SearchUsersData>
|
|
@@ -72,6 +124,8 @@ export default function UserSelectorPanel(
|
|
|
72
124
|
mode,
|
|
73
125
|
onKeyDown,
|
|
74
126
|
} = props
|
|
127
|
+
const uiLanguage = useUiLanguage()
|
|
128
|
+
const i18n = USER_SELECTOR_PANEL_COPY[uiLanguage]
|
|
75
129
|
const isValueControlled = Object.prototype.hasOwnProperty.call(props, 'value')
|
|
76
130
|
const [innerValue, setInnerValue] = useState(defaultValue)
|
|
77
131
|
const [query, setQuery] = useState('')
|
|
@@ -94,28 +148,31 @@ export default function UserSelectorPanel(
|
|
|
94
148
|
activeIndex >= 0 ? `${generatedListboxId}-option-${activeIndex}` : undefined
|
|
95
149
|
const isEmpty = !loading && optionCount === 0
|
|
96
150
|
|
|
97
|
-
const getSearchResult = useCallback(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
searchUsersCache.
|
|
110
|
-
|
|
111
|
-
|
|
151
|
+
const getSearchResult = useCallback(
|
|
152
|
+
(keyword: string) => {
|
|
153
|
+
const cacheKey = `${uiLanguage}:${keyword.trim()}`
|
|
154
|
+
const cachedResult = searchUsersCache.get(cacheKey)
|
|
155
|
+
if (cachedResult && cachedResult.expiresAt > Date.now()) {
|
|
156
|
+
return cachedResult.request
|
|
157
|
+
}
|
|
158
|
+
searchUsersCache.delete(cacheKey)
|
|
159
|
+
|
|
160
|
+
const request = requestUsers(keyword.trim(), uiLanguage)
|
|
161
|
+
.then((value) => normalizeSearchUsersResponse(value, uiLanguage))
|
|
162
|
+
.catch((error: unknown) => {
|
|
163
|
+
if (searchUsersCache.get(cacheKey)?.request === request) {
|
|
164
|
+
searchUsersCache.delete(cacheKey)
|
|
165
|
+
}
|
|
166
|
+
throw error
|
|
167
|
+
})
|
|
168
|
+
searchUsersCache.set(cacheKey, {
|
|
169
|
+
expiresAt: Date.now() + SEARCH_CACHE_TTL_MS,
|
|
170
|
+
request,
|
|
112
171
|
})
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return request
|
|
118
|
-
}, [])
|
|
172
|
+
return request
|
|
173
|
+
},
|
|
174
|
+
[uiLanguage],
|
|
175
|
+
)
|
|
119
176
|
|
|
120
177
|
const updateQuery = useCallback(
|
|
121
178
|
(nextQuery: string, action: UserSelectorSearchAction) => {
|
|
@@ -149,7 +206,9 @@ export default function UserSelectorPanel(
|
|
|
149
206
|
} catch (searchError) {
|
|
150
207
|
if (requestSequence !== requestSequenceRef.current) return
|
|
151
208
|
setError(
|
|
152
|
-
searchError instanceof Error
|
|
209
|
+
searchError instanceof Error
|
|
210
|
+
? searchError.message
|
|
211
|
+
: i18n.searchFailed,
|
|
153
212
|
)
|
|
154
213
|
setUsers([])
|
|
155
214
|
} finally {
|
|
@@ -158,7 +217,7 @@ export default function UserSelectorPanel(
|
|
|
158
217
|
}
|
|
159
218
|
}
|
|
160
219
|
},
|
|
161
|
-
[getSearchResult],
|
|
220
|
+
[getSearchResult, i18n],
|
|
162
221
|
)
|
|
163
222
|
|
|
164
223
|
useEffect(() => {
|
|
@@ -312,14 +371,14 @@ export default function UserSelectorPanel(
|
|
|
312
371
|
}}
|
|
313
372
|
onCompositionStart={() => setIsComposing(true)}
|
|
314
373
|
onKeyDown={handleKeyDown}
|
|
315
|
-
placeholder=
|
|
374
|
+
placeholder={i18n.searchMembers}
|
|
316
375
|
ref={inputRef}
|
|
317
376
|
role="searchbox"
|
|
318
377
|
value={query}
|
|
319
378
|
/>
|
|
320
379
|
|
|
321
380
|
<div
|
|
322
|
-
aria-label=
|
|
381
|
+
aria-label={i18n.memberList}
|
|
323
382
|
aria-multiselectable={isMultiple || undefined}
|
|
324
383
|
className={cn(
|
|
325
384
|
'flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-1 py-1 [scrollbar-color:#bbbfc4_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-[#bbbfc4] [&::-webkit-scrollbar-thumb]:bg-clip-padding [&::-webkit-scrollbar]:w-2',
|
|
@@ -351,7 +410,9 @@ export default function UserSelectorPanel(
|
|
|
351
410
|
className="mr-2 size-8 shrink-0 rounded-full object-cover"
|
|
352
411
|
src={RECORD_CREATOR_AVATAR_URL}
|
|
353
412
|
/>
|
|
354
|
-
<span className="min-w-0 flex-1 truncate"
|
|
413
|
+
<span className="min-w-0 flex-1 truncate">
|
|
414
|
+
{i18n.recordCreator}
|
|
415
|
+
</span>
|
|
355
416
|
<span aria-hidden="true" className="ml-[5px] size-3 shrink-0" />
|
|
356
417
|
</button>
|
|
357
418
|
) : null}
|
|
@@ -427,7 +488,7 @@ export default function UserSelectorPanel(
|
|
|
427
488
|
/>
|
|
428
489
|
</svg>
|
|
429
490
|
</span>
|
|
430
|
-
<span className="sr-only"
|
|
491
|
+
<span className="sr-only">{i18n.loading}</span>
|
|
431
492
|
</div>
|
|
432
493
|
) : null}
|
|
433
494
|
{isEmpty ? (
|
|
@@ -436,8 +497,8 @@ export default function UserSelectorPanel(
|
|
|
436
497
|
{error
|
|
437
498
|
? error
|
|
438
499
|
: query.trim()
|
|
439
|
-
?
|
|
440
|
-
:
|
|
500
|
+
? i18n.noMatches
|
|
501
|
+
: i18n.noRecommendations}
|
|
441
502
|
</div>
|
|
442
503
|
</div>
|
|
443
504
|
) : null}
|
|
@@ -446,19 +507,28 @@ export default function UserSelectorPanel(
|
|
|
446
507
|
)
|
|
447
508
|
}
|
|
448
509
|
|
|
449
|
-
function normalizeSearchUsersResponse(
|
|
510
|
+
function normalizeSearchUsersResponse(
|
|
511
|
+
value: unknown,
|
|
512
|
+
language: UiLanguage,
|
|
513
|
+
): SearchUsersData {
|
|
514
|
+
const i18n = USER_SELECTOR_PANEL_COPY[language]
|
|
450
515
|
if (!isRecord(value) || typeof value.code !== 'number') {
|
|
451
|
-
throw new Error(
|
|
516
|
+
throw new Error(i18n.invalidResponse)
|
|
452
517
|
}
|
|
453
518
|
|
|
454
519
|
if (value.code !== 0) {
|
|
455
|
-
|
|
456
|
-
|
|
520
|
+
const message = typeof value.msg === 'string' ? value.msg.trim() : ''
|
|
521
|
+
throw Object.assign(
|
|
522
|
+
new Error(message || i18n.searchFailedCode(value.code)),
|
|
523
|
+
{
|
|
524
|
+
code: String(value.code),
|
|
525
|
+
cause: value,
|
|
526
|
+
},
|
|
457
527
|
)
|
|
458
528
|
}
|
|
459
529
|
|
|
460
530
|
if (!isRecord(value.data) || !Array.isArray(value.data.users)) {
|
|
461
|
-
throw new Error(
|
|
531
|
+
throw new Error(i18n.invalidResponse)
|
|
462
532
|
}
|
|
463
533
|
|
|
464
534
|
return {
|
package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts
CHANGED
|
@@ -17,7 +17,11 @@ const recommendPayload = {
|
|
|
17
17
|
department: '研究部',
|
|
18
18
|
en_name: 'Ada',
|
|
19
19
|
id: '6882577547121393684',
|
|
20
|
-
i18n_department: {
|
|
20
|
+
i18n_department: {
|
|
21
|
+
en_us: 'Research',
|
|
22
|
+
zh_cn: '研究部',
|
|
23
|
+
ja_jp: '研究部門',
|
|
24
|
+
},
|
|
21
25
|
is_external: false,
|
|
22
26
|
},
|
|
23
27
|
'6882577547121393685': {
|
|
@@ -26,7 +30,11 @@ const recommendPayload = {
|
|
|
26
30
|
department: '外部组织',
|
|
27
31
|
en_name: 'Grace',
|
|
28
32
|
id: '6882577547121393685',
|
|
29
|
-
i18n_tenant: {
|
|
33
|
+
i18n_tenant: {
|
|
34
|
+
en_us: 'External tenant',
|
|
35
|
+
zh_cn: '外部租户',
|
|
36
|
+
ja_jp: '外部組織',
|
|
37
|
+
},
|
|
30
38
|
is_external: true,
|
|
31
39
|
},
|
|
32
40
|
},
|
|
@@ -40,9 +48,7 @@ const recommendPayload = {
|
|
|
40
48
|
|
|
41
49
|
beforeEach(() => {
|
|
42
50
|
runtime.fetch.mockReset()
|
|
43
|
-
vi.stubGlobal('
|
|
44
|
-
documentElement: { lang: 'zh-CN' },
|
|
45
|
-
})
|
|
51
|
+
vi.stubGlobal('navigator', { language: 'en-US' })
|
|
46
52
|
projectMeta.fetchProjectMeta.mockReset()
|
|
47
53
|
projectMeta.fetchProjectMeta.mockResolvedValue({
|
|
48
54
|
baseBindings: [{ baseToken: 'bascnDemo', tableIds: ['tblDemo'] }],
|
|
@@ -96,20 +102,28 @@ describe('requestUsers', () => {
|
|
|
96
102
|
it('fails before transport when the Site has no bound Base', async () => {
|
|
97
103
|
projectMeta.fetchProjectMeta.mockResolvedValue({ baseBindings: [] })
|
|
98
104
|
|
|
99
|
-
await expect(requestUsers('ada')).rejects.toThrow(
|
|
105
|
+
await expect(requestUsers('ada')).rejects.toThrow(
|
|
106
|
+
'application is not bound to a Base',
|
|
107
|
+
)
|
|
108
|
+
await expect(requestUsers('ada', 'zh')).rejects.toThrow(
|
|
109
|
+
'无法搜索成员:应用尚未绑定多维表格',
|
|
110
|
+
)
|
|
111
|
+
await expect(requestUsers('ada', 'ja')).rejects.toThrow(
|
|
112
|
+
'メンバーを検索できません:アプリにBaseが関連付けられていません',
|
|
113
|
+
)
|
|
100
114
|
expect(runtime.fetch).not.toHaveBeenCalled()
|
|
101
115
|
})
|
|
102
116
|
|
|
103
117
|
it('reports an HTTP failure without attempting to parse the body', async () => {
|
|
104
118
|
runtime.fetch.mockResolvedValue(new Response('', { status: 503 }))
|
|
105
119
|
|
|
106
|
-
await expect(requestUsers('ada')).rejects.toThrow('HTTP
|
|
120
|
+
await expect(requestUsers('ada')).rejects.toThrow('HTTP 503')
|
|
107
121
|
})
|
|
108
122
|
})
|
|
109
123
|
|
|
110
124
|
describe('normalizeRecommendUsersResponse', () => {
|
|
111
125
|
it('keeps recommendation order and maps localized names and descriptions', () => {
|
|
112
|
-
const result = normalizeRecommendUsersResponse(recommendPayload, 'en
|
|
126
|
+
const result = normalizeRecommendUsersResponse(recommendPayload, 'en')
|
|
113
127
|
|
|
114
128
|
expect(result.users).toEqual([
|
|
115
129
|
expect.objectContaining({
|
|
@@ -146,7 +160,7 @@ describe('normalizeRecommendUsersResponse', () => {
|
|
|
146
160
|
},
|
|
147
161
|
result_list: [{ token: 'token_ada', type: 0 }],
|
|
148
162
|
},
|
|
149
|
-
'en
|
|
163
|
+
'en',
|
|
150
164
|
)
|
|
151
165
|
|
|
152
166
|
expect(result.users).toEqual([
|
|
@@ -162,14 +176,44 @@ describe('normalizeRecommendUsersResponse', () => {
|
|
|
162
176
|
expect(
|
|
163
177
|
normalizeRecommendUsersResponse(
|
|
164
178
|
{ code: 0, data: recommendPayload, msg: 'ok' },
|
|
165
|
-
'zh
|
|
179
|
+
'zh',
|
|
166
180
|
).users.map((user) => user.name),
|
|
167
181
|
).toEqual(['格蕾丝', '艾达'])
|
|
168
182
|
})
|
|
169
183
|
|
|
184
|
+
it('uses Japanese names and descriptions when provided', () => {
|
|
185
|
+
const localizedPayload = {
|
|
186
|
+
...recommendPayload,
|
|
187
|
+
entities: {
|
|
188
|
+
users: {
|
|
189
|
+
...recommendPayload.entities.users,
|
|
190
|
+
'6882577547121393684': {
|
|
191
|
+
...recommendPayload.entities.users['6882577547121393684'],
|
|
192
|
+
display_name: { ja_jp: 'エイダ' },
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
const result = normalizeRecommendUsersResponse(localizedPayload, 'ja')
|
|
198
|
+
expect(result.users.map((user) => user.name)).toEqual(['Grace', 'エイダ'])
|
|
199
|
+
expect(result.users.map((user) => user.description)).toEqual([
|
|
200
|
+
'外部組織',
|
|
201
|
+
'研究部門',
|
|
202
|
+
])
|
|
203
|
+
})
|
|
204
|
+
|
|
170
205
|
it('surfaces backend business errors', () => {
|
|
171
206
|
expect(() =>
|
|
172
207
|
normalizeRecommendUsersResponse({ code: 123, msg: '无权限' }),
|
|
173
|
-
).toThrow(
|
|
208
|
+
).toThrow(/^无权限$/)
|
|
209
|
+
expect(() =>
|
|
210
|
+
normalizeRecommendUsersResponse({ code: 123, msg: '无权限' }, 'zh'),
|
|
211
|
+
).toThrow(/^无权限$/)
|
|
212
|
+
expect(() =>
|
|
213
|
+
normalizeRecommendUsersResponse({ code: 123, msg: '無権限' }, 'ja'),
|
|
214
|
+
).toThrow(/^無権限$/)
|
|
215
|
+
expect(() =>
|
|
216
|
+
normalizeRecommendUsersResponse({ code: 123, msg: ' ' }, 'en'),
|
|
217
|
+
).toThrow('Could not search members (error code 123)')
|
|
174
218
|
})
|
|
175
219
|
})
|