Canvas analysis previously sent every page. Add a Magic Notes setting for 1-8 canvas pages, defaulting to 1 for new and historical settings. Select the first N pages in current canvas order before capture and use the same text/image range for saved entries, drafts and canvas-source todos. Keep full 50-page saving/export, expose the same setting through config tools, and use the shared settings select with an inline help icon. Include pagination, persistence, production IPC and isolated Electron regressions, including text-only input and scaled page boundaries. Validation: focused feature tests and isolated Electron regressions passed; the final compact UI passed 14 tests. Typecheck and lint were reported passing by the coordinating agent. No full-suite pass is claimed because the completed full test command exposed no summary. No live model calls were made for this commit preparation. Release note: 魔法笔记新增“发送画布页数”设置,可选 1~8 页,默认 1 页,按当前画布顺序发送前 N 页的文字和图片;标题旁问号可查看说明,保存和导出完整画布不受影响。
115 lines
7.1 KiB
TypeScript
115 lines
7.1 KiB
TypeScript
import Quill from 'quill'
|
|
import { createRoot } from 'react-dom/client'
|
|
import { useState } from 'react'
|
|
import { ApplicationSettingsView } from '../../src/renderer/src/ApplicationCenter'
|
|
import type { ApplicationSettings } from '../../src/shared/application-settings-contracts'
|
|
import { MagicNotesWorkspace } from '../../src/renderer/src/MagicNotesWorkspace'
|
|
import type { MagicNoteCanvasContent } from '../../src/shared/magic-notes-contracts'
|
|
import i18n from '../../src/renderer/src/i18n'
|
|
import '../../src/renderer/src/styles.css'
|
|
import '../../src/renderer/src/magic-canvas.css'
|
|
|
|
async function notesAnalysisRegression() {
|
|
await i18n.changeLanguage('en-US')
|
|
const api = window.goodbuddy.magicNotes
|
|
const initialSettings = await window.goodbuddy.updates.getSettings()
|
|
if (initialSettings.magicNoteCanvasPageCount !== 1) throw new Error('Expected default page count 1')
|
|
const inputMode = (await window.goodbuddy.settings.getRuntime()).supportsImageInput ? 'canvas-images' : 'text-fallback'
|
|
const note = await api.create({ title: 'IPC canvas analysis' })
|
|
const content: MagicNoteCanvasContent = { version: 2, kind: 'paged-canvas', assets: [],
|
|
pages: [1, 2, 3].map(index => ({ id: `page-${index}`, width: 794, height: 1123, background: { type: 'template', template: 'blank' }, objects: [] })),
|
|
flow: { version: 1, ops: [{ insert: 'Review task' }, { insert: '\n', attributes: { list: 'unchecked' } },
|
|
{ insert: { canvasPageBreak: 'page-2' } }, { insert: 'Included second\n' },
|
|
{ insert: { canvasPageBreak: 'page-3' } }, { insert: 'Excluded third\n' }] } }
|
|
const seeded = await api.createEntry({ noteId: note.id, content })
|
|
const entryId = seeded.createdEntryId
|
|
const host = document.createElement('div')
|
|
document.body.append(host)
|
|
const errors: string[] = []
|
|
const root = createRoot(host)
|
|
function Probe() {
|
|
const [settings, setSettings] = useState<ApplicationSettings>(initialSettings)
|
|
return <><ApplicationSettingsView id="magic-notes" settings={settings} pending={false} onUpdate={async updates => {
|
|
setSettings(await window.goodbuddy.updates.updateSettings(updates)); return true
|
|
}} /><MagicNotesWorkspace applicationSettings={settings} onNotify={notification => { if (notification.tone === 'error') errors.push(notification.message) }} /></>
|
|
}
|
|
root.render(<Probe />)
|
|
const wait = async (predicate: () => unknown | Promise<unknown>) => {
|
|
for (let i = 0; i < 400; i++) {
|
|
if (await predicate()) return
|
|
if (errors.length) throw new Error(errors.join('\n'))
|
|
await new Promise(resolve => setTimeout(resolve, 25))
|
|
}
|
|
throw new Error(`Timed out: ${predicate}`)
|
|
}
|
|
const button = (label: string, parent: ParentNode = host) => [...parent.querySelectorAll<HTMLButtonElement>('button')].find(node => node.textContent?.trim() === label)!
|
|
const click = async (label: string, parent: ParentNode = host) => {
|
|
await wait(() => button(label, parent) && !button(label, parent).disabled)
|
|
button(label, parent).click()
|
|
}
|
|
const entry = async () => (await api.get(note.id)).entries.find(item => item.id === entryId)!
|
|
await wait(() => host.querySelector('select[aria-describedby="magic-note-canvas-page-count-help"]'))
|
|
const pageSelect = host.querySelector<HTMLSelectElement>('select[aria-describedby="magic-note-canvas-page-count-help"]')!
|
|
pageSelect.value = '2'
|
|
pageSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
|
await wait(async () => (await window.goodbuddy.updates.getSettings()).magicNoteCanvasPageCount === 2)
|
|
await wait(() => host.querySelector('[id^="magic-note-select-"]'))
|
|
host.querySelector<HTMLButtonElement>('[id^="magic-note-select-"]')!.click()
|
|
const nextPage = () => host.querySelector<HTMLButtonElement>('.magic-note-entry .canvas-note-next-page')!
|
|
await wait(() => nextPage() && !nextPage().disabled)
|
|
nextPage().click()
|
|
await wait(() => nextPage() && !nextPage().disabled)
|
|
nextPage().click()
|
|
await wait(() => nextPage()?.disabled)
|
|
host.querySelector<HTMLButtonElement>('.magic-note-entry .canvas-note-zoom-out')!.click()
|
|
await click('Analyze with AI')
|
|
await wait(async () => (await entry()).comments[0]?.inputMode === inputMode)
|
|
const manualRevision = (await entry()).revision
|
|
await click('Expand canvas editor')
|
|
const editor = () => host.querySelector<HTMLElement>('.magic-note-entry__editor')!
|
|
await wait(() => editor()?.querySelector<HTMLButtonElement>('[data-tool="flow-text"]')?.disabled === false)
|
|
editor().querySelector<HTMLButtonElement>('[data-tool="flow-text"]')!.click()
|
|
await wait(() => editor().querySelector('.canvas-flow-quill'))
|
|
const quill = Quill.find(editor().querySelector('.canvas-flow-quill')!) as Quill
|
|
quill.insertText(0, 'Updated ', 'user')
|
|
await click('Save changes')
|
|
await wait(async () => (await entry()).revision >= manualRevision + 2)
|
|
const editedRevision = (await entry()).revision
|
|
await click('Cancel')
|
|
await wait(() => host.querySelector('.magic-note-composer'))
|
|
await click('Canvas', host.querySelector('.magic-note-composer')!)
|
|
const composer = () => host.querySelector<HTMLElement>('.magic-note-composer')!
|
|
await wait(() => composer().querySelector<HTMLButtonElement>('[data-tool="flow-text"]')?.disabled === false)
|
|
composer().querySelector<HTMLButtonElement>('[data-tool="flow-text"]')!.click()
|
|
await wait(() => composer().querySelector('.canvas-flow-quill'))
|
|
;(Quill.find(composer().querySelector('.canvas-flow-quill')!) as Quill).setContents(content.flow!.ops, 'user')
|
|
await click('Analyze canvas draft', composer())
|
|
await wait(() => button('Analyze canvas draft', composer())?.disabled === false)
|
|
await click('Save entry', composer())
|
|
await wait(async () => (await api.get(note.id)).entries.some(item => item.id !== entryId && item.comments[0]?.inputMode === inputMode))
|
|
await click('Back to overview')
|
|
await click('Switch to to-dos')
|
|
await wait(() => host.querySelector<HTMLButtonElement>('.magic-todo-list-item__content'))
|
|
host.querySelector<HTMLButtonElement>('.magic-todo-list-item__content')!.click()
|
|
await click('Analyze with AI')
|
|
await wait(async () => (await api.listTodos()).todos[0]?.comments[0]?.inputMode === inputMode)
|
|
const todo = (await api.listTodos()).todos[0]!
|
|
// Automatic CSS-column pagination must obey the same boundary without embeds.
|
|
await click('Switch to notes')
|
|
await wait(() => host.querySelector('[id^="magic-note-select-"]'))
|
|
host.querySelector<HTMLButtonElement>('[id^="magic-note-select-"]')!.click()
|
|
await wait(() => composer())
|
|
await wait(() => composer().querySelector<HTMLButtonElement>('[data-tool="flow-text"]')?.disabled === false)
|
|
composer().querySelector<HTMLButtonElement>('[data-tool="flow-text"]')!.click()
|
|
const autoFlow = Quill.find(composer().querySelector('.canvas-flow-quill')!) as Quill
|
|
autoFlow.setText('Automatic start\n' + 'Flow line\n'.repeat(180) + 'Automatic excluded tail\n', 'user')
|
|
await click('Analyze canvas draft', composer())
|
|
await wait(() => button('Analyze canvas draft', composer())?.disabled === false)
|
|
const result = { errors, manualRevision, editedRevision, createdAnalyzed: true, todoAnalyzed: todo.comments[0]?.inputMode }
|
|
root.unmount()
|
|
host.remove()
|
|
return result
|
|
}
|
|
|
|
Object.assign(window, { notesAnalysisRegression })
|