MCPcopy
hub / github.com/audreyt/ethercalc / openLegacyExportDialog

Function openLegacyExportDialog

packages/client/src/boot.ts:112–233  ·  view source on GitHub ↗
(host: BootHost)

Source from the content-addressed store, hash-verified

110}
111
112export function openLegacyExportDialog(host: BootHost): void {
113 const room = host.SocialCalc._room ?? '';
114 if (!room) return;
115
116 const vex =
117 host.vex ??
118 (
119 typeof window !== 'undefined'
120 ? (window as Window & { vex?: BootHost['vex'] }).vex
121 : undefined
122 );
123 if (!vex?.dialog?.open) return;
124
125 // `window.parent.location` throws a SecurityError in cross-origin
126 // iframe embeds (Sandstorm, notion integrations, etc.). Fall back to
127 // the current document's location when that happens — the export-URL
128 // builder handles both shapes.
129 const readParentLocation = (): { href?: string; pathname: string } | undefined => {
130 if (host.parent?.location) return host.parent.location;
131 if (typeof window === 'undefined') return undefined;
132 try {
133 return window.parent.location;
134 } catch {
135 return undefined;
136 }
137 };
138 const parentLocation =
139 readParentLocation() ?? { pathname: host.location.pathname };
140 const multiRows =
141 (host as BootHost & { __MULTI__?: { rows?: unknown[] } }).__MULTI__?.rows ??
142 (typeof window !== 'undefined'
143 ? (window as Window & { __MULTI__?: { rows?: unknown[] } }).__MULTI__?.rows
144 : undefined);
145 // Empty `rows: []` is truthy — must check length (#232 viewer export).
146 const isMultiple = (multiRows?.length ?? 0) > 0 || /\.[1-9]\d*$/.test(room);
147 // Production path: synthetic `<a>` click. `window.open()` is
148 // popup-blocked in Chrome when called from inside the vex-dialog
149 // button handler (it's one async tick removed from the direct user
150 // click, so Chrome's user-activation window has lapsed). Anchor
151 // clicks don't hit the popup blocker, and they also honor the
152 // server's `Content-Disposition` header: CSV/XLSX/ODS download
153 // (server sets attachment), HTML opens inline (server doesn't).
154 //
155 // Tests override via `host.__exportOpen`; we deliberately do NOT
156 // fall back to `host.open` because in production `host === window`
157 // and `window.open` is the popup-blocked path we're avoiding.
158 // Production download path.
159 //
160 // Why `fetch → blob → object URL → anchor click` instead of a plain
161 // anchor pointing at the server URL: Chrome's "automatic downloads"
162 // security policy blocks same-origin navigation/download anchors
163 // that fire after any async hop (our vex dialog button -> callback
164 // -> openFormat is two ticks past the direct user click, so the
165 // user-activation gate has already closed). Pulling the bytes via
166 // fetch and then clicking an anchor that points at a blob: URL
167 // bypasses that gate — the click is treated as a direct save of
168 // local data, not a new navigation. This works uniformly for all
169 // four formats (xlsx/ods are binary, csv/html are text) and

Callers 1

Calls 7

readParentLocationFunction · 0.85
deriveFilenameFunction · 0.85
openFormatFunction · 0.85
appendChildMethod · 0.80
openMethod · 0.80
removeChildMethod · 0.65
fetchFunction · 0.50

Tested by

no test coverage detected