Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
134 lines
3.3 KiB
TypeScript
134 lines
3.3 KiB
TypeScript
import { CustomKeyBindings } from './models';
|
|
|
|
const SYNC_VERSION_FIELD = '__netcattySyncVersion';
|
|
const SYNC_ORIGIN_FIELD = '__netcattySyncOrigin';
|
|
|
|
export interface CustomKeyBindingsStorageRecord {
|
|
bindings: CustomKeyBindings;
|
|
version: number;
|
|
origin: string;
|
|
}
|
|
|
|
export const serializeCustomKeyBindings = (bindings: CustomKeyBindings): string =>
|
|
JSON.stringify(bindings);
|
|
|
|
export const areCustomKeyBindingsEqual = (a: CustomKeyBindings, b: CustomKeyBindings): boolean =>
|
|
serializeCustomKeyBindings(a) === serializeCustomKeyBindings(b);
|
|
|
|
export const parseCustomKeyBindingsStorageRecord = (
|
|
value: unknown,
|
|
): CustomKeyBindingsStorageRecord | null => {
|
|
let candidate = value;
|
|
if (typeof candidate === 'string') {
|
|
try {
|
|
candidate = JSON.parse(candidate);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!candidate || typeof candidate !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
const record = candidate as Record<string, unknown>;
|
|
if (
|
|
typeof record.version === 'number' &&
|
|
typeof record.origin === 'string' &&
|
|
record.bindings &&
|
|
typeof record.bindings === 'object'
|
|
) {
|
|
return {
|
|
version: record.version,
|
|
origin: record.origin,
|
|
bindings: record.bindings as CustomKeyBindings,
|
|
};
|
|
}
|
|
|
|
if (
|
|
typeof record[SYNC_VERSION_FIELD] === 'number' &&
|
|
typeof record[SYNC_ORIGIN_FIELD] === 'string' &&
|
|
record.bindings &&
|
|
typeof record.bindings === 'object'
|
|
) {
|
|
return {
|
|
version: record[SYNC_VERSION_FIELD] as number,
|
|
origin: record[SYNC_ORIGIN_FIELD] as string,
|
|
bindings: record.bindings as CustomKeyBindings,
|
|
};
|
|
}
|
|
|
|
return {
|
|
version: 0,
|
|
origin: 'legacy',
|
|
bindings: candidate as CustomKeyBindings,
|
|
};
|
|
};
|
|
|
|
export const serializeCustomKeyBindingsStorageRecord = (
|
|
record: CustomKeyBindingsStorageRecord,
|
|
): string =>
|
|
JSON.stringify({
|
|
[SYNC_VERSION_FIELD]: record.version,
|
|
[SYNC_ORIGIN_FIELD]: record.origin,
|
|
bindings: record.bindings,
|
|
});
|
|
|
|
export const nextCustomKeyBindingsSyncVersion = (
|
|
currentVersion: number,
|
|
now: number = Date.now(),
|
|
): number => Math.max(now, currentVersion + 1);
|
|
|
|
export const shouldApplyIncomingCustomKeyBindingsRecord = (
|
|
current: Pick<CustomKeyBindingsStorageRecord, 'version' | 'origin'>,
|
|
incoming: Pick<CustomKeyBindingsStorageRecord, 'version' | 'origin'>,
|
|
): boolean => {
|
|
if (incoming.version !== current.version) {
|
|
return incoming.version > current.version;
|
|
}
|
|
|
|
return incoming.origin > current.origin;
|
|
};
|
|
|
|
export const updateCustomKeyBinding = (
|
|
bindings: CustomKeyBindings,
|
|
bindingId: string,
|
|
scheme: 'mac' | 'pc',
|
|
newKey: string,
|
|
): CustomKeyBindings => ({
|
|
...bindings,
|
|
[bindingId]: {
|
|
...bindings[bindingId],
|
|
[scheme]: newKey,
|
|
},
|
|
});
|
|
|
|
export const resetCustomKeyBinding = (
|
|
bindings: CustomKeyBindings,
|
|
bindingId: string,
|
|
scheme?: 'mac' | 'pc',
|
|
): CustomKeyBindings => {
|
|
if (!scheme) {
|
|
const { [bindingId]: _removed, ...rest } = bindings;
|
|
return rest;
|
|
}
|
|
|
|
const existing = bindings[bindingId];
|
|
if (!existing) {
|
|
return bindings;
|
|
}
|
|
|
|
const nextBinding = { ...existing };
|
|
delete nextBinding[scheme];
|
|
|
|
if (Object.keys(nextBinding).length === 0) {
|
|
const { [bindingId]: _removed, ...rest } = bindings;
|
|
return rest;
|
|
}
|
|
|
|
return {
|
|
...bindings,
|
|
[bindingId]: nextBinding,
|
|
};
|
|
};
|