fix: align magic note headers and editor spacing
Align the notes list, detail toolbar and comment direction row, removing stacked top padding. Let text editing controls fill the record card while preserving text and action padding and canvas spacing. Add Electron geometry regressions and document the spacing rules. Release note: Magic Notes now aligns pane headers and removes excess gaps around text editing toolbars and input areas.
This commit is contained in:
@@ -797,6 +797,8 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- 待办 AI 评论保留方向选择、流式输出、重新分析和已有结果;来源加载失败通过应用通知提示,并提供就地重试。从待办打开原笔记后,“返回待办”恢复任务选择、搜索、筛选和列表滚动;宽屏焦点返回任务行,窄屏返回待办详情中的返回按钮。
|
||||
- 笔记详情主区按新到旧连续展示全部记录。打开笔记即在标题下、历史记录上显示空白输入区,保留文字/画布类型选择和保存操作,标题栏与索引均不提供“新增记录”按钮。新增保存成功后重建空白输入区,保留所选类型;自动分析结束也不转入旧记录编辑。旧记录通过显式编辑入口修改,保存使用同一 ID 和返回的修订号。点击索引或当前笔记内的 AI 来源只按 entry ID 滚动定位,不卸载编辑器、不清草稿、不询问离开。空白输入区离开不提示,返回总览、打开其他笔记或改为编辑另一条记录时仍保护未保存修改。
|
||||
- 打开笔记后页面一级标题仍为“魔法笔记”;笔记标题只在记录流顶部出现一次,使用 `22px` 的可编辑大标题,悬停显示下划线,聚焦显示强调色下划线,保留失焦保存、Enter 与 Escape。标题上方为吸顶的细工具栏,依次放置收起笔记列表、记录索引、条数与更新时间、AI 评论开关,均为带工具提示和可访问名称的图标按钮。记录流左右留白 `24px`(窄于 `600px` 时 `12px`),正文最大宽度 `880px` 并居中;吸顶工具栏不受此限制,始终铺满记录流宽度,AI 开关贴在右边缘,底部分隔线横贯整栏。
|
||||
- 桌面笔记详情的列表标题行、正文工具栏与 AI 评论方向标签行顶部对齐,首行统一使用 `40px` 控件高度并垂直居中;AI 区域不叠加面板和表单的顶部留白。
|
||||
- 文字记录的新建与编辑区域铺满卡片内沿,格式工具栏紧接头部,输入区域紧接工具栏;编辑器外层不再增加内边距或圆角。正文保留编辑器内部的文字留白,底部操作栏独立保留内边距与顶部分隔线;画布仍保留 `12px` 内容间距。
|
||||
- 新建记录输入区空闲时收成一行虚线提示框,隐藏类型切换、格式工具栏和保存栏;聚焦、已有草稿、选择画布或出现校验错误时展开,初始约四行并随内容增高。保存成功或切换笔记后恢复为一行。此规则不作用于旧记录编辑器。
|
||||
- 已保存记录的 AI 分析、编辑、删除为 `28px` 图标按钮,精细指针设备上悬停或键盘聚焦记录时才显示;删除悬停时使用危险色,确认条保持原样。粗指针设备始终显示。笔记内部列表、记录流、AI 区域和文字编辑区在精细指针设备上使用细原生滚动条,轨道透明;强制颜色与粗指针设备保留系统默认。说明、摘要和输入提示使用 `--font-body` 与 `--text-secondary`,时间和短标签保留 `--font-caption`。
|
||||
- 左侧记录索引默认收起,只在用户展开后记住展开状态;展开宽度默认 `168px`,可拖动右边界在 `140–320px` 内调整栏宽,缩略图随列宽等比适配。开关位于笔记工具栏,提供 `aria-controls`、展开状态和动作提示;收起后完全移除索引列、分隔条、间隙、边框及轨道,不占宽度,隐藏内容不可聚焦。右侧 AI 评论独立显示、滚动和收起,不与索引共享页签。每条记录一个导航项:画布显示真实首页缩略图及总页数,文字显示摘要,两者均显示类型和时间。两侧分别记住桌面展开状态和宽度,共用原有布局偏好;宽布局分隔条共用样式及 `separator` 语义,方向键每次调整 `16px`,Home、End 跳到当前上下限。两侧宽度按可用空间协调限制,为主区保留至少 `300px`,窗口缩小时不覆盖保存的偏好。内容容器不超过 `800px` 时索引默认隐藏,由同一标题栏按钮开闭标题栏下方固定 `168px` 的抽屉,不显示调宽分隔条;标题栏自身保留控件高度,不保留轨道。选择记录或按 Escape 后关闭,Escape 将焦点返回开关。AI 移至底部,高度不超过可用布局的 40% 且最多 `280px`,不显示分隔条。
|
||||
|
||||
@@ -1248,6 +1248,7 @@
|
||||
}
|
||||
|
||||
.magic-notes-page {
|
||||
--magic-notes-heading-height: 40px;
|
||||
color-scheme: light;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
@@ -1323,6 +1324,14 @@
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.magic-notes-workspace--notes .magic-notes-pane-heading {
|
||||
min-height: var(--magic-notes-heading-height);
|
||||
}
|
||||
|
||||
.magic-notes-workspace--notes .magic-notes-pane-heading :is(.icon-button, .magic-note-tag-manager-button) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.magic-notes-workspace--notes .magic-notes-card-grid {
|
||||
gap: 2px;
|
||||
margin-inline: calc(-1 * var(--space-1));
|
||||
@@ -1929,7 +1938,16 @@
|
||||
}
|
||||
|
||||
.magic-notes-ai-pane {
|
||||
padding: var(--space-4);
|
||||
padding: 0 var(--space-4) var(--space-4);
|
||||
}
|
||||
|
||||
.magic-notes-ai-pane > .magic-notes-ai-controls {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.magic-notes-ai-pane > .magic-notes-ai-controls .inline-help-label {
|
||||
min-height: var(--magic-notes-heading-height);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.magic-todo-source-entry {
|
||||
@@ -2481,7 +2499,7 @@
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
min-height: var(--magic-notes-heading-height);
|
||||
align-items: center;
|
||||
margin-inline: calc(-1 * var(--magic-note-stream-gutter, var(--space-4)));
|
||||
padding: var(--space-1) var(--magic-note-stream-gutter, var(--space-4));
|
||||
@@ -2752,9 +2770,17 @@
|
||||
|
||||
.magic-note-entry__editor {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.magic-note-entry--canvas-editing > .magic-note-entry__editor {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.magic-note-entry__editor > .magic-note-editor,
|
||||
.magic-note-composer > .magic-note-editor {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.magic-note-entry__delete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2771,7 +2797,8 @@
|
||||
}
|
||||
|
||||
.magic-note-entry__editor-actions {
|
||||
margin-top: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.magic-note-editor {
|
||||
|
||||
@@ -30,6 +30,7 @@ it('resizes record columns with native Electron input and preserves desktop widt
|
||||
const fs = require('node:fs');
|
||||
const assert = require('node:assert/strict');
|
||||
app.commandLine.appendSwitch('force-device-scale-factor', '1');
|
||||
app.commandLine.appendSwitch('disable-features', 'CalculateNativeWinOcclusion');
|
||||
app.setPath('userData', ${JSON.stringify(join(directory, 'profile'))});
|
||||
app.whenReady().then(async () => {
|
||||
const win = new BrowserWindow({ show: true, width: 1280, height: 800, useContentSize: true,
|
||||
@@ -126,6 +127,21 @@ it('resizes record columns with native Electron input and preserves desktop widt
|
||||
assert.equal((await rect(saveButton)).width, 0);
|
||||
assert.equal(await js('document.querySelector(".magic-notes-ai-pane")?.checkVisibility() ?? false'), false, 'AI stays closed for notes without comments');
|
||||
assert.equal((await rect('.magic-notes-index-pane')).width, 0, 'Record index starts collapsed');
|
||||
await click('.magic-note-entry button[aria-label="Edit"]');
|
||||
await wait('!!document.querySelector(".magic-note-entry__editor .ql-editor")');
|
||||
const entryBox = await rect('.magic-note-entry');
|
||||
const entryHeader = await rect('.magic-note-entry > header');
|
||||
const editToolbar = await rect('.magic-note-entry__editor .ql-toolbar');
|
||||
const editInput = await rect('.magic-note-entry__editor .ql-container');
|
||||
for (const box of [editToolbar, editInput]) {
|
||||
assert(Math.abs(box.x - entryBox.x - 1) <= 1 && Math.abs(box.width - entryBox.width + 2) <= 1, 'Editor fills the card inside its border: ' + JSON.stringify({ box, entryBox }));
|
||||
}
|
||||
assert(Math.abs(editToolbar.y - entryHeader.y - entryHeader.height) <= 1, 'Toolbar directly follows the entry header');
|
||||
assert(Math.abs(editInput.y - editToolbar.y - editToolbar.height) <= 1, 'Input directly follows the toolbar');
|
||||
await screenshot('magic-notes-entry-editor');
|
||||
await click('.magic-note-entry__editor-actions .secondary-button');
|
||||
await wait('!document.querySelector(".magic-note-entry__editor")');
|
||||
await js('document.querySelector(".magic-notes-stream-pane").scrollTop = 0');
|
||||
const focusComposer = async () => { await js('document.querySelector(' + JSON.stringify(editor) + ').focus()'); await settle(); };
|
||||
await focusComposer();
|
||||
const emptyHeight = (await rect(editor)).height;
|
||||
@@ -141,6 +157,14 @@ it('resizes record columns with native Electron input and preserves desktop widt
|
||||
const index = '.magic-notes-index-pane', handle = '.magic-notes-index-resize-handle', stream = '.magic-notes-stream-pane';
|
||||
await click('#magic-notes-index-toggle');
|
||||
await click('button[aria-controls="magic-notes-ai-pane"]');
|
||||
const listHeading = await rect('#magic-library-panel-notes > .magic-notes-pane-heading');
|
||||
const detailToolbar = await rect('.magic-note-detail-toolbar');
|
||||
const aiHeading = await rect('.magic-notes-ai-pane .inline-help-label');
|
||||
for (const heading of [listHeading, aiHeading]) {
|
||||
assert(Math.abs(heading.y - detailToolbar.y) <= 1, 'Pane headings share a top edge: ' + JSON.stringify({ heading, detailToolbar }));
|
||||
assert(Math.abs(heading.height - detailToolbar.height) <= 1, 'Pane headings share a row height: ' + JSON.stringify({ heading, detailToolbar }));
|
||||
}
|
||||
await screenshot('magic-notes-aligned-headings');
|
||||
assert.equal((await rect(index)).width, 168);
|
||||
const firstRecord = await rect('.magic-note-record');
|
||||
assert.equal(firstRecord.x, (await rect(index)).x, 'Thumbnails align with the left pane edge');
|
||||
|
||||
Reference in New Issue
Block a user