Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
23bff70
feat(xlsx-preview): add artifact spreadsheet preview
zhangjiadi225 Jul 3, 2026
05b6e68
fix(xlsx-preview): pad grid beyond used range and merge bottom bar
zhangjiadi225 Jul 3, 2026
6e66e57
fix(xlsx-preview): honor sheet sizing defaults and stop clipping cell…
zhangjiadi225 Jul 3, 2026
a58e023
fix(xlsx-preview): zoom as photographic scaling via CSS transform
zhangjiadi225 Jul 3, 2026
a1d5f95
chore(deps): move xlsx-preview renderer libs to devDependencies
zhangjiadi225 Jul 3, 2026
ae57f51
fix(xlsx-preview): address review feedback
zhangjiadi225 Jul 3, 2026
d033d27
fix(xlsx-preview): clear grid selection on sheet switch and tidy render
zhangjiadi225 Jul 6, 2026
93f8664
fix(xlsx-preview): preserve formula text in preview
zhangjiadi225 Jul 6, 2026
9ff9035
fix(xlsx-preview): resolve ci lint failures
zhangjiadi225 Jul 6, 2026
ddab0e1
fix(xlsx-preview): remove hardcoded mock strings
zhangjiadi225 Jul 6, 2026
8b805b6
fix(xlsx-preview): implement stubbed aggregate formulas (MAX/MIN/etc.)
zhangjiadi225 Jul 6, 2026
95bc876
fix(xlsx-preview): improve sheet tab and grid accessibility
zhangjiadi225 Jul 6, 2026
80d7d22
refactor(xlsx-preview): drop unused frozen-pane field from render model
zhangjiadi225 Jul 6, 2026
9182b15
fix(office-preview): preflight ZIP central directory before xlsx parsing
zhangjiadi225 Jul 6, 2026
1d4c696
fix(xlsx-preview): bound formula range materialization in the worker
zhangjiadi225 Jul 6, 2026
cfdc7f0
fix(xlsx-preview): clamp untrusted chart cache ptCount and point indices
zhangjiadi225 Jul 6, 2026
6880333
fix(xlsx-preview): render percent-stacked charts as normalized percen…
zhangjiadi225 Jul 6, 2026
3e37285
fix(xlsx-preview): restrict string date formatting to strict ISO shape
zhangjiadi225 Jul 6, 2026
529315b
fix(xlsx-preview): expose merged cells as a single semantic gridcell
zhangjiadi225 Jul 6, 2026
fbe4eba
fix(xlsx-preview): re-export the panel barrel as a named export
zhangjiadi225 Jul 6, 2026
3fcf97b
docs(xlsx-preview): translate comments to English
zhangjiadi225 Jul 6, 2026
c69ebec
fix(xlsx-preview): cap chart reference range materialization
zhangjiadi225 Jul 6, 2026
6896d81
fix(xlsx-preview): bound drawing anchor offset math
zhangjiadi225 Jul 6, 2026
ff3bd73
fix(xlsx-preview): keep date serial semantics in formula context
zhangjiadi225 Jul 6, 2026
450edcf
fix(xlsx-preview): translate zh-TW xlsx preview strings
zhangjiadi225 Jul 6, 2026
102134f
fix(xlsx-preview): bound image drawing anchor offset math
zhangjiadi225 Jul 6, 2026
bf838ec
fix(xlsx-preview): give each parse request its own worker
zhangjiadi225 Jul 6, 2026
c2faeea
Merge remote-tracking branch 'origin/main' into zhangjiadi225/excel-r…
zhangjiadi225 Jul 6, 2026
7a80996
Merge remote-tracking branch 'origin/main' into zhangjiadi225/excel-r…
zhangjiadi225 Jul 7, 2026
ae89cbe
test(file-watcher): stabilize watcher event waits
zhangjiadi225 Jul 7, 2026
a8e2afb
Revert "test(file-watcher): stabilize watcher event waits"
zhangjiadi225 Jul 7, 2026
7308904
Merge remote-tracking branch 'origin/main' into zhangjiadi225/excel-r…
zhangjiadi225 Jul 7, 2026
b70bd82
fix(xlsx-preview): address review findings on chart refs, image keys,…
zhangjiadi225 Jul 7, 2026
cbe661c
Merge remote-tracking branch 'origin/main' into zhangjiadi225/excel-r…
zhangjiadi225 Jul 7, 2026
c9a09da
fix(xlsx-preview): cap floating objects per sheet and memo chart parts
zhangjiadi225 Jul 7, 2026
7c1fb6e
fix(xlsx-preview): downgrade percentStacked charts with negative values
zhangjiadi225 Jul 7, 2026
f7e8a75
fix(xlsx-preview): skip formula evaluation in 1904 date system workbooks
zhangjiadi225 Jul 7, 2026
d4ad4e8
fix(office-preview): drop per-entry uncompressed ZIP limit
zhangjiadi225 Jul 7, 2026
3292167
fix(chat-shell): stop canvas remount on maximize/minimize
zhangjiadi225 Jul 7, 2026
ea06596
fix(office-preview): restore per-entry uncompressed ZIP cap at 64 MiB
zhangjiadi225 Jul 7, 2026
80d26ab
fix(xlsx-preview): stop chart anchor extraction at the floating-objec…
zhangjiadi225 Jul 7, 2026
bb63d0c
fix(xlsx-preview): expose merges as spanning gridcells in their real row
zhangjiadi225 Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.5",
"echarts": "^6.1.0",
"electron": "41.8.0",
"electron-builder": "26.15.3",
"electron-devtools-installer": "^3.2.0",
Expand All @@ -332,8 +333,10 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"exceljs": "^4.4.0",
"fast-diff": "^1.3.0",
"fast-xml-parser": "5.4.1",
"fast-formula-parser": "^1.0.19",
"fast-xml-parser": "^5.4.1",
"fetch-socks": "1.3.2",
"form-data": "4.0.5",
"fractional-indexing": "^3.2.0",
Expand All @@ -354,6 +357,7 @@
"js-base64": "3.7.7",
"json-schema": "0.4.0",
"jsonc-parser": "^3.3.1",
"jszip": "^3.10.1",
"katex": "0.16.22",
"ky": "1.8.1",
"linguist-languages": "^8.1.0",
Expand All @@ -369,6 +373,7 @@
"nanoid": "3.3.11",
"notion-helper": "^1.3.22",
"npx-scope-finder": "^1.2.0",
"numfmt": "^3.2.6",
"og-crd": "^0.0.2",
"ollama-ai-provider-v2": "3.3.1",
"open": "^8.4.2",
Expand Down
331 changes: 331 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions src/main/services/file/__tests__/watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ describe('createDirectoryWatcher', () => {

const w = createDirectoryWatcher(dir as FilePath)
await waitForReady(w)
const addEvent = waitForEvent(w, (e) => e.kind === 'add' && e.path === target)
await writeFile(target, 'hello')
const ev = await waitForEvent(w, (e) => e.kind === 'add' && e.path === target)
const ev = await addEvent
expect(ev.kind).toBe('add')
expect(
await danglingCache.check({
Expand All @@ -108,8 +109,9 @@ describe('createDirectoryWatcher', () => {

const w = createDirectoryWatcher(dir as FilePath, { stabilityThresholdMs: 0 })
await waitForReady(w)
const unlinkEvent = waitForEvent(w, (e) => e.kind === 'unlink' && e.path === target)
await rm(target)
const ev = await waitForEvent(w, (e) => e.kind === 'unlink' && e.path === target)
const ev = await unlinkEvent
expect(ev.kind).toBe('unlink')
expect(
await danglingCache.check({
Expand Down Expand Up @@ -137,12 +139,14 @@ describe('createDirectoryWatcher', () => {
await waitForReady(w)

// First write registers the file (fires 'add'); second write fires 'change'.
const addEvent = waitForEvent(w, (e) => e.kind === 'add' && e.path === target)
await writeFile(target, 'v1')
await waitForEvent(w, (e) => e.kind === 'add' && e.path === target)
await addEvent
// Brief settle so chokidar's awaitWriteFinish window closes on the add.
await new Promise((r) => setTimeout(r, 250))
const changeEvent = waitForEvent(w, (e) => e.kind === 'change' && e.path === target)
await writeFile(target, 'v2-content-larger')
const ev = await waitForEvent(w, (e) => e.kind === 'change' && e.path === target)
const ev = await changeEvent
expect(ev.kind).toBe('change')
await w.close()
})
Expand Down Expand Up @@ -170,8 +174,9 @@ describe('createDirectoryWatcher', () => {

const rootFile = path.join(dir, 'root.txt') as FilePath
const nestedFile = path.join(nestedDir, 'nested.txt') as FilePath
const rootAddEvent = waitForEvent(w, (e) => e.kind === 'add' && e.path === rootFile)
await writeFile(rootFile, 'root')
await waitForEvent(w, (e) => e.kind === 'add' && e.path === rootFile)
await rootAddEvent

await writeFile(nestedFile, 'nested')
await new Promise((r) => setTimeout(r, 400))
Expand Down Expand Up @@ -206,8 +211,9 @@ describe('createDirectoryWatcher', () => {

const w = createDirectoryWatcher(dir as FilePath)
await waitForReady(w)
const addEvent = waitForEvent(w, (e) => e.kind === 'add' && e.path?.endsWith('.txt'), 30_000)
await writeFile(writtenPath, 'hello')
const ev = await waitForEvent(w, (e) => e.kind === 'add' && e.path?.endsWith('.txt'), 30_000)
const ev = await addEvent
if (ev.kind !== 'add') throw new Error('expected add event')
expect(ev.path).toBe(writtenPath)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { useTranslation } from 'react-i18next'

const logger = loggerService.withContext('OfficePreviewPanel')

const SUPPORTED_OFFICE_PREVIEW_EXTENSIONS = new Set(['docx', 'pptx'])

interface OfficeDocumentPreviewProps {
filePath: string
fileName: string
Expand All @@ -31,6 +29,7 @@ export interface OfficePreviewPanelProps {

let wordPreviewPanelPromise: Promise<OfficeDocumentPreviewPanel> | null = null
let pptxPreviewPanelPromise: Promise<OfficeDocumentPreviewPanel> | null = null
let xlsxPreviewPanelPromise: Promise<OfficeDocumentPreviewPanel> | null = null

function loadWordPreviewPanel() {
wordPreviewPanelPromise ??= import('./WordPreviewPanel')
Expand All @@ -52,6 +51,26 @@ function loadPptxPreviewPanel() {
return pptxPreviewPanelPromise
}

function loadXlsxPreviewPanel() {
xlsxPreviewPanelPromise ??= import('./XlsxPreviewPanel')
.then((module) => module.XlsxPreviewPanel)
.catch((err: unknown) => {
xlsxPreviewPanelPromise = null
throw err
})
return xlsxPreviewPanelPromise
}

// Extension → lazy panel loader. Doubles as the source of truth for which office
// documents get an inline preview; anything not listed falls back to "open externally".
const OFFICE_PREVIEW_LOADERS: Record<string, () => Promise<OfficeDocumentPreviewPanel>> = {
docx: loadWordPreviewPanel,
pptx: loadPptxPreviewPanel,
xlsx: loadXlsxPreviewPanel
}

const SUPPORTED_OFFICE_PREVIEW_EXTENSIONS = new Set(Object.keys(OFFICE_PREVIEW_LOADERS))

function extOf(name: string | undefined): string {
if (!name) return ''
const dot = name.lastIndexOf('.')
Expand Down Expand Up @@ -115,7 +134,8 @@ function SupportedOfficePreview({
let cancelled = false
setLoadError(null)

const loader = extension === 'docx' ? loadWordPreviewPanel : loadPptxPreviewPanel
const loader = OFFICE_PREVIEW_LOADERS[extension]
if (!loader) return
loader()
.then((Component) => {
if (!cancelled) setLoadedPreview({ extension, Component })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'

import DocumentPreviewToolbar from '../DocumentPreviewToolbar'
import { toUint8Array } from '../toUint8Array'
import { assertDocxZipLimits } from './docxZipPreflight'
import { assertZipLimits } from './zipPreflight'

const logger = loggerService.withContext('WordPreviewPanel')

Expand Down Expand Up @@ -144,7 +144,7 @@ const WordPreviewPanel = ({ filePath, fileName, refreshKey, sourceSize, actions
assertSourceSize(docxData.byteLength)
if (!isCurrent()) return

assertDocxZipLimits(docxData)
assertZipLimits(docxData, 'DOCX')
if (!isCurrent()) return

await renderAsync(docxData, stagingBody, stagingStyle, {
Expand Down
Loading
Loading