fix(frontend): simplify report dialog controls and feedback

Use highlight-only reason choices, show the associated target on one line, and replace inline submission progress with shared toast feedback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-31 00:14:36 +08:00
1 parent 80aecab586
commit b2811cc248
4 files changed
+86 -49

No files matched your search

@@ -399,7 +399,7 @@ test('report modal uses accessible reasons, automatic target association and opt
assert.match(scripts, /report_settings\(\)/)
const reportJs = read('assets/js/report.js')
for (const part of ['aksrt-report-target', 'name="report_reason"', 'type="radio"', '其他原因', '补充说明', '图片举证', '提交举报', '无需填写链接']) {
for (const part of ['aksrt-report-target', 'name="report_reason"', 'type="radio"', '其他原因', '补充说明', '图片举证', '提交举报', '关联对象:']) {
assert.ok(reportJs.includes(part), `report.js missing form contract: ${part}`)
}
assert.doesNotMatch(reportJs, /report_url|违规链接|data-aksrt-report-reason=|bindAutoHeight/)
@@ -413,6 +413,10 @@ test('report modal uses accessible reasons, automatic target association and opt
assert.match(css, /\.aksrt-report-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
assert.match(css, /600px/)
assert.match(css, /:focus-visible/)
assert.match(css, /\.aksrt-report-reason:has\(input:checked\)/)
assert.match(css, /clip-path: inset\(50%\)/)
assert.match(css, /text-overflow: ellipsis/)
assert.doesNotMatch(reportJs, /aksrt-report-target__icon|无需填写链接|正在提交举报,请稍候/)
assert.match(css, /safe-area-inset-bottom/)
assert.match(css, /\[hidden\] \{ display: none !important;/)
assert.doesNotMatch(modalRuntimeCss, /aksrt-report-modal/, 'retired report styles must not remain in the shared stylesheet')
@@ -208,6 +208,9 @@ test('opens from an article button, associates the target, omits URL input, and
assert.equal(modal.shell.querySelector('[name="report_url"]'), null);
assert.equal(modal.shell.querySelector('input[type="url"]'), null);
assert.ok(modal.shell.querySelector('.aksrt-report-target'));
assert.equal(modal.shell.querySelector('.aksrt-report-target__label').textContent, '关联对象:');
assert.equal(modal.shell.querySelector('.aksrt-report-target__icon'), null);
assert.equal(target.getAttribute('title'), target.textContent);
});
test('requires a radio reason and other text, then sends canonical fields with description', async () => {
@@ -373,7 +376,8 @@ test('server failure preserves values, reenables controls, and allows a retry',
assert.equal(otherInput.disabled, false);
assert.equal(description.disabled, false);
assert.equal(submitButton.disabled, false);
assert.equal(form.querySelector('[data-aksrt-report-error]').textContent, 'Server rejected report');
assert.equal(form.querySelector('[data-aksrt-report-error]').hidden, true);
assert.deepEqual(runtime.notyfCalls.at(-1).slice(0, 3), ['Server rejected report', 'danger', 5]);
submit(modal);
const retry = callsFor(runtime, '/report/submit')[1];
@@ -382,6 +386,40 @@ test('server failure preserves values, reenables controls, and allows a retry',
await flushPromises();
});
test('uses a persistent shared toast per submit attempt without inline submitting text', async () => {
const runtime = createRuntime();
const modal = runtime.openReport();
selectReason(modal, 0);
const form = modal.shell.querySelector('.aksrt-report-form');
const button = form.querySelector('[data-aksrt-report-submit]');
const status = form.querySelector('[data-aksrt-report-status]');
submit(modal);
const firstToast = runtime.notyfCalls.at(-1);
assert.deepEqual(firstToast.slice(0, 3), ['正在提交举报…', 'load', '']);
assert.ok(firstToast[3].startsWith(form.id + '-submit-'));
assert.equal(button.textContent, '提交举报');
assert.equal(button.disabled, true);
assert.equal(status.textContent, '');
assert.equal(form.getAttribute('aria-busy'), 'true');
submit(modal);
assert.equal(runtime.notyfCalls.length, 1);
callsFor(runtime, '/report/submit')[0].resolve({ success: false, message: 'Retry report' });
await flushPromises();
assert.deepEqual(runtime.notyfCalls.at(-1), ['Retry report', 'danger', 5, firstToast[3]]);
assert.equal(button.disabled, false);
assert.equal(status.textContent, '');
submit(modal);
const secondToast = runtime.notyfCalls.at(-1);
assert.equal(secondToast[1], 'load');
assert.notEqual(secondToast[3], firstToast[3]);
callsFor(runtime, '/report/submit')[1].resolve({ success: true, message: 'Report accepted' });
await flushPromises();
assert.deepEqual(runtime.notyfCalls.at(-1), ['Report accepted', 'success', 5, secondToast[3]]);
});
test('blocks duplicate submits and closing an old submitting dialog cannot close the new one', async () => {
const runtime = createRuntime();
const oldModal = runtime.openReport();