Merge pull request 'Scale docx pages to the preview column' (#44) from feature/docx-paper-preview into main
Scale docx pages to the preview column
This commit was merged in pull request #44.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
node_modules
|
||||
dist/
|
||||
*.local
|
||||
*.tsbuildinfo
|
||||
|
||||
# mimosa 扫描钩子的本地缓存(scripts/hooks 写入,不入库)
|
||||
.mimosa
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// docx 分页纸面按预览栏宽度等比缩小。只缩不放:栏宽够放下整页时保持 100%。
|
||||
// gutter 与 styles.css 里 .docx-wrapper 的左右 padding(12px + 12px)对齐。
|
||||
|
||||
export function computeFitScale(containerWidth: number, pageWidth: number, gutter: number): number {
|
||||
if (!Number.isFinite(containerWidth) || !Number.isFinite(pageWidth) || pageWidth <= 0) return 1
|
||||
if (!Number.isFinite(gutter)) return 1
|
||||
const available = containerWidth - gutter
|
||||
if (!(available > 0)) return 1
|
||||
return Math.min(1, available / pageWidth)
|
||||
}
|
||||
|
||||
const PAGE_GUTTER_PX = 24
|
||||
|
||||
/** host 是 docx-preview 的渲染根(内含 .docx-wrapper);el 是用来量栏宽的元素。
|
||||
* 拖动分隔条时 el 变宽,ResizeObserver 重算。返回的函数卸下监听并清掉行内缩放。 */
|
||||
export function fitPagesToWidth(host: HTMLElement, el: HTMLElement): () => void {
|
||||
const apply = () => {
|
||||
const wrapper = host.querySelector<HTMLElement>('.docx-wrapper')
|
||||
const page = host.querySelector<HTMLElement>('section.docx')
|
||||
if (!wrapper || !page) return
|
||||
const scale = computeFitScale(el.clientWidth, page.offsetWidth, PAGE_GUTTER_PX)
|
||||
wrapper.style.transformOrigin = 'top left'
|
||||
wrapper.style.transform = scale < 1 ? `scale(${scale})` : ''
|
||||
const nextHeight = `${Math.round(wrapper.offsetHeight * scale)}px`
|
||||
if (host.style.height !== nextHeight) host.style.height = nextHeight
|
||||
host.style.overflow = 'hidden'
|
||||
host.style.minHeight = '0'
|
||||
}
|
||||
apply()
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return () => clearFit(host)
|
||||
}
|
||||
const observer = new ResizeObserver(() => apply())
|
||||
observer.observe(el)
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
clearFit(host)
|
||||
}
|
||||
}
|
||||
|
||||
function clearFit(host: HTMLElement) {
|
||||
const wrapper = host.querySelector<HTMLElement>('.docx-wrapper')
|
||||
if (wrapper) {
|
||||
wrapper.style.transform = ''
|
||||
wrapper.style.transformOrigin = ''
|
||||
}
|
||||
host.style.height = ''
|
||||
host.style.overflow = ''
|
||||
host.style.minHeight = ''
|
||||
}
|
||||
+2
-1
@@ -4,6 +4,7 @@
|
||||
import { configureFilePreviewLoaders, configureLinkPreviewFetcher, renderMarkdown, setSafeHTML, stripUnsafeLinks } from '@ui'
|
||||
import { auth } from './auth'
|
||||
import type { LinkPreviewData } from '@ui'
|
||||
import { fitPagesToWidth } from './previewfit'
|
||||
|
||||
const fetchBuf = async (url: string | undefined): Promise<ArrayBuffer> => {
|
||||
const r = await fetch(url!)
|
||||
@@ -43,7 +44,7 @@ const docxLoader = async (el: HTMLElement, source: { contentUrl?: string; name?:
|
||||
el.innerHTML = ''
|
||||
await renderAsync(buf, el, el, { className: 'docx', inWrapper: true, breakPages: true })
|
||||
stripUnsafeLinks(el)
|
||||
return
|
||||
return fitPagesToWidth(el, el)
|
||||
} catch (err) {
|
||||
console.error('[preview:docx] page render failed, falling back', source.name, err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -203,8 +203,8 @@ button { font-family: inherit; font-size: inherit; color: inherit; }
|
||||
.preview-pane-body { flex: 1; min-height: 0; overflow: auto; }
|
||||
.preview-pane-body .ui-fp { height: 100%; }
|
||||
.preview-pane-body .ui-fp-pdf { height: 100%; min-height: 480px; border: none; }
|
||||
.docx-wrapper { background: var(--bg-inset) !important; padding: 12px; }
|
||||
.docx-wrapper > section.docx { background: var(--bg) !important; color: var(--text); box-shadow: var(--shadow-md); }
|
||||
.docx-wrapper { background: var(--bg-inset) !important; padding: 12px; display: flex; flex-direction: column; align-items: flex-start !important; }
|
||||
.docx-wrapper > section.docx { background: var(--paper) !important; color: var(--paper-ink); box-shadow: var(--shadow-md); }
|
||||
/* 过程段内联组(流式/关闭自动折叠时,按原位渲染的过程容器) */
|
||||
.part-group { margin: 2px 0; }
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { computeFitScale } from '../src/previewfit'
|
||||
|
||||
describe('computeFitScale', () => {
|
||||
it('keeps the page at 100% when the column is wider than the page', () => {
|
||||
expect(computeFitScale(1100, 794, 24)).toBe(1)
|
||||
expect(computeFitScale(818, 794, 24)).toBe(1)
|
||||
})
|
||||
|
||||
it('scales the page down to the column width after subtracting the gutter', () => {
|
||||
expect(computeFitScale(420, 794, 24)).toBeCloseTo((420 - 24) / 794)
|
||||
expect(computeFitScale(794, 794, 24)).toBeCloseTo((794 - 24) / 794)
|
||||
})
|
||||
|
||||
it('does not invert or zoom when the width is unusable', () => {
|
||||
expect(computeFitScale(0, 794, 24)).toBe(1)
|
||||
expect(computeFitScale(20, 794, 24)).toBe(1)
|
||||
expect(computeFitScale(420, 0, 24)).toBe(1)
|
||||
expect(computeFitScale(Number.NaN, 794, 24)).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
{"root":["./src/App.tsx","./src/api.ts","./src/auth.ts","./src/contracts.ts","./src/live.ts","./src/main.tsx","./src/messageparts.ts","./src/outbox.ts","./src/passwords.ts","./src/pluginHost.ts","./src/previewsetup.ts","./src/previewtabs.ts","./src/projectmeta.ts","./src/runstate.test.ts","./src/runstate.ts","./src/scrollsync.ts","./src/store.ts","./src/tasklist.ts","./src/theme.ts","./src/toolkind.ts","./src/workspaces.ts","./src/components/AdminUsersModal.tsx","./src/components/AdminZone.tsx","./src/components/AgentTracker.tsx","./src/components/ArtifactsPanel.tsx","./src/components/BorrowPanels.tsx","./src/components/Conversation.tsx","./src/components/DocViewer.tsx","./src/components/LoginPage.tsx","./src/components/PreviewPane.tsx","./src/components/ProcessTimeline.tsx","./src/components/ProjectSettingsModal.tsx","./src/components/SettingsModal.tsx","./src/components/TaskSidebar.tsx","./src/components/TopBar.tsx","./src/components/Workbench.tsx","./src/components/Workspaces.tsx","./src/components/adminSections.tsx","./test/auth.test.ts","./test/contracts.test.ts","./test/design-guard.test.ts","./test/html-sinks.test.ts","./test/live.test.ts","./test/messageparts.test.ts","./test/notice.test.ts","./test/outbox.test.ts","./test/previewtabs.test.ts","./test/scrollsync.test.ts","./test/send.test.ts","./test/tasklist.test.ts","./test/theme.test.ts","./test/toolkind.test.ts","./test/workspaces.test.ts"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user