diff --git a/.gitignore b/.gitignore index b5c87ff..e11d940 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ node_modules/ # Local-only notes +temp/ POST_OVERVIEW.md diff --git a/doc/agent-smoke-cases.md b/doc/agent-smoke-cases.md index 7d90fb5..e213c3b 100644 --- a/doc/agent-smoke-cases.md +++ b/doc/agent-smoke-cases.md @@ -66,16 +66,20 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM 范围: 抽屉基础控件渲染 +状态: +用例内会显式切到 `整页模式` + 步骤: 1. 先执行 `AGENT-CHROME-001` +2. 在 `选项` 中确认当前为 `整页模式` 断言: 1. `上一帖` 按钮存在 2. `下一帖` 按钮存在 3. `选项` 按钮存在 -4. `回复主题` 按钮存在 +4. `回复主题` 按钮存在但默认隐藏 5. `新标签打开` 链接存在 ### AGENT-CHROME-003:设置面板打开和关闭 @@ -273,7 +277,7 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM 头部 `回复主题` 主入口 + 可选悬浮快捷入口 状态: -当前不在默认批量脚本中 +当前不在默认批量脚本中;用例内会显式切到 `智能预览` 步骤: @@ -310,6 +314,48 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM 2. 文本按钮和链接都保持 `white-space: nowrap` 3. 可见文本按钮的宽度应明显大于高度,避免被挤成竖排窄条 +### AGENT-CHROME-016:正文字号设置应即时生效 + +范围: +抽屉设置中的正文字号滑杆 + +状态: +当前不在默认批量脚本中;用例内会显式切到 `智能预览` + +步骤: + +1. 打开任一主题抽屉 +2. 打开 `选项` +3. 读取第一条帖子正文 `.ld-post-body` 的计算字号 +4. 拖动 `正文字号` 滑杆到另一个档位 + +断言: + +1. 滑杆右侧读数会同步更新成新的 `xxpx` +2. `.ld-post-body` 的计算 `font-size` 会立即更新成对应像素值 +3. 若当前正文里存在 `pre` / `code`,其字号也会同步变化,并保持比正文小 `1px` + +### AGENT-CHROME-017:整页模式下正文字号设置应禁用 + +范围: +默认整页模式下的正文字号控件可用性 + +状态: +当前不在默认批量脚本中;用例内会显式切到 `整页模式` + +步骤: + +1. 打开任一主题抽屉 +2. 打开 `选项` +3. 切到 `整页模式` +4. 读取 `正文字号` 设置项状态 + +断言: + +1. `正文字号` 设置项显示为禁用态 +2. 对应滑杆 `disabled=true` +3. 提示文案明确说明该设置仅在 `智能预览` 下生效 + ## 3. 记录格式 执行完用例后,建议按下面格式留证据: diff --git a/scripts/agent-smoke.sh b/scripts/agent-smoke.sh index 441aae2..1c3e34b 100755 --- a/scripts/agent-smoke.sh +++ b/scripts/agent-smoke.sh @@ -21,6 +21,8 @@ ALL_CASES=( "AGENT-CHROME-013" "AGENT-CHROME-014" "AGENT-CHROME-015" + "AGENT-CHROME-016" + "AGENT-CHROME-017" ) CASES=("${DEFAULT_CASES[@]}") @@ -469,22 +471,37 @@ set_setting_value() { (() => { const key = $key_json; const value = $value_json; - const select = document.querySelector(\`#ld-drawer-settings [data-setting="\${key}"]\`); - if (!(select instanceof HTMLSelectElement)) { + const control = document.querySelector(\`#ld-drawer-settings [data-setting="\${key}"]\`); + if (!(control instanceof HTMLSelectElement) && !(control instanceof HTMLInputElement)) { return { ok: false, reason: "setting-not-found", key }; } - const hasOption = Array.from(select.options).some((option) => option.value === value); - if (!hasOption) { - return { ok: false, reason: "option-not-found", key, value }; + if (control instanceof HTMLSelectElement) { + const hasOption = Array.from(control.options).some((option) => option.value === value); + if (!hasOption) { + return { ok: false, reason: "option-not-found", key, value }; + } + + if (control.value === value) { + return { ok: true, changed: false, key, value }; + } + + control.value = value; + control.dispatchEvent(new Event("change", { bubbles: true })); + return { ok: true, changed: true, key, value }; + } + + if (control.type !== "range") { + return { ok: false, reason: "unsupported-input-type", key, inputType: control.type || "text" }; } - if (select.value === value) { + if (control.value === value) { return { ok: true, changed: false, key, value }; } - select.value = value; - select.dispatchEvent(new Event("change", { bubbles: true })); + control.value = value; + control.dispatchEvent(new Event("input", { bubbles: true })); + control.dispatchEvent(new Event("change", { bubbles: true })); return { ok: true, changed: true, key, value }; })() EOF @@ -622,6 +639,35 @@ EOF ab_eval "$script" } +post_body_font_state() { + local script + script=$(cat <<'EOF' +(() => { + const body = document.querySelector("#ld-drawer-root .ld-post-body"); + const field = document.querySelector('#ld-drawer-settings [data-setting-group="postBodyFontSize"]'); + const control = document.querySelector('#ld-drawer-settings [data-setting="postBodyFontSize"]'); + const value = document.querySelector('#ld-drawer-settings [data-setting-value="postBodyFontSize"]'); + const hint = document.querySelector('#ld-drawer-settings [data-setting-hint="postBodyFontSize"]'); + const code = document.querySelector("#ld-drawer-root .ld-post-body pre, #ld-drawer-root .ld-post-body code"); + return { + pageOpen: document.body.classList.contains("ld-drawer-page-open"), + bodyExists: body instanceof HTMLElement, + bodyFontSize: body ? Math.round(parseFloat(getComputedStyle(body).fontSize) || 0) : null, + codeExists: code instanceof HTMLElement, + codeFontSize: code ? Math.round(parseFloat(getComputedStyle(code).fontSize) || 0) : null, + fieldDisabled: field?.classList.contains("is-disabled") || false, + fieldAriaDisabled: field?.getAttribute("aria-disabled") || null, + controlDisabled: control?.disabled || false, + controlValue: control?.value || null, + valueText: value?.textContent?.trim() || null, + hintText: hint?.textContent?.trim() || null + }; +})() +EOF +) + ab_eval "$script" +} + click_reply_trigger() { local target=$1 local selector_json script @@ -734,22 +780,39 @@ run_001() { run_002() { local case_id="AGENT-CHROME-002" - local state + local original_settings original_preview preview_result state open_topic_by_index 0 >/dev/null || { record_fail "$case_id" "无法打开抽屉" return 0 } + + original_settings=$(settings_values) + original_preview=$(echo "$original_settings" | jq -r '.previewMode // empty') + if [ -z "$original_preview" ]; then + record_fail "$case_id" "无法读取当前预览模式" + return 0 + fi + + preview_result=$(set_setting_value previewMode iframe) + if [ "$(echo "$preview_result" | jq -r '.ok')" != "true" ]; then + record_fail "$case_id" "无法切到整页模式: $preview_result" + return 0 + fi + ab wait 1200 >/dev/null + ensure_settings_closed state=$(drawer_state) + set_setting_value previewMode "$original_preview" >/dev/null || true + if [ "$(echo "$state" | jq -r '.prevText // empty')" = "上一帖" ] \ && [ "$(echo "$state" | jq -r '.nextText // empty')" = "下一帖" ] \ && [ "$(echo "$state" | jq -r '.settingsText // empty')" = "选项" ] \ && [ "$(echo "$state" | jq -r '.replyText // empty')" = "回复主题" ] \ - && [ "$(echo "$state" | jq -r '.replyHidden')" = "false" ] \ + && [ "$(echo "$state" | jq -r '.replyHidden')" = "true" ] \ && [ "$(echo "$state" | jq -r '.newTabText // empty')" = "新标签打开" ]; then - record_pass "$case_id" "toolbar ready" + record_pass "$case_id" "toolbar ready in iframe mode" else - record_fail "$case_id" "toolbar incomplete" + record_fail "$case_id" "toolbar incomplete: $(echo "$state" | jq -c '{prevText, nextText, settingsText, replyText, replyHidden, newTabText}')" fi } @@ -1130,7 +1193,7 @@ run_013() { run_014() { local case_id="AGENT-CHROME-014" - local original_settings original_floating off_result off_state on_result on_state settings_open_state click_result panel_state + local original_settings original_floating original_preview preview_result off_result off_state on_result on_state settings_open_state click_result panel_state local passed=0 local detail="" @@ -1141,13 +1204,22 @@ run_014() { original_settings=$(settings_values) original_floating=$(echo "$original_settings" | jq -r '.floatingReplyButton // empty') - if [ -z "$original_floating" ]; then - record_fail "$case_id" "无法读取悬浮回复入口设置" + original_preview=$(echo "$original_settings" | jq -r '.previewMode // empty') + if [ -z "$original_floating" ] || [ -z "$original_preview" ]; then + record_fail "$case_id" "无法读取当前设置" return 0 fi + preview_result=$(set_setting_value previewMode smart) + if [ "$(echo "$preview_result" | jq -r '.ok')" != "true" ]; then + record_fail "$case_id" "无法切到智能预览: $preview_result" + return 0 + fi + ab wait 1200 >/dev/null + off_result=$(set_setting_value floatingReplyButton off) if [ "$(echo "$off_result" | jq -r '.ok')" != "true" ]; then + set_setting_value previewMode "$original_preview" >/dev/null || true record_fail "$case_id" "无法关闭悬浮回复入口: $off_result" return 0 fi @@ -1175,6 +1247,7 @@ run_014() { close_reply_panel_if_open >/dev/null || true ab wait 400 >/dev/null set_setting_value floatingReplyButton "$original_floating" >/dev/null || true + set_setting_value previewMode "$original_preview" >/dev/null || true ab wait 800 >/dev/null ensure_settings_closed @@ -1230,6 +1303,107 @@ run_015() { fi } +run_016() { + local case_id="AGENT-CHROME-016" + local original_settings original_value original_preview preview_result target_value + local before set_result after + + open_topic_by_index 0 >/dev/null || { + record_fail "$case_id" "无法打开抽屉" + return 0 + } + + original_settings=$(settings_values) + original_value=$(echo "$original_settings" | jq -r '.postBodyFontSize // empty') + original_preview=$(echo "$original_settings" | jq -r '.previewMode // empty') + if [ -z "$original_value" ] || [ -z "$original_preview" ]; then + record_fail "$case_id" "无法读取当前设置" + return 0 + fi + + preview_result=$(set_setting_value previewMode smart) + if [ "$(echo "$preview_result" | jq -r '.ok')" != "true" ]; then + record_fail "$case_id" "无法切到智能预览: $preview_result" + return 0 + fi + ab wait 1200 >/dev/null + ensure_settings_open + before=$(post_body_font_state) + target_value=18 + if [ "$original_value" = "18" ]; then + target_value=13 + fi + + set_result=$(set_setting_value postBodyFontSize "$target_value") + if [ "$(echo "$set_result" | jq -r '.ok')" != "true" ]; then + ensure_settings_closed + record_fail "$case_id" "无法设置正文字号: $set_result" + return 0 + fi + + ab wait 200 >/dev/null + after=$(post_body_font_state) + + set_setting_value postBodyFontSize "$original_value" >/dev/null || true + set_setting_value previewMode "$original_preview" >/dev/null || true + ab wait 200 >/dev/null + ensure_settings_closed + + if [ "$(echo "$before" | jq -r '.pageOpen')" = "true" ] \ + && [ "$(echo "$before" | jq -r '.bodyExists')" = "true" ] \ + && [ "$(echo "$after" | jq -r '.bodyExists')" = "true" ] \ + && [ "$(echo "$after" | jq -r '.controlValue // empty')" = "$target_value" ] \ + && [ "$(echo "$after" | jq -r '.valueText // empty')" = "${target_value}px" ] \ + && [ "$(echo "$after" | jq -r '.bodyFontSize')" = "$target_value" ] \ + && { [ "$(echo "$after" | jq -r '.codeExists')" != "true" ] || [ "$(echo "$after" | jq -r '.codeFontSize')" = "$((target_value - 1))" ]; } \ + && [ "$(echo "$before" | jq -r '.bodyFontSize')" != "$(echo "$after" | jq -r '.bodyFontSize')" ]; then + record_pass "$case_id" "before=$(echo "$before" | jq -c '{bodyFontSize, codeExists, codeFontSize, controlValue, valueText}') after=$(echo "$after" | jq -c '{bodyFontSize, codeExists, codeFontSize, controlValue, valueText}')" + else + record_fail "$case_id" "before=$(echo "$before" | jq -c '{pageOpen, bodyExists, bodyFontSize, codeExists, codeFontSize, controlValue, valueText}') after=$(echo "$after" | jq -c '{pageOpen, bodyExists, bodyFontSize, codeExists, codeFontSize, controlValue, valueText}') target=$target_value" + fi +} + +run_017() { + local case_id="AGENT-CHROME-017" + local original_settings original_preview preview_result state + + open_topic_by_index 0 >/dev/null || { + record_fail "$case_id" "无法打开抽屉" + return 0 + } + + original_settings=$(settings_values) + original_preview=$(echo "$original_settings" | jq -r '.previewMode // empty') + if [ -z "$original_preview" ]; then + record_fail "$case_id" "无法读取当前预览模式" + return 0 + fi + + preview_result=$(set_setting_value previewMode iframe) + if [ "$(echo "$preview_result" | jq -r '.ok')" != "true" ]; then + record_fail "$case_id" "无法切到整页模式: $preview_result" + return 0 + fi + + ab wait 1200 >/dev/null + ensure_settings_open + state=$(post_body_font_state) + + set_setting_value previewMode "$original_preview" >/dev/null || true + ab wait 200 >/dev/null + ensure_settings_closed + + if [ "$(echo "$state" | jq -r '.pageOpen')" = "true" ] \ + && [ "$(echo "$state" | jq -r '.fieldDisabled')" = "true" ] \ + && [ "$(echo "$state" | jq -r '.fieldAriaDisabled // empty')" = "true" ] \ + && [ "$(echo "$state" | jq -r '.controlDisabled')" = "true" ] \ + && echo "$state" | jq -e '.hintText | test("仅智能预览可用")' >/dev/null; then + record_pass "$case_id" "state=$(echo "$state" | jq -c '{fieldDisabled, fieldAriaDisabled, controlDisabled, valueText, hintText}')" + else + record_fail "$case_id" "state=$(echo "$state" | jq -c '{pageOpen, fieldDisabled, fieldAriaDisabled, controlDisabled, controlValue, valueText, hintText}')" + fi +} + run_case() { local case_id=$1 case "$case_id" in @@ -1245,6 +1419,8 @@ run_case() { AGENT-CHROME-013) run_013 ;; AGENT-CHROME-014) run_014 ;; AGENT-CHROME-015) run_015 ;; + AGENT-CHROME-016) run_016 ;; + AGENT-CHROME-017) run_017 ;; *) record_fail "$case_id" "未知用例 ID" ;; diff --git a/src/content.css b/src/content.css index 742ec58..30e05fa 100644 --- a/src/content.css +++ b/src/content.css @@ -1,5 +1,6 @@ :root { --ld-drawer-width: clamp(360px, 42vw, 920px); + --ld-post-body-font-size: 15px; --ld-image-preview-scale: 1; --ld-topic-tracker-left: 50vw; --ld-topic-tracker-top: 112px; @@ -350,11 +351,28 @@ body.ld-drawer-page-open #ld-drawer-root { gap: 6px; } +#ld-drawer-root .ld-setting-field.is-disabled { + opacity: 0.58; +} + +#ld-drawer-root .ld-setting-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + #ld-drawer-root .ld-setting-label { font-size: 12px; color: var(--primary-medium, rgba(15, 23, 42, 0.64)); } +#ld-drawer-root .ld-setting-value { + color: var(--primary-medium, rgba(15, 23, 42, 0.72)); + font-size: 12px; + font-variant-numeric: tabular-nums; +} + #ld-drawer-root .ld-setting-hint { font-size: 11px; color: var(--primary-medium, rgba(15, 23, 42, 0.56)); @@ -371,6 +389,23 @@ body.ld-drawer-page-open #ld-drawer-root { padding: 10px 12px; } +#ld-drawer-root .ld-setting-range { + width: 100%; + margin: 0; + accent-color: var(--tertiary, #3b82f6); + cursor: pointer; +} + +#ld-drawer-root .ld-setting-field.is-disabled .ld-setting-range { + cursor: not-allowed; +} + +#ld-drawer-root .ld-setting-range:focus-visible { + outline: 2px solid color-mix(in srgb, var(--tertiary, #3b82f6) 24%, transparent); + outline-offset: 3px; + border-radius: 999px; +} + #ld-drawer-root .ld-settings-reset { align-self: end; justify-self: end; @@ -707,7 +742,7 @@ body.ld-drawer-page-open #ld-drawer-root { #ld-drawer-root .ld-post-body { padding: 16px; line-height: 1.72; - font-size: 14px; + font-size: var(--ld-post-body-font-size, 14px); } #ld-drawer-root .ld-post-actions { @@ -790,7 +825,7 @@ body.ld-drawer-page-open #ld-drawer-root { #ld-drawer-root .ld-post-body pre, #ld-drawer-root .ld-post-body code { - font-size: 13px; + font-size: max(12px, calc(var(--ld-post-body-font-size, 14px) - 1px)); } #ld-drawer-root .ld-post-body pre { diff --git a/src/content.js b/src/content.js index f406477..2b9384e 100644 --- a/src/content.js +++ b/src/content.js @@ -10,19 +10,22 @@ const IMAGE_PREVIEW_SCALE_MIN = 1; const IMAGE_PREVIEW_SCALE_MAX = 4; const IMAGE_PREVIEW_SCALE_STEP = 0.2; + const POST_BODY_FONT_SIZE_MIN = 13; + const POST_BODY_FONT_SIZE_MAX = 18; const REPLY_UPLOAD_MARKER = "\u2063"; const POST_ACTION_TYPE_IDS = { like: 2 }; const DEFAULT_SETTINGS = { - previewMode: "smart", + previewMode: "iframe", postMode: "all", + postBodyFontSize: 15, authorFilter: "all", replyOrder: "default", floatingReplyButton: "off", - drawerWidth: "medium", + drawerWidth: "narrow", drawerWidthCustom: 720, - drawerMode: "push" + drawerMode: "overlay" }; const DRAWER_WIDTHS = { narrow: "clamp(320px, 34vw, 680px)", @@ -106,6 +109,10 @@ openInTab: null, settingsPanel: null, settingsCard: null, + postBodyFontSizeField: null, + postBodyFontSizeControl: null, + postBodyFontSizeValue: null, + postBodyFontSizeHint: null, settingsCloseButton: null, settingsToggle: null, latestRepliesRefreshButton: null, @@ -206,6 +213,21 @@ +