ethercalc
Version:
Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)
225 lines (203 loc) • 7.52 kB
text/typescript
/**
* Types for the bits of `window.SocialCalc` we touch from the new client.
*
* Scope is intentionally loose — internals of the sheet model stay `unknown`
* because the full SocialCalc typedefs are a multi-phase project on their
* own. We only describe what the ported `player-*.ls` files actually read
* from or write to. When a field is only used as an opaque handle (event
* target / dictionary key) we keep it `unknown` or `Record<string, unknown>`.
*
* NOTE: the legacy `<script src="/static/socialcalc.js">` tag loads SocialCalc
* onto `window` before our bundle runs. External integrations (Drupal
* sheetnode, etc., see AGENTS.md §7 item 10) reach into the same globals,
* so widening a field here can be a breaking change.
*/
// ─── Callback / event shapes ─────────────────────────────────────────────
/**
* Broadcast callback signature — the single public surface we preserve.
*
* `type` is one of the client→server message discriminators in
* `packages/shared/src/messages.ts`, plus the internal `ask.ecell` channel
* used by presence. `data` is merged into the payload; the adapter fills in
* `room`, `user`, `auth` automatically.
*/
export type BroadcastFn = (type: string, data?: Record<string, unknown>) => void;
/** Cursor coordinates as SocialCalc gives them to us. */
export interface ECellInfo {
coord: string;
row: number;
col: number;
}
export interface CellElement {
element: {
className: string;
};
}
export interface EditorRange {
hasrange: boolean;
top: number;
bottom: number;
left: number;
right: number;
}
export interface EditorContext {
highlights: Record<string, string>;
sheetobj: {
CreateSheetSave: () => string;
ScheduleSheetCommands: (
cmd: string,
saveundo: boolean,
isRemote: boolean,
) => void;
};
cellskip: Record<string, string | undefined>;
}
export interface EditorObject {
ecell: ECellInfo;
context: EditorContext;
range2: EditorRange;
cellhandles: { ShowCellHandles: (show: boolean) => void };
MoveECellCallback: Record<string, (editor: EditorObject) => void>;
StatusCallback: Record<
string,
{ func: (editor: EditorObject, kind: string, arg: unknown, params: unknown) => void; params: unknown }
>;
SettingsCallbacks: Record<string, SettingsCallback>;
SetECellHeaders: (kind: string) => void;
UpdateCellCSS: (cell: CellElement | undefined, row: number, col: number) => void;
EnsureECellVisible: () => void;
busy: boolean;
ensureecell: boolean;
}
export interface SettingsCallback {
save: (editor: EditorObject, setting: string) => string;
load: (editor: EditorObject, setting: string, line: string, flags: unknown) => void | boolean;
}
/** `SocialCalc.Sheet` prototype methods we override. */
export interface SheetProto {
ScheduleSheetCommands?: (
this: SheetObject,
cmdstr: string,
saveundo: boolean,
isRemote: boolean,
) => void;
}
export interface SheetObject {
_room?: string;
ScheduleSheetCommands: (cmdstr: string, saveundo: boolean, isRemote: boolean) => void;
ResetSheet: () => void;
cells: Record<string, unknown>;
recalconce?: boolean;
}
export interface SpreadsheetLike {
editor: EditorObject;
sheet: SheetObject;
context: { sheetobj: SheetObject };
ExecuteCommand?: (cmd: string, args?: string) => void;
formDataViewer?: SpreadsheetLike & { _room: string; loaded: boolean };
currentTab?: string;
tabnums?: Record<string, number | undefined>;
tabs?: unknown[];
views?: Record<string, unknown>;
ExportCallback?: (s: unknown) => void;
InitializeSpreadsheetViewer?: (id: string, a: number, b: number, c: number) => void;
InitializeSpreadsheetControl?: (id: string, a: number, b: number, c: number) => void;
DoOnResize?: () => void;
ParseSheetSave: (save: string) => void;
DecodeSpreadsheetSave: (save: string) => DecodedSpreadsheet | undefined;
}
export interface DecodedSpreadsheet {
sheet?: { start: number; end: number };
edit?: { start: number; end: number };
}
export interface SocialCalcGlobal {
// Core state fields owned by the player layer.
_username?: string;
_room?: string;
_auth?: string | undefined;
_app?: boolean | undefined;
_view?: boolean | undefined;
hadSnapshot?: boolean;
isConnected?: boolean;
requestParams?: Record<string, string>;
/**
* Sanitiser hook for `valueformat===text-html` cells. Installed at boot by
* `installSecurityPolicy` for backward compatibility with cached
* pre-3.1.0 `static/socialcalc.js` assets. SocialCalc 3.1.0's native
* security model uses `Callbacks.untrustedContent` +
* `Callbacks.securityPolicy.sanitizeHtml` instead. See
* `src/sanitize-html.ts` and `scripts/build-assets.ts`.
*/
sanitizeHTML?: (raw: string) => string;
// Constants + generic API.
Constants: Record<string, string>;
Callbacks: {
broadcast?: BroadcastFn;
untrustedContent?: boolean;
securityPolicy?: {
sanitizeHtml?: (html: string) => string;
allowedUrlSchemes?: string[];
allowedDataMimeTypes?: string[];
};
[k: string]: unknown;
};
RecalcInfo: {
LoadSheet?: (ref: string) => void;
LoadSheetCache?: Record<string, string>;
};
Formula?: { SheetCache?: { sheets?: Record<string, unknown> } };
Keyboard?: { passThru?: boolean };
CurrentSpreadsheetControlObject?: SpreadsheetLike;
// Ctors referenced by main.ts.
SpreadsheetControl?: new () => SpreadsheetLike;
SpreadsheetViewer?: new () => SpreadsheetLike;
Cell?: new (coord: string) => { displaystring?: string };
// Hook targets + their originals.
DoPositionCalculations?: (...args: unknown[]) => unknown;
OrigDoPositionCalculations?: (...args: unknown[]) => unknown;
LoadEditorSettings?: (editor: EditorObject, str: string, flags: unknown) => void;
OrigLoadEditorSettings?: (editor: EditorObject, str: string, flags: unknown) => void;
SizeSSDiv?: (spreadsheet: { parentNode?: unknown } | undefined) => void;
OrigSizeSSDiv?: (spreadsheet: { parentNode?: unknown } | undefined) => void;
ScheduleSheetCommands?: (
sheet: SheetObject,
cmdstr: string,
saveundo: boolean,
isRemote: boolean,
) => void;
OrigScheduleSheetCommands?: (
sheet: SheetObject,
cmdstr: string,
saveundo: boolean,
isRemote: boolean,
) => void;
MoveECell?: (editor: EditorObject, newcell: string) => string;
GetEditorCellElement?: (
editor: EditorObject,
row: number,
col: number,
) => CellElement | undefined;
coordToCr?: (coord: string) => ECellInfo;
EditorSheetStatusCallback?: (a: unknown, kind: string, arg: unknown, editor: EditorObject) => void;
RecalcLoadedSheet?: (room: string, sheetdata: string, recalc: boolean) => void;
// `SocialCalc.Sheet` is used as a class — we only need to access `.prototype`.
Sheet?: { prototype: SheetProto };
}
export interface EtherCalcGlobal {
_room?: string;
[k: string]: unknown;
}
declare global {
interface Window {
SocialCalc?: SocialCalcGlobal;
EtherCalc?: EtherCalcGlobal;
CryptoJS?: { MD5: (s: string) => { toString: () => string } };
__MULTI__?: { rows?: Array<{ link: string; title: string }> };
spreadsheet?: SpreadsheetLike;
addmsg?: (msg: string, joined?: boolean) => void;
doresize?: () => void;
GraphOnClick?: (s: SpreadsheetLike, t?: unknown) => void;
DoGraph?: (help: boolean, resize: boolean) => void;
}
}
export {};