Compare commits

...

2 Commits

Author SHA1 Message Date
root 56400805f4 Add article export to Word, PDF, and Markdown
Desktop Client Build / Resolve Build Metadata (push) Successful in 24s
Frontend CI / Frontend (push) Successful in 5m26s
Desktop Client Build / Build Desktop Client (push) Successful in 22m50s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 30s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:59:19 +08:00
root 53ba8ff4cf Add browser toolbar to auth windows 2026-05-21 23:57:20 +08:00
9 changed files with 2105 additions and 62 deletions
+3
View File
@@ -23,7 +23,10 @@
"@vueuse/core": "^14.2.1",
"ant-design-vue": "^4.2.6",
"dayjs": "^1.11.20",
"docx": "^9.6.1",
"dompurify": "^3.4.2",
"html2canvas": "^1.4.1",
"jspdf": "^4.2.1",
"markdown-it": "^14.1.1",
"nanoid": "^5.1.7",
"pinia": "^3.0.4",
+16
View File
@@ -1078,6 +1078,22 @@ const enUS = {
noContent: 'This article does not have generated content yet.',
editor: {
publish: 'Publish',
download: 'Download',
downloadFormats: {
word: 'Word',
pdf: 'PDF',
markdown: 'Markdown',
},
downloadMessages: {
success: 'Download started.',
failed: 'Export failed. Please try again.',
},
markdownExportConfirm: {
title: 'Download Markdown',
description:
'Images will be removed from the Markdown file. Text content such as headings, tables, and links will be kept.',
confirm: 'Download',
},
titlePlaceholder: 'Enter article title',
leaveConfirm: {
title: 'Unsaved changes',
+15
View File
@@ -1025,6 +1025,21 @@ const zhCN = {
noContent: "当前文章还没有正文内容",
editor: {
publish: "发布",
download: "下载",
downloadFormats: {
word: "Word",
pdf: "PDF",
markdown: "Markdown",
},
downloadMessages: {
success: "文件已开始下载",
failed: "导出失败,请稍后重试",
},
markdownExportConfirm: {
title: "下载 Markdown",
description: "Markdown 文件将去除正文图片,仅保留标题、文字、表格和链接等文本内容。",
confirm: "确认下载",
},
titlePlaceholder: "请输入文章标题",
leaveConfirm: {
title: "当前文章还没保存",
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,11 @@
<script setup lang="ts">
import {
CloseOutlined,
DownOutlined,
DownloadOutlined,
FileMarkdownOutlined,
FilePdfOutlined,
FileWordOutlined,
LeftOutlined,
SafetyCertificateOutlined,
SearchOutlined,
@@ -15,6 +20,12 @@ import { useRoute, useRouter } from 'vue-router'
import ArticleEditorCanvas from '@/components/ArticleEditorCanvas.vue'
import PublishArticleModal from '@/components/PublishArticleModal.vue'
import { articlesApi, complianceApi } from '@/lib/api'
import {
downloadArticleDocument,
downloadArticlePdf,
hasArticleMarkdownImages,
type ArticleExportFormat,
} from '@/lib/article-export'
import { formatError } from '@/lib/errors'
import { useCompanyStore } from '@/stores/company'
@@ -34,10 +45,12 @@ const markdown = ref('')
const initialTitle = ref('')
const initialMarkdown = ref('')
const leaveModalOpen = ref(false)
const markdownExportConfirmOpen = ref(false)
const publishModalOpen = ref(false)
const compliancePanelOpen = ref(false)
const editorComplianceResult = ref<ComplianceCheckResult | null>(null)
const editorCanvasRef = ref<EditorCanvasPublic | null>(null)
const exportLoading = ref<ArticleExportFormat | null>(null)
const detailQuery = useQuery({
queryKey: computed(() => ['articles', 'detail', companyStore.currentBrandId, articleId.value]),
@@ -213,6 +226,63 @@ async function handleComplianceCheck(): Promise<void> {
}
}
async function handleDownload(format: ArticleExportFormat): Promise<void> {
if (!detail.value || exportLoading.value) {
return
}
if (format === 'markdown') {
if (hasArticleMarkdownImages(markdown.value)) {
markdownExportConfirmOpen.value = true
return
}
await runDownload(format)
return
}
await runDownload(format)
}
async function handleConfirmMarkdownDownload(): Promise<void> {
markdownExportConfirmOpen.value = false
await runDownload('markdown')
}
function handleCancelMarkdownDownload(): void {
markdownExportConfirmOpen.value = false
}
async function runDownload(format: ArticleExportFormat): Promise<void> {
if (!detail.value || exportLoading.value) {
return
}
exportLoading.value = format
try {
const options = {
title: title.value,
markdown: markdown.value,
untitledLabel: t('article.untitled'),
}
if (format === 'pdf') {
await downloadArticlePdf(options)
message.success(t('article.editor.downloadMessages.success'))
return
}
await downloadArticleDocument(format, options)
message.success(t('article.editor.downloadMessages.success'))
} catch (error) {
message.error(t('article.editor.downloadMessages.failed'))
} finally {
window.setTimeout(() => {
exportLoading.value = null
}, 200)
}
}
function closeCompliancePanel(): void {
compliancePanelOpen.value = false
}
@@ -448,6 +518,29 @@ onBeforeUnmount(() => {
</button>
<div class="article-editor-view__actions">
<a-dropdown :trigger="['click']" placement="bottomRight">
<a-button :disabled="!detail || Boolean(exportLoading)" :loading="Boolean(exportLoading)">
<template #icon><DownloadOutlined /></template>
{{ t('article.editor.download') }}
<DownOutlined v-if="!exportLoading" class="article-editor-view__download-caret" />
</a-button>
<template #overlay>
<a-menu class="article-editor-view__download-menu">
<a-menu-item key="word" @click="handleDownload('word')">
<FileWordOutlined class="article-editor-view__download-icon article-editor-view__download-icon--word" />
<span>{{ t('article.editor.downloadFormats.word') }}</span>
</a-menu-item>
<a-menu-item key="pdf" @click="handleDownload('pdf')">
<FilePdfOutlined class="article-editor-view__download-icon article-editor-view__download-icon--pdf" />
<span>{{ t('article.editor.downloadFormats.pdf') }}</span>
</a-menu-item>
<a-menu-item key="markdown" @click="handleDownload('markdown')">
<FileMarkdownOutlined class="article-editor-view__download-icon article-editor-view__download-icon--markdown" />
<span>{{ t('article.editor.downloadFormats.markdown') }}</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button
:disabled="editorLocked"
:loading="complianceCheckLoading"
@@ -583,6 +676,20 @@ onBeforeUnmount(() => {
<a-empty v-else :description="t('common.noData')" />
<a-modal
:open="markdownExportConfirmOpen"
:title="t('article.editor.markdownExportConfirm.title')"
:ok-text="t('article.editor.markdownExportConfirm.confirm')"
:cancel-text="t('common.cancel')"
:confirm-loading="exportLoading === 'markdown'"
@ok="handleConfirmMarkdownDownload"
@cancel="handleCancelMarkdownDownload"
>
<p class="article-editor-view__leave-copy">
{{ t('article.editor.markdownExportConfirm.description') }}
</p>
</a-modal>
<a-modal
:open="leaveModalOpen"
:title="t('article.editor.leaveConfirm.title')"
@@ -663,6 +770,41 @@ onBeforeUnmount(() => {
gap: 12px;
}
.article-editor-view__download-caret {
margin-left: 4px;
color: #667085;
font-size: 10px;
}
.article-editor-view__download-menu :deep(.ant-dropdown-menu-item) {
min-width: 170px;
padding: 11px 14px;
gap: 12px;
color: #1f2937;
font-size: 15px;
}
.article-editor-view__download-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
color: #475467;
font-size: 18px;
}
.article-editor-view__download-icon--word {
color: #2563eb;
}
.article-editor-view__download-icon--pdf {
color: #f04438;
}
.article-editor-view__download-icon--markdown {
color: #3b82f6;
}
.article-editor-view__main {
height: calc(100vh - 180px);
overflow: hidden;
+30
View File
@@ -155,6 +155,36 @@ export default defineConfig(({ mode }) => {
return 'imaging'
}
if (
id.includes('/jspdf/') ||
id.includes('/html2canvas/') ||
id.includes('/canvg/') ||
id.includes('/css-line-break/') ||
id.includes('/text-segmentation/') ||
id.includes('/fflate/') ||
id.includes('/pako/') ||
id.includes('/base64-arraybuffer/') ||
id.includes('/fast-png/') ||
id.includes('/iobuffer/') ||
id.includes('/raf/') ||
id.includes('/rgbcolor/') ||
id.includes('/stackblur-canvas/') ||
id.includes('/svg-pathdata/') ||
id.includes('/utrie/')
) {
return 'pdf-export'
}
if (
id.includes('/docx/') ||
id.includes('/jszip/') ||
id.includes('/xml-js/') ||
id.includes('/xml/') ||
id.includes('/hash.js/')
) {
return 'word-export'
}
if (id.includes('/lodash') || id.includes('/dayjs')) {
return 'utils'
}
+155 -47
View File
@@ -2,8 +2,8 @@ import { createHash } from 'node:crypto'
import {
appendFileSync,
mkdirSync,
readFileSync,
readdirSync,
readFileSync,
statSync,
writeFileSync,
} from 'node:fs'
@@ -12,7 +12,7 @@ import { dirname, join } from 'node:path'
import type { DesktopAccountInfo } from '@geo/shared-types'
import { aiPlatformCatalog } from '@geo/shared-types'
import type { Cookie, Session, WebContents } from 'electron/main'
import { BrowserWindow, app, session as electronSession, nativeTheme } from 'electron/main'
import { app, BrowserWindow, session as electronSession, nativeTheme } from 'electron/main'
import { normalizeAccountPlatformUid, sameAccountPlatformUid } from '../shared/account-identity'
import {
@@ -72,7 +72,12 @@ import {
} from './session-registry'
import { upsertDesktopAccount } from './transport/api-client'
import { STANDARD_USER_AGENT } from './user-agent'
import { openWorkbenchWindow } from './workbench-window'
import {
getWorkbenchWindowWebContents,
isWorkbenchWindowReadyForDetection,
loadWorkbenchWindowURL,
openWorkbenchWindow,
} from './workbench-window'
interface DetectedAccount {
platformUid: string
@@ -118,6 +123,11 @@ interface ActiveBindOperation {
promise: Promise<DesktopAccountInfo>
}
interface BoundWindowHandle {
window: BrowserWindow
webContents: WebContents
}
interface WeixinGzhConsoleState {
loggedOut: boolean
title: string
@@ -438,7 +448,11 @@ function appendAccountBindDiagnostic(event: Record<string, unknown>): void {
const target = join(app.getPath('userData'), ACCOUNT_BIND_DIAGNOSTIC_FILE)
try {
mkdirSync(dirname(target), { recursive: true })
appendFileSync(target, `${JSON.stringify({ at: new Date().toISOString(), ...event })}\n`, 'utf8')
appendFileSync(
target,
`${JSON.stringify({ at: new Date().toISOString(), ...event })}\n`,
'utf8',
)
} catch {
// Best-effort diagnostics. Never block binding on local log writes.
}
@@ -447,7 +461,9 @@ function appendAccountBindDiagnostic(event: Record<string, unknown>): void {
async function summarizeDoubaoAuthCookies(
session: Session,
urls: Iterable<string>,
): Promise<Array<{ url: string | null; cookieCount: number; authCookieCount: number; authCookies: string[] }>> {
): Promise<
Array<{ url: string | null; cookieCount: number; authCookieCount: number; authCookies: string[] }>
> {
const summaries: Array<{
url: string | null
cookieCount: number
@@ -2498,14 +2514,16 @@ export async function probeAIAccountSession(
})
}
if (definitionLooksLikeLoginRedirect(currentURL, {
id: platformMeta.id,
label: platformMeta.label,
loginUrl: platformMeta.loginUrl,
consoleUrl: platformMeta.consoleUrl,
detect: (context: DetectContext) =>
detectAIPlatformAccount(platformMeta.id, platformMeta.label, context),
})) {
if (
definitionLooksLikeLoginRedirect(currentURL, {
id: platformMeta.id,
label: platformMeta.label,
loginUrl: platformMeta.loginUrl,
consoleUrl: platformMeta.consoleUrl,
detect: (context: DetectContext) =>
detectAIPlatformAccount(platformMeta.id, platformMeta.label, context),
})
) {
return {
verdict: 'expired',
reason: 'login_redirect',
@@ -3082,13 +3100,13 @@ async function waitForWindowNavigationSettled(
let stableSince = Date.now()
while (!window.isDestroyed() && Date.now() - startedAt < timeoutMs) {
const currentURL = window.webContents.getURL()
const currentURL = getBoundWindowURL(window)
if (currentURL !== lastURL) {
lastURL = currentURL
stableSince = Date.now()
}
if (!window.webContents.isLoading() && Date.now() - stableSince >= 450) {
if (!isBoundWindowLoading(window) && Date.now() - stableSince >= 450) {
return
}
@@ -3130,14 +3148,18 @@ async function verifyToutiaoConsoleAccess(
return false
}
await loadWindowURLSafely(window, TOUTIAO_CONSOLE_HOME_URL, '头条号创作台校验')
await loadBoundWindowURLSafely(window, TOUTIAO_CONSOLE_HOME_URL, '头条号创作台校验')
await waitForWindowNavigationSettled(window)
if (window.isDestroyed()) {
return false
}
const currentURL = window.webContents.getURL()
const webContents = getBoundWindowWebContents(window)
if (!webContents) {
return false
}
const currentURL = webContents.getURL()
if (isToutiaoLoginURL(currentURL)) {
return false
}
@@ -3147,7 +3169,7 @@ async function verifyToutiaoConsoleAccess(
}
const pageResponse = await pageFetchJson<ToutiaoMediaInfoResponse>(
window.webContents,
webContents,
'https://mp.toutiao.com/mp/agw/media/get_media_info',
{
headers: {
@@ -3228,7 +3250,11 @@ async function verifyWangyihaoConsoleAccess(
return false
}
const currentURL = window.webContents.getURL()
const webContents = getBoundWindowWebContents(window)
if (!webContents) {
return false
}
const currentURL = webContents.getURL()
if (!currentURL || currentURL.startsWith('data:text/html')) {
return false
}
@@ -3238,7 +3264,7 @@ async function verifyWangyihaoConsoleAccess(
return false
}
const pageState = await readWangyihaoConsoleState(window.webContents)
const pageState = await readWangyihaoConsoleState(webContents)
if (pageState?.loggedOut) {
return false
}
@@ -3272,7 +3298,11 @@ async function verifyWangyihaoBindWindowReady(
return false
}
const currentURL = window.webContents.getURL()
const webContents = getBoundWindowWebContents(window)
if (!webContents) {
return false
}
const currentURL = webContents.getURL()
const definition = platformBindingDefinitions.wangyihao
if (!currentURL || currentURL.startsWith('data:text/html')) {
return false
@@ -3281,7 +3311,7 @@ async function verifyWangyihaoBindWindowReady(
return false
}
const pageState = await readWangyihaoConsoleState(window.webContents)
const pageState = await readWangyihaoConsoleState(webContents)
if (pageState?.loggedOut) {
return false
}
@@ -3325,9 +3355,14 @@ async function verifyZolConsoleAccess(
return false
}
const webContents = getBoundWindowWebContents(window)
if (!webContents) {
return false
}
const detected = await detectZol({
session,
webContents: window.webContents,
webContents,
}).catch(() => null)
return detected ? detectedAccountMatchesIdentity(detected, account) : false
}
@@ -4173,6 +4208,68 @@ function createBoundWindow(
return window
}
async function createVisibleBoundWindow(
title: string,
targetURL: string,
sessionHandle: Session,
): Promise<BoundWindowHandle> {
const window = await openWorkbenchWindow({
title,
targetURL,
session: sessionHandle,
})
return {
window,
webContents: getWorkbenchWindowWebContents(window) ?? window.webContents,
}
}
function getBoundWindowWebContents(window: BrowserWindow): WebContents | null {
if (window.isDestroyed()) {
return null
}
const contents = getWorkbenchWindowWebContents(window) ?? window.webContents
return contents && !contents.isDestroyed() ? contents : null
}
function getBoundWindowURL(window: BrowserWindow): string {
return getBoundWindowWebContents(window)?.getURL() ?? ''
}
function isBoundWindowLoading(window: BrowserWindow): boolean {
return getBoundWindowWebContents(window)?.isLoading() ?? false
}
function isBoundWindowReadyForDetection(window: BrowserWindow): boolean {
const contents = getBoundWindowWebContents(window)
if (!contents) {
return false
}
const currentURL = contents.getURL()
const fallbackReady =
/^https?:\/\//i.test(currentURL) &&
!currentURL.startsWith('data:text/html') &&
!contents.isLoading()
return (
isWorkbenchWindowReadyForDetection(window) || isWindowReadyForDetection(window) || fallbackReady
)
}
async function loadBoundWindowURLSafely(
window: BrowserWindow,
targetURL: string,
context: string,
): Promise<void> {
if (await loadWorkbenchWindowURL(window, targetURL)) {
return
}
await loadWindowURLSafely(window, targetURL, context)
}
function focusBindWindow(window: BrowserWindow): void {
if (window.isDestroyed()) {
return
@@ -4230,14 +4327,29 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
loginUrl: diagnosticUrl(definition.loginUrl),
})
let window: BrowserWindow
let bindWebContents: WebContents
try {
window = createBoundWindow(`绑定 ${definition.label}`, definition.loginUrl, handle.session)
const boundWindow = await createVisibleBoundWindow(
`绑定 ${definition.label}`,
definition.loginUrl,
handle.session,
)
window = boundWindow.window
bindWebContents = boundWindow.webContents
} catch (error) {
forgetSessionHandle(handle.accountId)
throw error instanceof Error ? error : new Error('desktop_account_bind_failed')
}
const currentBindWebContents = (): WebContents | null => {
if (window.isDestroyed()) {
return null
}
const contents = getWorkbenchWindowWebContents(window) ?? bindWebContents
return contents && !contents.isDestroyed() ? contents : null
}
const bindPromise = new Promise<DesktopAccountInfo>((resolve, reject) => {
let finished = false
let checking = false
@@ -4285,16 +4397,11 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
}
const syncDetectionState = () => {
if (window.isDestroyed()) {
if (!currentBindWebContents()) {
detectReady = false
return
}
const currentURL = window.webContents.getURL()
const fallbackReady =
/^https?:\/\//i.test(currentURL) &&
!currentURL.startsWith('data:text/html') &&
!window.webContents.isLoading()
detectReady = isWindowReadyForDetection(window) || fallbackReady
detectReady = isBoundWindowReadyForDetection(window)
}
const scheduleDetectAndBind = (delayMs = 120) => {
@@ -4315,11 +4422,11 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
scheduleDetectAndBind()
}
window.webContents.on('did-finish-load', handleNavigationReady)
window.webContents.on('did-navigate', handleNavigationReady)
window.webContents.on('did-navigate-in-page', handleNavigationReady)
window.webContents.on('page-title-updated', () => scheduleDetectAndBind(250))
window.webContents.on(
bindWebContents.on('did-finish-load', handleNavigationReady)
bindWebContents.on('did-navigate', handleNavigationReady)
bindWebContents.on('did-navigate-in-page', handleNavigationReady)
bindWebContents.on('page-title-updated', () => scheduleDetectAndBind(250))
bindWebContents.on(
'did-fail-load',
(_event, errorCode, _errorDescription, _validatedURL, isMainFrame) => {
if (isMainFrame && errorCode !== -3) {
@@ -4330,9 +4437,10 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
const detectAndBind = async () => {
syncDetectionState()
const currentURL = window.isDestroyed() ? '' : window.webContents.getURL()
const canProbe = detectReady || /^https?:\/\//i.test(currentURL)
if (finished || checking || !canProbe || window.isDestroyed()) {
const contents = currentBindWebContents()
const currentURL = contents?.getURL() ?? ''
const canProbe = Boolean(contents) && (detectReady || /^https?:\/\//i.test(currentURL))
if (finished || checking || !canProbe || !contents || window.isDestroyed()) {
return
}
checking = true
@@ -4342,7 +4450,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
platform: definition.id,
detectReady,
canProbe,
loading: window.webContents.isLoading(),
loading: contents.isLoading(),
url: diagnosticUrl(currentURL),
partition: handle.partition,
})
@@ -4350,11 +4458,11 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
platform: definition.id,
detectReady,
canProbe,
loading: window.webContents.isLoading(),
loading: contents.isLoading(),
url: currentURL,
})
const detected = await definition
.detect({ session: handle.session, webContents: window.webContents })
.detect({ session: handle.session, webContents: contents })
.catch((error) => {
console.warn(`[desktop-bind] ${definition.id} detect failed`, {
message: error instanceof Error ? error.message : String(error),
@@ -4398,14 +4506,14 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
console.info('[desktop-bind] toutiao console verification', {
platform: definition.id,
ready: consoleReady,
url: window.isDestroyed() ? '' : window.webContents.getURL(),
url: getBoundWindowURL(window),
})
if (!consoleReady) {
return
}
const detectedFromWorkbench = await definition
.detect({ session: handle.session, webContents: window.webContents })
.detect({ session: handle.session, webContents: contents })
.catch(() => null)
if (detectedFromWorkbench) {
normalizedDetected = sanitizeDetectedAccount(detectedFromWorkbench)
@@ -4421,14 +4529,14 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
console.info('[desktop-bind] wangyihao console verification', {
platform: definition.id,
ready: consoleReady,
url: window.isDestroyed() ? '' : window.webContents.getURL(),
url: getBoundWindowURL(window),
})
if (!consoleReady) {
return
}
const detectedFromWorkbench = await definition
.detect({ session: handle.session, webContents: window.webContents })
.detect({ session: handle.session, webContents: contents })
.catch(() => null)
if (detectedFromWorkbench) {
normalizedDetected = sanitizeDetectedAccount(detectedFromWorkbench)
@@ -4437,7 +4545,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
normalizedDetected = await finalizeDetectedAccountForBind(
definition,
{ session: handle.session, webContents: window.webContents },
{ session: handle.session, webContents: contents },
normalizedDetected,
)
@@ -521,7 +521,7 @@ function workbenchErrorDataURL(title: string, targetURL: string, message: string
<body>
<main>
<h1>${escapeHtml(title)}加载失败</h1>
<p>远程工作台暂时无法打开。可以使用窗口左上方的刷新按钮重试。</p>
<p>远程页面暂时无法打开。可以使用窗口左上方的刷新按钮重试。</p>
<code>${escapeHtml(host || targetURL)}
${escapeHtml(message)}</code>
@@ -563,6 +563,19 @@ function getRecordForSender(sender: WebContents): WorkbenchWindowRecord | null {
return record
}
function getRecordForWindow(window: BrowserWindow): WorkbenchWindowRecord | null {
const record = workbenchWindows.get(window.webContents.id)
if (
!record ||
record.window !== window ||
record.window.isDestroyed() ||
record.view.webContents.isDestroyed()
) {
return null
}
return record
}
function defaultNavigationState(): WorkbenchNavigationState {
return {
canGoBack: false,
@@ -740,6 +753,38 @@ async function navigateWorkbenchRemote(
}
}
export function getWorkbenchWindowWebContents(window: BrowserWindow): WebContents | null {
return getRecordForWindow(window)?.view.webContents ?? null
}
export function isWorkbenchWindowReadyForDetection(window: BrowserWindow): boolean {
const record = getRecordForWindow(window)
if (!record) {
return false
}
const currentURL = record.view.webContents.getURL()
return (
isRemoteHTTPURL(currentURL) &&
!isLocalWorkbenchPage(currentURL) &&
!record.loading &&
!record.view.webContents.isLoading()
)
}
export async function loadWorkbenchWindowURL(
window: BrowserWindow,
targetURL: string,
): Promise<boolean> {
const record = getRecordForWindow(window)
if (!record) {
return false
}
await navigateWorkbenchRemote(record, targetURL)
return true
}
export async function openWorkbenchWindow(options: WorkbenchWindowOptions): Promise<BrowserWindow> {
registerWorkbenchNavigationHandlers()
+230 -14
View File
@@ -80,9 +80,18 @@ importers:
dayjs:
specifier: ^1.11.20
version: 1.11.20
docx:
specifier: ^9.6.1
version: 9.6.1
dompurify:
specifier: ^3.4.2
version: 3.4.2
html2canvas:
specifier: ^1.4.1
version: 1.4.1
jspdf:
specifier: ^4.2.1
version: 4.2.1
markdown-it:
specifier: ^14.1.1
version: 14.1.1
@@ -153,7 +162,7 @@ importers:
version: 0.1.38
'@wxt-dev/module-vue':
specifier: ^1.0.3
version: 1.0.3(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))(wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2))
version: 1.0.3(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))(wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2))
typescript:
specifier: ^5.9.3
version: 5.9.3
@@ -162,7 +171,7 @@ importers:
version: 3.2.6(typescript@5.9.3)
wxt:
specifier: ^0.20.20
version: 0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2)
version: 0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2)
apps/desktop-client:
dependencies:
@@ -1579,9 +1588,18 @@ packages:
'@types/node@24.12.0':
resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
'@types/node@25.9.1':
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==}
'@types/pako@2.0.4':
resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
'@types/plist@3.0.5':
resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/responselike@1.0.3':
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
@@ -2003,6 +2021,10 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -2130,6 +2152,10 @@ packages:
caniuse-lite@1.0.30001788:
resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==}
canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -2355,6 +2381,9 @@ packages:
crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
@@ -2481,6 +2510,10 @@ packages:
os: [darwin]
hasBin: true
docx@9.6.1:
resolution: {integrity: sha512-ZJja9/KBUuFC109sCMzovoq2GR2wCG/AuxivjA+OHj/q0TEgJIm3S7yrlUxIy3B+bV8YDj/BiHfWyrRFmyWpDQ==}
engines: {node: '>=10'}
dom-align@1.12.4:
resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==}
@@ -2779,6 +2812,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-png@6.4.0:
resolution: {integrity: sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==}
fast-redact@3.5.0:
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
engines: {node: '>=6'}
@@ -2795,6 +2831,9 @@ packages:
picomatch:
optional: true
fflate@0.8.3:
resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -3015,6 +3054,9 @@ packages:
has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
hash.js@1.1.7:
resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -3032,6 +3074,10 @@ packages:
html-escaper@3.0.3:
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
@@ -3119,6 +3165,9 @@ packages:
inversify@6.1.4:
resolution: {integrity: sha512-PbxrZH/gTa1fpPEEGAjJQzK8tKMIp5gRg6EFNJlCtzUcycuNdmhv3uk5P8Itm/RIjgHJO16oQRLo9IHzQN51bA==}
iobuffer@5.4.0:
resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==}
ip-address@10.1.0:
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
engines: {node: '>= 12'}
@@ -3361,6 +3410,9 @@ packages:
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
engines: {node: '>=12', npm: '>=6'}
jspdf@4.2.1:
resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==}
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
@@ -3797,6 +3849,9 @@ packages:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
minimalistic-assert@1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
@@ -4052,6 +4107,9 @@ packages:
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
parse-json@7.1.1:
resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==}
engines: {node: '>=16'}
@@ -4091,6 +4149,9 @@ packages:
perfect-debounce@2.1.0:
resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -4316,6 +4377,9 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
rc9@3.0.1:
resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
@@ -4348,6 +4412,9 @@ packages:
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
registry-auth-token@5.1.1:
resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==}
engines: {node: '>=14'}
@@ -4413,6 +4480,10 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
@@ -4618,6 +4689,10 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
stat-mode@1.0.0:
resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
engines: {node: '>= 6'}
@@ -4697,6 +4772,10 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
svg-pathdata@6.0.3:
resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
engines: {node: '>=12.0.0'}
tar-fs@2.1.4:
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
@@ -4715,6 +4794,9 @@ packages:
temp-file@3.4.0:
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@@ -4819,6 +4901,9 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
undici@6.25.0:
resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==}
engines: {node: '>=18.17'}
@@ -4901,6 +4986,9 @@ packages:
util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -5212,6 +5300,10 @@ packages:
resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==}
engines: {node: '>=12'}
xml-js@1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
@@ -5220,6 +5312,9 @@ packages:
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
engines: {node: '>=4.0.0'}
xml@1.0.1:
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
xmlbuilder@11.0.1:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'}
@@ -6756,12 +6851,21 @@ snapshots:
dependencies:
undici-types: 7.16.0
'@types/node@25.9.1':
dependencies:
undici-types: 7.24.6
'@types/pako@2.0.4': {}
'@types/plist@3.0.5':
dependencies:
'@types/node': 24.12.0
xmlbuilder: 11.0.1
optional: true
'@types/raf@3.4.3':
optional: true
'@types/responselike@1.0.3':
dependencies:
'@types/node': 24.12.0
@@ -6898,10 +7002,10 @@ snapshots:
vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)
vue: 3.5.31(typescript@5.9.3)
'@vitejs/plugin-vue@6.0.6(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))':
'@vitejs/plugin-vue@6.0.6(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.13
vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1)
vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1)
vue: 3.5.31(typescript@5.9.3)
'@vitest/expect@4.1.5':
@@ -7069,10 +7173,10 @@ snapshots:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
'@wxt-dev/module-vue@1.0.3(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))(wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2))':
'@wxt-dev/module-vue@1.0.3(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))(wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2))':
dependencies:
'@vitejs/plugin-vue': 6.0.6(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))
wxt: 0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2)
'@vitejs/plugin-vue': 6.0.6(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1))(vue@3.5.31(typescript@5.9.3))
wxt: 0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2)
transitivePeerDependencies:
- vite
- vue
@@ -7333,6 +7437,8 @@ snapshots:
balanced-match@4.0.4: {}
base64-arraybuffer@1.0.2: {}
base64-js@1.5.1: {}
baseline-browser-mapping@2.10.20: {}
@@ -7524,6 +7630,18 @@ snapshots:
caniuse-lite@1.0.30001788: {}
canvg@3.0.11:
dependencies:
'@babel/runtime': 7.29.2
'@types/raf': 3.4.3
core-js: 3.49.0
raf: 3.4.1
regenerator-runtime: 0.13.11
rgbcolor: 1.0.1
stackblur-canvas: 2.7.0
svg-pathdata: 6.0.3
optional: true
ccount@2.0.1: {}
chai@6.2.2: {}
@@ -7733,6 +7851,10 @@ snapshots:
crypt@0.0.2: {}
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
@@ -7853,6 +7975,15 @@ snapshots:
verror: 1.10.1
optional: true
docx@9.6.1:
dependencies:
'@types/node': 25.9.1
hash.js: 1.1.7
jszip: 3.10.1
nanoid: 5.1.7
xml: 1.0.1
xml-js: 1.6.11
dom-align@1.12.4: {}
dom-scroll-into-view@2.0.1: {}
@@ -8233,6 +8364,12 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-png@6.4.0:
dependencies:
'@types/pako': 2.0.4
iobuffer: 5.4.0
pako: 2.1.0
fast-redact@3.5.0: {}
fd-slicer@1.1.0:
@@ -8243,6 +8380,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.4
fflate@0.8.3: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -8487,6 +8626,11 @@ snapshots:
has-unicode@2.0.1: {}
hash.js@1.1.7:
dependencies:
inherits: 2.0.4
minimalistic-assert: 1.0.1
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
@@ -8501,6 +8645,11 @@ snapshots:
html-escaper@3.0.3: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
htmlparser2@10.1.0:
dependencies:
domelementtype: 2.3.0
@@ -8594,6 +8743,8 @@ snapshots:
transitivePeerDependencies:
- reflect-metadata
iobuffer@5.4.0: {}
ip-address@10.1.0: {}
is-absolute@0.1.7:
@@ -8810,6 +8961,17 @@ snapshots:
ms: 2.1.3
semver: 7.7.4
jspdf@4.2.1:
dependencies:
'@babel/runtime': 7.29.2
fast-png: 6.4.0
fflate: 0.8.3
optionalDependencies:
canvg: 3.0.11
core-js: 3.49.0
dompurify: 3.4.2
html2canvas: 1.4.1
jszip@3.10.1:
dependencies:
lie: 3.3.0
@@ -9422,6 +9584,8 @@ snapshots:
mimic-response@3.1.0: {}
minimalistic-assert@1.0.1: {}
minimatch@10.2.5:
dependencies:
brace-expansion: 5.0.5
@@ -9709,6 +9873,8 @@ snapshots:
pako@1.0.11: {}
pako@2.1.0: {}
parse-json@7.1.1:
dependencies:
'@babel/code-frame': 7.29.0
@@ -9740,6 +9906,9 @@ snapshots:
perfect-debounce@2.1.0: {}
performance-now@2.1.0:
optional: true
picocolors@1.1.1: {}
picomatch@2.3.2: {}
@@ -9993,6 +10162,11 @@ snapshots:
quick-lru@5.1.1: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
optional: true
rc9@3.0.1:
dependencies:
defu: 6.1.6
@@ -10037,6 +10211,9 @@ snapshots:
reflect-metadata@0.2.2: {}
regenerator-runtime@0.13.11:
optional: true
registry-auth-token@5.1.1:
dependencies:
'@pnpm/npm-conf': 3.0.2
@@ -10127,6 +10304,9 @@ snapshots:
rfdc@1.4.1: {}
rgbcolor@1.0.1:
optional: true
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@@ -10367,6 +10547,9 @@ snapshots:
stackback@0.0.2: {}
stackblur-canvas@2.7.0:
optional: true
stat-mode@1.0.0: {}
std-env@4.1.0: {}
@@ -10446,6 +10629,9 @@ snapshots:
dependencies:
has-flag: 4.0.0
svg-pathdata@6.0.3:
optional: true
tar-fs@2.1.4:
dependencies:
chownr: 1.1.4
@@ -10477,6 +10663,10 @@ snapshots:
async-exit-hook: 2.0.1
fs-extra: 10.1.0
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
@@ -10560,6 +10750,8 @@ snapshots:
undici-types@7.16.0: {}
undici-types@7.24.6: {}
undici@6.25.0: {}
unified@11.0.5:
@@ -10684,6 +10876,10 @@ snapshots:
is-typed-array: 1.1.15
which-typed-array: 1.1.20
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
uuid@8.3.2: {}
validator@13.15.35: {}
@@ -10705,13 +10901,13 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite-node@6.0.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1):
vite-node@6.0.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1):
dependencies:
cac: 7.0.0
es-module-lexer: 2.0.0
obug: 2.1.1
pathe: 2.0.3
vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1)
vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1)
transitivePeerDependencies:
- '@emnapi/core'
- '@emnapi/runtime'
@@ -10749,7 +10945,21 @@ snapshots:
jiti: 2.6.1
lightningcss: 1.32.0
vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1):
vite@7.3.2(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0):
dependencies:
esbuild: 0.27.5
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.8
rollup: 4.60.2
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.9.1
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.32.0
vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
@@ -10757,7 +10967,7 @@ snapshots:
rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.12.0
'@types/node': 25.9.1
esbuild: 0.27.5
fsevents: 2.3.3
jiti: 2.6.1
@@ -10960,7 +11170,7 @@ snapshots:
is-wsl: 3.1.1
powershell-utils: 0.1.0
wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2):
wxt@0.20.20(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(eslint@10.2.1(jiti@2.6.1))(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.2):
dependencies:
'@1natsu/wait-element': 4.1.2
'@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.60.2)
@@ -11003,8 +11213,8 @@ snapshots:
scule: 1.3.0
tinyglobby: 0.2.15
unimport: 6.0.2
vite: 7.3.2(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)
vite-node: 6.0.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.5)(jiti@2.6.1)
vite: 7.3.2(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0)
vite-node: 6.0.0(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.9.1)(esbuild@0.27.5)(jiti@2.6.1)
web-ext-run: 0.2.4
optionalDependencies:
eslint: 10.2.1(jiti@2.6.1)
@@ -11029,6 +11239,10 @@ snapshots:
xdg-basedir@5.1.0: {}
xml-js@1.6.11:
dependencies:
sax: 1.6.0
xml-name-validator@4.0.0: {}
xml2js@0.6.2:
@@ -11036,6 +11250,8 @@ snapshots:
sax: 1.6.0
xmlbuilder: 11.0.1
xml@1.0.1: {}
xmlbuilder@11.0.1: {}
xmlbuilder@15.1.1: {}