refactor(client): 收敛 /sidebar envelope 解析与取扩展名双实现
- api.ts:call 与 fetchUpload 里逐字相同的响应 envelope 解析 (json().catch(null) + ok/value 判断 + SidebarApiError 构造)收敛为 私有 readEnvelope<T>(),两处调用;判断顺序与错误码回退逻辑不变。 - 取扩展名双实现收敛:extOf 的实现迁至零依赖的 paths.ts(lang.ts 与 service.ts 均可达的基础模块),lang.ts re-export 保持既有导入面 (tests/file-types.spec.ts 从 lang.ts 导入 extOf);service.ts 删除 模块私有的同体 extOfPath,matchFileViewer 改用 extOf。方向说明: service.ts 不能反向 import lang.ts——lang.ts 携带全部 @codemirror/* 依赖且只经 editor 懒加载 chunk 进入浏览器,core bundle 静态引用它 会把 CodeMirror 拉进核心包(构建产物已验证:client.js / client-registry.js 中 0 处 codemirror,client-editor.js 不变)。
This commit is contained in:
+21
-18
@@ -129,6 +129,25 @@ export type TerminalDepsStatus =
|
|||||||
note?: string
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse one `/sidebar` JSON response envelope into its value. A non-ok
|
||||||
|
* status, an unparseable body, or any shape other than `{ok: true, value}`
|
||||||
|
* surfaces as {@link SidebarApiError} carrying the wire code (falling back
|
||||||
|
* to the HTTP status). Shared by the JSON api route and the raw upload
|
||||||
|
* route, whose envelopes are identical.
|
||||||
|
*/
|
||||||
|
async function readEnvelope<T>(response: Response): Promise<T> {
|
||||||
|
const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null
|
||||||
|
= await response.json().catch(() => null)
|
||||||
|
if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {
|
||||||
|
throw new SidebarApiError(
|
||||||
|
parsed?.error?.code ?? 'http',
|
||||||
|
parsed?.error?.message ?? `HTTP ${response.status}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return parsed.value as T
|
||||||
|
}
|
||||||
|
|
||||||
async function call<T>(method: string, payload: Record<string, unknown>, signal?: AbortSignal): Promise<T> {
|
async function call<T>(method: string, payload: Record<string, unknown>, signal?: AbortSignal): Promise<T> {
|
||||||
let response: Response
|
let response: Response
|
||||||
try {
|
try {
|
||||||
@@ -141,15 +160,7 @@ async function call<T>(method: string, payload: Record<string, unknown>, signal?
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new SidebarApiError('network', error instanceof Error ? error.message : String(error))
|
throw new SidebarApiError('network', error instanceof Error ? error.message : String(error))
|
||||||
}
|
}
|
||||||
const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null
|
return readEnvelope<T>(response)
|
||||||
= await response.json().catch(() => null)
|
|
||||||
if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {
|
|
||||||
throw new SidebarApiError(
|
|
||||||
parsed?.error?.code ?? 'http',
|
|
||||||
parsed?.error?.message ?? `HTTP ${response.status}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return parsed.value as T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,15 +191,7 @@ async function fetchUpload<T>(
|
|||||||
if (error instanceof DOMException && error.name === 'AbortError') throw error
|
if (error instanceof DOMException && error.name === 'AbortError') throw error
|
||||||
throw new SidebarApiError('network', error instanceof Error ? error.message : String(error))
|
throw new SidebarApiError('network', error instanceof Error ? error.message : String(error))
|
||||||
}
|
}
|
||||||
const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null
|
return readEnvelope<T>(response)
|
||||||
= await response.json().catch(() => null)
|
|
||||||
if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {
|
|
||||||
throw new SidebarApiError(
|
|
||||||
parsed?.error?.code ?? 'http',
|
|
||||||
parsed?.error?.message ?? `HTTP ${response.status}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return parsed.value as T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** One request's session scope: the conversation id plus its cwd when known. */
|
/** One request's session scope: the conversation id plus its cwd when known. */
|
||||||
|
|||||||
+5
-7
@@ -48,14 +48,12 @@ import { pascal } from '@codemirror/legacy-modes/mode/pascal'
|
|||||||
import { vb } from '@codemirror/legacy-modes/mode/vb'
|
import { vb } from '@codemirror/legacy-modes/mode/vb'
|
||||||
import { vhdl } from '@codemirror/legacy-modes/mode/vhdl'
|
import { vhdl } from '@codemirror/legacy-modes/mode/vhdl'
|
||||||
import { stex } from '@codemirror/legacy-modes/mode/stex'
|
import { stex } from '@codemirror/legacy-modes/mode/stex'
|
||||||
|
import { extOf } from './paths.ts'
|
||||||
|
|
||||||
/** The lowercased file extension of a path ('' when none). */
|
// Re-exported for the established import surface (tests import extOf from
|
||||||
export function extOf(path: string): string {
|
// this module); the implementation lives in the dependency-free paths.ts so
|
||||||
const at = path.lastIndexOf('.')
|
// core-bundle modules can share it without pulling the CodeMirror packages.
|
||||||
if (at === -1) return ''
|
export { extOf }
|
||||||
const base = path.slice(at + 1).toLowerCase()
|
|
||||||
return base.includes('/') || base.includes('\\') ? '' : base
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Language key for an extension, or null for plain text. Pure (tested). */
|
/** Language key for an extension, or null for plain text. Pure (tested). */
|
||||||
export function languageKeyForExt(ext: string): string | null {
|
export function languageKeyForExt(ext: string): string | null {
|
||||||
|
|||||||
@@ -70,3 +70,16 @@ export function baseName(path: string): string {
|
|||||||
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
||||||
return at === -1 ? path : path.slice(at + 1)
|
return at === -1 ? path : path.slice(at + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lowercased file extension of a path ('' when none). The dot must sit
|
||||||
|
* inside the last segment — a dot in a directory name is not an extension.
|
||||||
|
* Shared by the editor language mapping (lang.ts) and the viewer registry's
|
||||||
|
* extension matching (service.ts), which both live in the core bundle.
|
||||||
|
*/
|
||||||
|
export function extOf(path: string): string {
|
||||||
|
const at = path.lastIndexOf('.')
|
||||||
|
if (at === -1) return ''
|
||||||
|
const base = path.slice(at + 1).toLowerCase()
|
||||||
|
return base.includes('/') || base.includes('\\') ? '' : base
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
type SidebarSnapshot, type SidebarState, type SidebarStore, type SidebarTab,
|
type SidebarSnapshot, type SidebarState, type SidebarStore, type SidebarTab,
|
||||||
} from './state.ts'
|
} from './state.ts'
|
||||||
import { isNarrowWidth } from './breakpoints.ts'
|
import { isNarrowWidth } from './breakpoints.ts'
|
||||||
|
import { extOf } from './paths.ts'
|
||||||
import type { SessionScope } from './api.ts'
|
import type { SessionScope } from './api.ts'
|
||||||
import type { SidebarPrefs } from '../prefs-shared.ts'
|
import type { SidebarPrefs } from '../prefs-shared.ts'
|
||||||
|
|
||||||
@@ -427,14 +428,6 @@ export interface BetterSidebarService {
|
|||||||
openFile(scope: SessionScope, path: string, title?: string): void
|
openFile(scope: SessionScope, path: string, title?: string): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract the lowercase extension without leading dot from a path. */
|
|
||||||
function extOfPath(path: string): string {
|
|
||||||
const at = path.lastIndexOf('.')
|
|
||||||
if (at === -1) return ''
|
|
||||||
const base = path.slice(at + 1).toLowerCase()
|
|
||||||
return base.includes('/') || base.includes('\\') ? '' : base
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The file name of a path (both separators). */
|
/** The file name of a path (both separators). */
|
||||||
function baseNameOf(path: string): string {
|
function baseNameOf(path: string): string {
|
||||||
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
||||||
@@ -571,7 +564,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
|
|||||||
const isViewerEnabled = (id: string): boolean => store.getPrefs().viewersEnabled[id] !== false
|
const isViewerEnabled = (id: string): boolean => store.getPrefs().viewersEnabled[id] !== false
|
||||||
|
|
||||||
const matchFileViewer = (path: string, head?: Uint8Array): FileViewerDescriptor | undefined => {
|
const matchFileViewer = (path: string, head?: Uint8Array): FileViewerDescriptor | undefined => {
|
||||||
const ext = extOfPath(path)
|
const ext = extOf(path)
|
||||||
// Single pass in priority order (descending; stable for equal
|
// Single pass in priority order (descending; stable for equal
|
||||||
// priorities — insertion order). Each descriptor gets first refusal in
|
// priorities — insertion order). Each descriptor gets first refusal in
|
||||||
// its own turn: `detect` (when head bytes are available) beats its own
|
// its own turn: `detect` (when head bytes are available) beats its own
|
||||||
|
|||||||
Reference in New Issue
Block a user