Skip to content

Commit 7c1374d

Browse files
authored
Replace timeout-based HTML export completion with explicit signal (#1425)
* Fix HTML export completion and error signaling * Handle already-loaded webview when signaling export completion
1 parent 3c4351c commit 7c1374d

6 files changed

Lines changed: 70 additions & 29 deletions

File tree

src/ExportHTML.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,9 @@ export const exportHTML = async (options: IExportOptions) => {
1616
const filePath = path.join(folderPath ? folderPath : ".", file)
1717

1818
if (data) {
19-
try {
20-
await jetpack.writeAsync(filePath, data)
21-
} catch (error) {
22-
console.error(error)
23-
}
19+
await jetpack.writeAsync(filePath, data)
2420
}
2521
else if (srcFilePath) {
26-
try {
27-
await jetpack.copyAsync(srcFilePath, filePath, { overwrite: true })
28-
} catch (error) {
29-
console.error(error)
30-
}
22+
await jetpack.copyAsync(srcFilePath, filePath, { overwrite: true })
3123
}
3224
}

src/MainController.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ export default class MainController {
136136
logger,
137137
() => this.config,
138138
extensionContext.extensionPath,
139-
this.isInExport
139+
this.isInExport,
140+
this.onExportError
140141
)
141142
this.diagnostics = languages.createDiagnosticCollection('vscode-revealjs')
142143
this.configByKey = new Map(configDesc.map((d) => [d.label, d]))
@@ -159,23 +160,41 @@ export default class MainController {
159160
}
160161
//#endregion
161162

162-
// active export during 5 seconds
163-
private exportTimeout: NodeJS.Timeout | null = null
164-
public isInExport = () => this.exportTimeout != null && this.exportTimeout != undefined
163+
private exportState: {
164+
resolve: (path: string) => void
165+
reject: (error: Error) => void
166+
path: string
167+
} | null = null
168+
public isInExport = () => this.exportState !== null
169+
170+
private readonly onExportError = (error: unknown) => {
171+
if (!this.exportState) return
172+
173+
const normalizedError = error instanceof Error ? error : new Error(String(error))
174+
this.exportState.reject(new Error(`HTML export failed: ${normalizedError.message}`))
175+
this.exportState = null
176+
}
165177

166178
public shouldOpenFilemanagerAfterHTMLExport = () => this.currentContext?.configuration.openFilemanagerAfterHTMLExport ?? false
167179

168180

169181
public exportAsync = async () => {
170-
if (this.exportTimeout) {
171-
clearTimeout(this.exportTimeout)
182+
if (!this.currentContext) {
183+
throw new Error('No active markdown context to export')
184+
}
185+
186+
if (this.exportState) {
187+
this.exportState.reject(new Error('A previous HTML export was interrupted by a new export request'))
188+
this.exportState = null
172189
}
173-
if (this.currentContext) { await jetpack.removeAsync(this.currentContext.exportPath) }
174190

175-
const promise = new Promise<string>((resolve) => {
176-
this.exportTimeout = setTimeout(() => resolve(this.currentContext?.exportPath ?? ''), 5000)
191+
await jetpack.removeAsync(this.currentContext.exportPath)
192+
const exportPath = this.currentContext.exportPath
193+
194+
const promise = new Promise<string>((resolve, reject) => {
195+
this.exportState = { resolve, reject, path: exportPath }
177196
})
178-
//await commands.executeCommand(SHOW_REVEALJS_IN_BROWSER)
197+
179198
this.webViewPane ? this.refreshWebViewPane() : await commands.executeCommand(SHOW_REVEALJS)
180199

181200
return promise
@@ -264,6 +283,14 @@ export default class MainController {
264283
if (!message || typeof message !== 'object') return
265284

266285
const command = 'command' in message ? message.command : undefined
286+
if (command === 'exportComplete') {
287+
if (this.exportState) {
288+
this.exportState.resolve(this.exportState.path)
289+
this.exportState = null
290+
}
291+
return
292+
}
293+
267294
if (command !== 'slideChanged') return
268295

269296
const horizontal = 'horizontal' in message ? Number(message.horizontal) : Number.NaN
@@ -284,7 +311,7 @@ export default class MainController {
284311
if (this.webViewPane && this.currentContext) {
285312
this.startServer()
286313
this.webViewPane.title = this.currentContext.configuration.title
287-
void this.webViewPane.update(this.currentContext.uriWithPosition)
314+
void this.webViewPane.update(this.currentContext.uriWithPosition, this.isInExport())
288315
}
289316
}
290317

src/RevealContext.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export class RevealContext extends Disposable {
2929
public logger: Logger,
3030
public getConfiguration: () => Configuration,
3131
public extensionPath: string,
32-
public isInExport: () => boolean
32+
public isInExport: () => boolean,
33+
public onExportError: (error: unknown) => void = () => {}
3334
) {
3435
super()
3536
this.editor = editor
@@ -170,14 +171,15 @@ export class RevealContexts {
170171
private readonly logger: Logger,
171172
private readonly getConfiguration: () => Configuration,
172173
private readonly extensionPath: string,
173-
private readonly isInExport: () => boolean
174+
private readonly isInExport: () => boolean,
175+
private readonly onExportError: (error: unknown) => void
174176
) {
175177
this.logger = logger
176178
}
177179

178180
getOrAdd(editor: TextEditor) {
179181
if (this.innerMap.has(editor.document.uri)) return this.innerMap.get(editor.document.uri)
180-
const context = new RevealContext(editor, this.logger, this.getConfiguration, this.extensionPath, this.isInExport)
182+
const context = new RevealContext(editor, this.logger, this.getConfiguration, this.extensionPath, this.isInExport, this.onExportError)
181183

182184
context.onDidDispose(() => this.logger.info(`CONTEXT: ${editor.document.uri} disposed`))
183185

src/RevealServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ export class RevealServer extends Disposable {
178178
const opts: IExportOptions = { folderPath: exportPath, url: req.originalUrl.split('?')[0], srcFilePath: null, data: body }
179179
await exportfn(opts)
180180
} catch (error) {
181-
this.context.logger.info(`Error : ${error}`)
181+
this.context.logger.error(`Export error: ${error}`)
182+
this.context.onExportError(error)
182183
}
183184

184185
// @ts-ignore

src/WebViewPane.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ export default class WebviewPane
3636
this.webviewPanel.title = title;
3737
}
3838

39-
public async update(url:string) {
39+
public async update(url:string, sendExportSignal = false) {
4040
const parsedUrl = new URL(url)
4141
const slideHash = parsedUrl.hash || '#/'
4242
parsedUrl.hash = ''
4343

4444
const response = await fetch(parsedUrl.toString())
4545
const html = await response.text()
4646
const htmlWithBase = this.injectBaseHref(html, parsedUrl.toString())
47-
this.webviewPanel.webview.html = this.injectBridgeScript(htmlWithBase, slideHash)
47+
this.webviewPanel.webview.html = this.injectBridgeScript(htmlWithBase, slideHash, sendExportSignal)
4848
this.#onDidUpdate.fire()
4949
}
5050

@@ -57,7 +57,7 @@ export default class WebviewPane
5757
return `${baseTag}${html}`
5858
}
5959

60-
private injectBridgeScript(html: string, slideHash: string) {
60+
private injectBridgeScript(html: string, slideHash: string, sendExportSignal: boolean) {
6161
const script = `
6262
<script>
6363
(function () {
@@ -93,6 +93,18 @@ export default class WebviewPane
9393
}
9494
9595
setTimeout(postCurrentSlide, 0);
96+
97+
if (${sendExportSignal}) {
98+
const postExportComplete = () => {
99+
vscode.postMessage({ command: 'exportComplete' });
100+
};
101+
102+
if (document.readyState === 'complete') {
103+
postExportComplete();
104+
} else {
105+
window.addEventListener('load', postExportComplete, { once: true });
106+
}
107+
}
96108
}());
97109
</script>
98110
`

src/commands/exportHTML.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export const EXPORT_HTML = 'vscode-revealjs.exportHTML'
77
export type EXPORT_HTML = typeof EXPORT_HTML
88

99
export const exportHTML = (logger: Logger, startExport: () => Promise<string>, doOpenAfterExport: () => boolean) => async () => {
10-
await window.withProgress(
10+
try {
11+
await window.withProgress(
1112
{
1213
location: ProgressLocation.Notification,
1314
title: 'Export',
@@ -27,6 +28,12 @@ export const exportHTML = (logger: Logger, startExport: () => Promise<string>, d
2728
}
2829
}
2930
)
31+
} catch (error) {
32+
const message = error instanceof Error ? error.message : String(error)
33+
logger.error(`Export failed: ${message}`)
34+
void window.showErrorMessage(`RevealJS HTML export failed: ${message}`)
35+
throw error
36+
}
3037
}
3138

3239
function timeout(ms) {

0 commit comments

Comments
 (0)