release: 发布 v0.5.0
新增共享 Preview Engine,统一 Web 连续预览、快速分页、Playwright PDF 与 Electron PDF;实现稳定前缀复用和修改位置后的增量分页,保留媒体块按文档顺序串行回填与单次重排。 完善跨端链接与桌面文档工作流:Web 受控处理锚点和 HTTP/HTTPS 外链;Desktop 支持本地路径、file URI、系统协议、多窗口、同文件单例、Markdown 当前或新窗口打开,以及聚焦时外部文件变化提示。 统一四套内置主题名称并默认使用 Typora Github;修复连续预览双滚动条、ECharts 尺寸、PDF 本地链接、围栏代码块 Typora DOM 与重复行内样式;桌面发行链强制完整重建内嵌 Web,避免安装包携带陈旧资源。 发布 Web/Compose 与 Windows NSIS/ZIP:镜像 yixiong/md-to-pdf:v0.5.0 已健康部署;NSIS SHA-256 为 60992D1FDCA513F46346C78478537EB4159D8C0E76B41ECF3CDC25BE77707D92,ZIP SHA-256 为 D74F82293FB67126E583546CBA894569EFC9A0B1B6343FAC648CCC95F1D188D8,本机安装版已升级至 v0.5.0。 验证:全项目 238 项测试通过,类型检查、生产构建和 git diff --check 通过;Web 快速/连续/精确预览、Compose、Desktop 多窗口、窗口状态、文件关联、链接与代码块均完成真实环境验收。
This commit is contained in:
+53
-451
@@ -1,79 +1,32 @@
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
ipcMain,
|
||||
Menu,
|
||||
net,
|
||||
protocol,
|
||||
screen,
|
||||
session,
|
||||
shell,
|
||||
type IpcMainInvokeEvent
|
||||
session
|
||||
} from "electron";
|
||||
import {
|
||||
access,
|
||||
mkdir,
|
||||
writeFile
|
||||
} from "node:fs/promises";
|
||||
import { access, mkdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import {
|
||||
createApplicationService,
|
||||
MAXIMUM_MARKDOWN_LENGTH,
|
||||
type ApplicationService
|
||||
} from "@md-to-pdf/application";
|
||||
import { parseThemeResourceUrl } from "./application-contract.js";
|
||||
import {
|
||||
DESKTOP_CONSUME_PENDING_MARKDOWN,
|
||||
DESKTOP_DISCARD_PENDING_MARKDOWN,
|
||||
DESKTOP_GET_THEME_CSS,
|
||||
DESKTOP_GENERATE_PDF,
|
||||
DESKTOP_LIST_THEMES,
|
||||
DESKTOP_MARKDOWN_OPENED,
|
||||
DESKTOP_OPEN_MARKDOWN,
|
||||
DESKTOP_OPEN_THEME_DIRECTORY,
|
||||
DESKTOP_REFRESH_THEMES,
|
||||
DESKTOP_RENDER_MARKDOWN,
|
||||
DESKTOP_SAVE_MARKDOWN,
|
||||
DESKTOP_SAVE_PDF,
|
||||
DESKTOP_START_NEW_MARKDOWN
|
||||
} from "./channels.js";
|
||||
import {
|
||||
parseMarkdownRenderRequest,
|
||||
parseThemeId,
|
||||
parseThemeResourceUrl
|
||||
} from "./application-contract.js";
|
||||
import {
|
||||
DESKTOP_PDF_PARTITION,
|
||||
ElectronPdfGenerator
|
||||
} from "./electron-pdf-generator.js";
|
||||
import { parsePagedDocumentPayload } from "./pdf-contract.js";
|
||||
DesktopApplicationController
|
||||
} from "./desktop-application-controller.js";
|
||||
import { DESKTOP_PDF_PARTITION } from "./electron-pdf-generator.js";
|
||||
import {
|
||||
findMarkdownFileArgument,
|
||||
isMarkdownFilePath,
|
||||
readMarkdownDocument,
|
||||
type OpenedMarkdownDocument
|
||||
isMarkdownFilePath
|
||||
} from "./markdown-file.js";
|
||||
import {
|
||||
loadWindowState,
|
||||
resolveWindowBounds,
|
||||
saveWindowState,
|
||||
type WindowState
|
||||
} from "./window-state.js";
|
||||
|
||||
const APP_SCHEME = "mdpdf";
|
||||
const APP_HOST = "bundle";
|
||||
const THEME_HOST = "theme";
|
||||
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DEFAULT_WINDOW_SIZE = { width: 1440, height: 960 };
|
||||
const MINIMUM_WINDOW_SIZE = { width: 1024, height: 720 };
|
||||
let applicationController:
|
||||
| {
|
||||
window: BrowserWindow;
|
||||
openMarkdownFile(filePath: string): Promise<void>;
|
||||
flushWindowState(): Promise<void>;
|
||||
}
|
||||
| undefined;
|
||||
let applicationController: DesktopApplicationController | undefined;
|
||||
let pendingExternalMarkdownPath = findMarkdownFileArgument(
|
||||
process.argv,
|
||||
process.cwd()
|
||||
@@ -130,29 +83,16 @@ function encodeThemeAssetPath(assetPath: string) {
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function focusApplicationWindow() {
|
||||
const window = applicationController?.window;
|
||||
if (!window || window.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.show();
|
||||
window.focus();
|
||||
}
|
||||
|
||||
function dispatchExternalMarkdown(filePath: string) {
|
||||
if (!applicationController) {
|
||||
pendingExternalMarkdownPath = filePath;
|
||||
return;
|
||||
}
|
||||
void applicationController.openMarkdownFile(filePath).catch(
|
||||
void applicationController.openExternalMarkdown(filePath).catch(
|
||||
(error: unknown) => {
|
||||
console.warn("无法打开系统传入的 Markdown 文件", error);
|
||||
}
|
||||
);
|
||||
focusApplicationWindow();
|
||||
}
|
||||
|
||||
function getDesktopThemeDirectory() {
|
||||
@@ -226,12 +166,10 @@ async function registerApplicationProtocol(
|
||||
} catch {
|
||||
return new Response("Bad request", { status: 400 });
|
||||
}
|
||||
|
||||
const target = path.resolve(webRoot, relativePath);
|
||||
if (!isContainedPath(webRoot, target)) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
await access(target);
|
||||
} catch {
|
||||
@@ -248,376 +186,6 @@ async function registerApplicationProtocol(
|
||||
]);
|
||||
}
|
||||
|
||||
function lockDownWindow(window: BrowserWindow, allowedOrigin: string) {
|
||||
window.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
||||
window.webContents.on("will-navigate", (event, targetUrl) => {
|
||||
if (new URL(targetUrl).origin !== allowedOrigin) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
const applicationSession = window.webContents.session;
|
||||
applicationSession.setPermissionCheckHandler(() => false);
|
||||
applicationSession.setPermissionRequestHandler(
|
||||
(_webContents, _permission, callback) => callback(false)
|
||||
);
|
||||
}
|
||||
|
||||
async function createApplication(
|
||||
applicationService: ApplicationService
|
||||
) {
|
||||
const developmentUrl = getCommandLineValue("web-url");
|
||||
const applicationUrl =
|
||||
developmentUrl ?? `${APP_SCHEME}://${APP_HOST}/index.html`;
|
||||
const renderUrl = new URL(
|
||||
"/preview-frame.html?target=pdf",
|
||||
applicationUrl
|
||||
).href;
|
||||
const windowStatePath = path.join(
|
||||
app.getPath("userData"),
|
||||
"window-state.json"
|
||||
);
|
||||
const savedWindowState = await loadWindowState(windowStatePath);
|
||||
const primaryWorkArea = screen.getPrimaryDisplay().workArea;
|
||||
const initialBounds = resolveWindowBounds(
|
||||
savedWindowState?.bounds,
|
||||
screen.getAllDisplays().map((display) => display.workArea),
|
||||
primaryWorkArea,
|
||||
DEFAULT_WINDOW_SIZE,
|
||||
MINIMUM_WINDOW_SIZE
|
||||
);
|
||||
|
||||
const window = new BrowserWindow({
|
||||
title: `Markdown PDF 导出器 v${app.getVersion()}`,
|
||||
icon: getApplicationIcon(),
|
||||
...initialBounds,
|
||||
minWidth: Math.min(MINIMUM_WINDOW_SIZE.width, initialBounds.width),
|
||||
minHeight: Math.min(MINIMUM_WINDOW_SIZE.height, initialBounds.height),
|
||||
autoHideMenuBar: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
preload: path.join(currentDirectory, "app-preload.cjs")
|
||||
}
|
||||
});
|
||||
let saveWindowStateTimer: NodeJS.Timeout | undefined;
|
||||
let pendingWindowStateWrite = Promise.resolve();
|
||||
let latestWindowState: WindowState = {
|
||||
version: 1,
|
||||
bounds: window.getNormalBounds(),
|
||||
maximized: window.isMaximized()
|
||||
};
|
||||
const captureWindowState = () => {
|
||||
if (window.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
latestWindowState = {
|
||||
version: 1,
|
||||
bounds: window.getNormalBounds(),
|
||||
maximized: window.isMaximized()
|
||||
};
|
||||
};
|
||||
const persistWindowState = () => {
|
||||
const state = structuredClone(latestWindowState);
|
||||
pendingWindowStateWrite = pendingWindowStateWrite
|
||||
.catch(() => undefined)
|
||||
.then(() => saveWindowState(windowStatePath, state))
|
||||
.catch((error: unknown) => {
|
||||
console.warn("无法保存窗口状态", error);
|
||||
});
|
||||
return pendingWindowStateWrite;
|
||||
};
|
||||
const scheduleWindowStateSave = () => {
|
||||
captureWindowState();
|
||||
if (saveWindowStateTimer) {
|
||||
clearTimeout(saveWindowStateTimer);
|
||||
}
|
||||
saveWindowStateTimer = setTimeout(() => {
|
||||
saveWindowStateTimer = undefined;
|
||||
void persistWindowState();
|
||||
}, 2_000);
|
||||
};
|
||||
const flushWindowState = async () => {
|
||||
if (saveWindowStateTimer) {
|
||||
clearTimeout(saveWindowStateTimer);
|
||||
saveWindowStateTimer = undefined;
|
||||
}
|
||||
captureWindowState();
|
||||
await persistWindowState();
|
||||
};
|
||||
window.on("move", scheduleWindowStateSave);
|
||||
window.on("resize", scheduleWindowStateSave);
|
||||
window.on("maximize", scheduleWindowStateSave);
|
||||
window.on("unmaximize", scheduleWindowStateSave);
|
||||
lockDownWindow(window, new URL(applicationUrl).origin);
|
||||
|
||||
const pdfGenerator = new ElectronPdfGenerator({
|
||||
renderUrl,
|
||||
preloadPath: path.join(currentDirectory, "pdf-preload.cjs")
|
||||
});
|
||||
const validateSender = (event: IpcMainInvokeEvent) =>
|
||||
event.sender === window.webContents &&
|
||||
event.senderFrame !== null &&
|
||||
new URL(event.senderFrame.url).origin ===
|
||||
new URL(applicationUrl).origin;
|
||||
let documentRoot: string | undefined;
|
||||
let currentMarkdownPath: string | undefined;
|
||||
let pendingOpenedMarkdown:
|
||||
| {
|
||||
document: OpenedMarkdownDocument;
|
||||
filePath: string;
|
||||
}
|
||||
| undefined;
|
||||
const queueOpenedMarkdown = async (filePath: string) => {
|
||||
const opened = await readMarkdownDocument(
|
||||
filePath,
|
||||
MAXIMUM_MARKDOWN_LENGTH
|
||||
);
|
||||
pendingOpenedMarkdown = {
|
||||
document: opened,
|
||||
filePath
|
||||
};
|
||||
window.webContents.send(DESKTOP_MARKDOWN_OPENED);
|
||||
};
|
||||
|
||||
ipcMain.handle(DESKTOP_OPEN_MARKDOWN, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面文件请求");
|
||||
}
|
||||
const selection = await dialog.showOpenDialog(window, {
|
||||
title: "打开 Markdown",
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{ name: "Markdown 文件", extensions: ["md", "markdown"] }
|
||||
]
|
||||
});
|
||||
const filePath = selection.filePaths[0];
|
||||
if (selection.canceled || !filePath) {
|
||||
return undefined;
|
||||
}
|
||||
const opened = await readMarkdownDocument(
|
||||
filePath,
|
||||
MAXIMUM_MARKDOWN_LENGTH
|
||||
);
|
||||
documentRoot = path.dirname(filePath);
|
||||
currentMarkdownPath = filePath;
|
||||
return opened;
|
||||
});
|
||||
ipcMain.handle(DESKTOP_CONSUME_PENDING_MARKDOWN, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面文件请求");
|
||||
}
|
||||
const pending = pendingOpenedMarkdown;
|
||||
pendingOpenedMarkdown = undefined;
|
||||
if (!pending) {
|
||||
return undefined;
|
||||
}
|
||||
currentMarkdownPath = pending.filePath;
|
||||
documentRoot = path.dirname(pending.filePath);
|
||||
return pending.document;
|
||||
});
|
||||
ipcMain.handle(DESKTOP_DISCARD_PENDING_MARKDOWN, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面文件请求");
|
||||
}
|
||||
pendingOpenedMarkdown = undefined;
|
||||
});
|
||||
ipcMain.handle(DESKTOP_START_NEW_MARKDOWN, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面文件请求");
|
||||
}
|
||||
currentMarkdownPath = undefined;
|
||||
documentRoot = undefined;
|
||||
pendingOpenedMarkdown = undefined;
|
||||
});
|
||||
ipcMain.handle(
|
||||
DESKTOP_SAVE_MARKDOWN,
|
||||
async (
|
||||
event,
|
||||
unsafeFileName: unknown,
|
||||
unsafeMarkdown: unknown
|
||||
) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面文件请求");
|
||||
}
|
||||
if (
|
||||
typeof unsafeFileName !== "string" ||
|
||||
unsafeFileName.length === 0 ||
|
||||
unsafeFileName.length > 500 ||
|
||||
typeof unsafeMarkdown !== "string" ||
|
||||
unsafeMarkdown.length > MAXIMUM_MARKDOWN_LENGTH
|
||||
) {
|
||||
throw new Error("Markdown 保存参数无效");
|
||||
}
|
||||
|
||||
let targetPath = currentMarkdownPath;
|
||||
if (!targetPath) {
|
||||
const suggestedName = /\.(?:md|markdown)$/iu.test(
|
||||
path.basename(unsafeFileName)
|
||||
)
|
||||
? path.basename(unsafeFileName)
|
||||
: `${path.basename(unsafeFileName)}.md`;
|
||||
const selection = await dialog.showSaveDialog(window, {
|
||||
title: "保存 Markdown",
|
||||
defaultPath: path.join(
|
||||
app.getPath("documents"),
|
||||
suggestedName
|
||||
),
|
||||
filters: [
|
||||
{ name: "Markdown 文件", extensions: ["md", "markdown"] }
|
||||
]
|
||||
});
|
||||
if (selection.canceled || !selection.filePath) {
|
||||
return undefined;
|
||||
}
|
||||
targetPath = /\.(?:md|markdown)$/iu.test(selection.filePath)
|
||||
? selection.filePath
|
||||
: `${selection.filePath}.md`;
|
||||
}
|
||||
|
||||
await writeFile(targetPath, unsafeMarkdown, "utf8");
|
||||
currentMarkdownPath = targetPath;
|
||||
documentRoot = path.dirname(targetPath);
|
||||
return { fileName: path.basename(targetPath) };
|
||||
}
|
||||
);
|
||||
ipcMain.handle(DESKTOP_OPEN_THEME_DIRECTORY, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面主题请求");
|
||||
}
|
||||
const themeDirectory = getDesktopThemeDirectory();
|
||||
await mkdir(themeDirectory, { recursive: true });
|
||||
const errorMessage = await shell.openPath(themeDirectory);
|
||||
if (errorMessage) {
|
||||
throw new Error(`无法打开自定义主题目录:${errorMessage}`);
|
||||
}
|
||||
return themeDirectory;
|
||||
});
|
||||
ipcMain.handle(DESKTOP_REFRESH_THEMES, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面主题请求");
|
||||
}
|
||||
applicationService.invalidateThemes();
|
||||
});
|
||||
ipcMain.handle(
|
||||
DESKTOP_RENDER_MARKDOWN,
|
||||
async (event, unsafeRequest: unknown) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面渲染请求");
|
||||
}
|
||||
return applicationService.render(
|
||||
parseMarkdownRenderRequest(unsafeRequest),
|
||||
documentRoot ? { localRoot: documentRoot } : {}
|
||||
);
|
||||
}
|
||||
);
|
||||
ipcMain.handle(DESKTOP_LIST_THEMES, async (event) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面主题请求");
|
||||
}
|
||||
return applicationService.listThemes();
|
||||
});
|
||||
ipcMain.handle(
|
||||
DESKTOP_GET_THEME_CSS,
|
||||
async (event, unsafeThemeId: unknown) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面主题请求");
|
||||
}
|
||||
const css = await applicationService.getThemeCss(
|
||||
parseThemeId(unsafeThemeId)
|
||||
);
|
||||
if (css === undefined) {
|
||||
throw new Error("未找到指定主题");
|
||||
}
|
||||
return css;
|
||||
}
|
||||
);
|
||||
ipcMain.handle(
|
||||
DESKTOP_GENERATE_PDF,
|
||||
async (event, unsafePayload: unknown) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面 PDF 请求");
|
||||
}
|
||||
const payload = parsePagedDocumentPayload(unsafePayload);
|
||||
const result = await pdfGenerator.generate(payload);
|
||||
return {
|
||||
pdf: result.pdf,
|
||||
pageCount: result.pageCount,
|
||||
echartsErrors: result.echartsErrors,
|
||||
mermaidErrors: result.mermaidErrors
|
||||
};
|
||||
}
|
||||
);
|
||||
ipcMain.handle(
|
||||
DESKTOP_SAVE_PDF,
|
||||
async (
|
||||
event,
|
||||
unsafeFileName: unknown,
|
||||
unsafePdf: unknown
|
||||
) => {
|
||||
if (!validateSender(event)) {
|
||||
throw new Error("拒绝未授权的桌面 PDF 保存请求");
|
||||
}
|
||||
if (
|
||||
typeof unsafeFileName !== "string" ||
|
||||
unsafeFileName.length === 0 ||
|
||||
unsafeFileName.length > 500 ||
|
||||
!(unsafePdf instanceof Uint8Array) ||
|
||||
unsafePdf.byteLength === 0 ||
|
||||
unsafePdf.byteLength > 200 * 1024 * 1024
|
||||
) {
|
||||
throw new Error("PDF 保存参数无效");
|
||||
}
|
||||
|
||||
const suggestedName = path.basename(unsafeFileName).endsWith(".pdf")
|
||||
? path.basename(unsafeFileName)
|
||||
: `${path.basename(unsafeFileName)}.pdf`;
|
||||
const selection = await dialog.showSaveDialog(window, {
|
||||
title: "导出 PDF",
|
||||
defaultPath: path.join(app.getPath("documents"), suggestedName),
|
||||
filters: [{ name: "PDF 文件", extensions: ["pdf"] }]
|
||||
});
|
||||
if (selection.canceled || !selection.filePath) {
|
||||
return false;
|
||||
}
|
||||
await writeFile(selection.filePath, unsafePdf);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
window.once("ready-to-show", () => {
|
||||
if (savedWindowState?.maximized) {
|
||||
window.maximize();
|
||||
}
|
||||
window.show();
|
||||
});
|
||||
window.on("close", () => {
|
||||
void flushWindowState();
|
||||
});
|
||||
window.on("closed", () => {
|
||||
ipcMain.removeHandler(DESKTOP_OPEN_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_CONSUME_PENDING_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_DISCARD_PENDING_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_START_NEW_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_SAVE_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_OPEN_THEME_DIRECTORY);
|
||||
ipcMain.removeHandler(DESKTOP_REFRESH_THEMES);
|
||||
ipcMain.removeHandler(DESKTOP_RENDER_MARKDOWN);
|
||||
ipcMain.removeHandler(DESKTOP_LIST_THEMES);
|
||||
ipcMain.removeHandler(DESKTOP_GET_THEME_CSS);
|
||||
ipcMain.removeHandler(DESKTOP_GENERATE_PDF);
|
||||
ipcMain.removeHandler(DESKTOP_SAVE_PDF);
|
||||
void pdfGenerator.close();
|
||||
});
|
||||
await window.loadURL(applicationUrl);
|
||||
return {
|
||||
window,
|
||||
openMarkdownFile: queueOpenedMarkdown,
|
||||
flushWindowState
|
||||
};
|
||||
}
|
||||
|
||||
const hasSingleInstanceLock = app.requestSingleInstanceLock();
|
||||
let windowStateReadyToQuit = false;
|
||||
|
||||
@@ -631,8 +199,8 @@ if (!hasSingleInstanceLock) {
|
||||
);
|
||||
if (filePath) {
|
||||
dispatchExternalMarkdown(filePath);
|
||||
} else {
|
||||
focusApplicationWindow();
|
||||
} else if (!applicationController?.focusMostRecentWindow()) {
|
||||
void applicationController?.createWindow();
|
||||
}
|
||||
});
|
||||
app.on("open-file", (event, filePath) => {
|
||||
@@ -641,6 +209,11 @@ if (!hasSingleInstanceLock) {
|
||||
dispatchExternalMarkdown(filePath);
|
||||
}
|
||||
});
|
||||
app.on("activate", () => {
|
||||
if (!applicationController?.focusMostRecentWindow()) {
|
||||
void applicationController?.createWindow();
|
||||
}
|
||||
});
|
||||
app.on("before-quit", (event) => {
|
||||
if (windowStateReadyToQuit || !applicationController) {
|
||||
return;
|
||||
@@ -649,8 +222,14 @@ if (!hasSingleInstanceLock) {
|
||||
void applicationController.flushWindowState().finally(() => {
|
||||
windowStateReadyToQuit = true;
|
||||
app.quit();
|
||||
setTimeout(() => {
|
||||
windowStateReadyToQuit = false;
|
||||
}, 1_000);
|
||||
});
|
||||
});
|
||||
app.on("will-quit", () => {
|
||||
void applicationController?.closeResources();
|
||||
});
|
||||
app
|
||||
.whenReady()
|
||||
.then(async () => {
|
||||
@@ -658,18 +237,41 @@ if (!hasSingleInstanceLock) {
|
||||
if (process.platform === "win32") {
|
||||
app.setAppUserModelId("com.md-to-pdf.desktop");
|
||||
}
|
||||
await mkdir(getDesktopThemeDirectory(), { recursive: true });
|
||||
const themeDirectory = getDesktopThemeDirectory();
|
||||
await mkdir(themeDirectory, { recursive: true });
|
||||
const applicationService = createDesktopApplicationService();
|
||||
await registerApplicationProtocol(applicationService);
|
||||
applicationController =
|
||||
await createApplication(applicationService);
|
||||
|
||||
const developmentUrl = getCommandLineValue("web-url");
|
||||
const applicationUrl =
|
||||
developmentUrl ?? `${APP_SCHEME}://${APP_HOST}/index.html`;
|
||||
applicationController = new DesktopApplicationController({
|
||||
applicationService,
|
||||
applicationUrl,
|
||||
renderUrl: new URL(
|
||||
"/preview-frame.html?target=pdf",
|
||||
applicationUrl
|
||||
).href,
|
||||
preloadPath: path.join(
|
||||
currentDirectory,
|
||||
"app-preload.cjs"
|
||||
),
|
||||
pdfPreloadPath: path.join(
|
||||
currentDirectory,
|
||||
"pdf-preload.cjs"
|
||||
),
|
||||
iconPath: getApplicationIcon(),
|
||||
themeDirectory,
|
||||
windowStatePath: path.join(
|
||||
app.getPath("userData"),
|
||||
"window-state.json"
|
||||
)
|
||||
});
|
||||
await applicationController.initialize();
|
||||
|
||||
const startupMarkdownPath = pendingExternalMarkdownPath;
|
||||
pendingExternalMarkdownPath = undefined;
|
||||
if (startupMarkdownPath) {
|
||||
await applicationController.openMarkdownFile(
|
||||
startupMarkdownPath
|
||||
);
|
||||
}
|
||||
await applicationController.createWindow(startupMarkdownPath);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error("桌面应用启动失败", error);
|
||||
|
||||
Reference in New Issue
Block a user