@allbluecn/web-app 0.26.0 → 0.26.1
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/package.json
CHANGED
|
@@ -7,11 +7,20 @@
|
|
|
7
7
|
// 自判返回 null)。
|
|
8
8
|
// 云登录入口在侧边栏用户菜单(isLocalGuest → /login)与 /settings/sync
|
|
9
9
|
// 设置页卡片,不在壳层悬浮表单(2026-10-31 用户裁决移除右下角表单)。
|
|
10
|
+
import { useLocation } from "@tanstack/react-router"
|
|
11
|
+
|
|
10
12
|
import { DesktopSyncIndicator } from "@/components/desktop/sync-indicator"
|
|
11
13
|
import { DesktopConflictCenter } from "@/components/desktop/conflict-center"
|
|
12
14
|
import { DesktopStateListener } from "@/components/desktop/desktop-state-listener"
|
|
13
15
|
|
|
14
16
|
export function DesktopShellWidgets() {
|
|
17
|
+
const pathname = useLocation().pathname
|
|
18
|
+
// mini 快捷记录面板(/quick-capture 独立 BrowserWindow)同属
|
|
19
|
+
// DEPLOY_TARGET=desktop,Providers 全页挂载本组件 → 右下角悬浮容器
|
|
20
|
+
// (fixed right-4 bottom-4 z-50 w-80)会叠在 360×480 面板之上压住底部
|
|
21
|
+
// 提示行,面板页提前不渲染(高亮判断同款纪律:用 useLocation,禁
|
|
22
|
+
// useRouter().state.location.pathname——后者是 stale 值不随导航更新)
|
|
23
|
+
if (pathname === "/quick-capture") return null
|
|
15
24
|
return (
|
|
16
25
|
<>
|
|
17
26
|
{/* 监听器无 UI(订阅身份切换 → 缓存三件套失效),置于悬浮容器外防其
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
// 桌面托盘 mini 快捷记录面板(QuickCapturePanel)
|
|
18
|
+
// 桌面托盘 mini 快捷记录面板(QuickCapturePanel)9 用例:
|
|
19
19
|
// 1) 双 tab 渲染 + 默认 story 占位符
|
|
20
20
|
// 2) 灵感 tab 下 Enter 保存:quickCaptureStoryFn 收到 inspiration 实参
|
|
21
21
|
// 3) 保存成功:显示已记录 identifier、输入框清空、notifyQuickCapture 通知桌面壳
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// 6) Esc 隐藏窗口(bridge.window 收到 "hide"),不触发保存
|
|
25
25
|
// 7) onMiniPanelMode 下发 inspiration 切换 mode;unmount 触发退订
|
|
26
26
|
// 8) 无桥(浏览器直开):渲染与保存不抛错(可选链降级)
|
|
27
|
+
// 9) 请求在途期间再 Enter 不重复提交(防重,deferred mock 手动 resolve)
|
|
27
28
|
// 桥与 serverFn 全 mock(jsdom 无 Electron 壳;getDesktopBridge mock 同款
|
|
28
29
|
// desktop-state-listener 测试)。
|
|
29
30
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
@@ -145,4 +146,27 @@ describe('QuickCapturePanel', () => {
|
|
|
145
146
|
await user.keyboard('{Escape}')
|
|
146
147
|
expect(bridgeStub.window).not.toHaveBeenCalled()
|
|
147
148
|
})
|
|
149
|
+
|
|
150
|
+
it('请求在途期间再 Enter 不重复提交(防重,deferred mock)', async () => {
|
|
151
|
+
// 首次调用挂起(手动 resolve),往返期间第二次 Enter 应被忽略
|
|
152
|
+
let resolveFn: (v: { identifier: string; name: string }) => void = () => {}
|
|
153
|
+
quickCaptureStoryFnMock.mockImplementationOnce(
|
|
154
|
+
() =>
|
|
155
|
+
new Promise((resolve) => {
|
|
156
|
+
resolveFn = resolve
|
|
157
|
+
}),
|
|
158
|
+
)
|
|
159
|
+
const user = userEvent.setup()
|
|
160
|
+
render(<QuickCapturePanel initialMode="story" />)
|
|
161
|
+
const input = screen.getByPlaceholderText('记一个需求…') as HTMLTextAreaElement
|
|
162
|
+
await user.type(input, '想法F')
|
|
163
|
+
await user.keyboard('{Enter}')
|
|
164
|
+
expect(quickCaptureStoryFnMock).toHaveBeenCalledTimes(1)
|
|
165
|
+
await user.keyboard('{Enter}')
|
|
166
|
+
expect(quickCaptureStoryFnMock).toHaveBeenCalledTimes(1)
|
|
167
|
+
expect(input).toHaveValue('想法F')
|
|
168
|
+
resolveFn({ identifier: 'STORY-9', name: '想法F' })
|
|
169
|
+
expect(await screen.findByText('已记录 STORY-9')).toBeTruthy()
|
|
170
|
+
await waitFor(() => expect(input).toHaveValue(''))
|
|
171
|
+
})
|
|
148
172
|
})
|
|
@@ -41,6 +41,8 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
|
|
|
41
41
|
const [value, setValue] = useState("")
|
|
42
42
|
const [savedIdentifier, setSavedIdentifier] = useState<string | null>(null)
|
|
43
43
|
const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
44
|
+
// 保存防重:请求在途(value 尚未清空)期间忽略再次 Enter,防重复落库
|
|
45
|
+
const savingRef = useRef(false)
|
|
44
46
|
|
|
45
47
|
// main 侧下发面板当前 tab(托盘菜单「记需求/记灵感」点击后推送);
|
|
46
48
|
// unsubscribe 进 cleanup,防止窗口复用时叠加订阅
|
|
@@ -58,8 +60,10 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
|
|
|
58
60
|
}, [])
|
|
59
61
|
|
|
60
62
|
const save = async () => {
|
|
63
|
+
if (savingRef.current) return
|
|
61
64
|
const name = value.trim()
|
|
62
65
|
if (!name) return
|
|
66
|
+
savingRef.current = true
|
|
63
67
|
try {
|
|
64
68
|
const res = await quickCaptureStoryFn({ data: { kind: mode, name } })
|
|
65
69
|
setSavedIdentifier(res.identifier)
|
|
@@ -70,6 +74,8 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
|
|
|
70
74
|
feedbackTimerRef.current = setTimeout(() => setSavedIdentifier(null), FEEDBACK_CLEAR_MS)
|
|
71
75
|
} catch {
|
|
72
76
|
toast.error(m.quick_capture_failed())
|
|
77
|
+
} finally {
|
|
78
|
+
savingRef.current = false
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -112,7 +112,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
112
112
|
"items": [
|
|
113
113
|
{
|
|
114
114
|
"name": "@allbluecn/web-app",
|
|
115
|
-
"version": "v0.26.
|
|
115
|
+
"version": "v0.26.1",
|
|
116
116
|
"lastUpdate": "Nov 5, 2026",
|
|
117
117
|
"source": "apps/web/package.json",
|
|
118
118
|
"docsUrl": "https://github.com/allbluecn/allblue"
|