feat(admin): dock ticket reply composer in conversation and move notes to a dialog

Put the reply composer at the bottom of the conversation card so the
timeline and composer form one viewport-height panel; the send button is
visible on load and Ctrl/Cmd+Enter submits. Internal notes now open in a
dialog instead of expanding inline, so the sidebar no longer grows.

Remove the shared ticket_composer helper and the inline note expansion CSS;
both dialogs share ticket_dialog_head.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-31 15:10:13 +08:00
1 parent 54cc5301c3
commit b9000e1b93
8 files changed
+178 -73

No files matched your search

@@ -39,10 +39,12 @@ test('list and workspace are page views; only creation is a dialog on the list p
assert.match(list, /ticket_page_url\(\['id' => \$id\] \+ \$context\)/);
assert.match(create, /<dialog id="' \. esc_attr\(self::TICKET_CREATE_DIALOG\)/);
assert.match(create, /ticket_form_open\('create', 0,/);
assert.match(js, /dialog\.showModal\(\)/);
assert.match(js, /\.showModal\(\)/);
assert.match(js, /window\.confirm\(strings\.discard\)/);
// 处理工作区不得回到弹窗。
assert.doesNotMatch(detail, /<dialog|data-aksrt-dialog/);
// 处理工作区本身不是弹窗;只有内部备注的输入在弹窗内。
assert.equal((detail.match(/<dialog /g) || []).length, 1);
assert.match(detail, /<dialog id="' \. esc_attr\(self::TICKET_NOTE_DIALOG\)/);
assert.match(detail, /ticket_form_open\('note', \$id, 'aksrt-ticket-dialog__frame'\)/);
assert.doesNotMatch(js + css, /aksrt-ticket-detail-dialog|aksrt-ticket-modal-open|data-aksrt-ticket-panel/);
});
@@ -63,9 +65,13 @@ test('conversation, private notes and final-state confirmation remain distinct',
assert.match(detail, /TicketNoteService::for_ticket\(\$id\)/);
assert.match(detail, /TicketRepository::get_instance\(\)->replies\(\$id\)/);
assert.match(detail, /!empty\(\$quote\['is_deleted'\]\) \? __\('原回复已删除', 'aksrt'\)/);
for (const action of ['reply', 'note']) assert.ok(detail.includes(`ticket_composer('${action}'`));
// 回复框是会话面板的底部,而不是独立卡片。
assert.match(detail, /return \$html \. '<\/ol><\/div>' \. \$footer \. '<\/section>';/);
assert.ok(detail.includes("ticket_form_open('reply', $id, 'aksrt-ticket-composer')"));
assert.match(detail, /data-aksrt-ticket-send-shortcut/);
assert.match(js, /event\.ctrlKey \|\| event\.metaKey/);
assert.ok(detail.includes("ticket_form_open('status'"));
assert.match(detail, /return \$this->ticket_form_open\(\$action, \$id,/);
assert.doesNotMatch(detail, /function ticket_composer\(/);
assert.match(detail, /aksrt-ticket-detail__closed/);
assert.match(detail, /TicketStatusService::can_transition\(\$from, \$key\)/);
assert.match(detail, /data-aksrt-ticket-close-confirm/);