From 0681e70c97faff307e73ea340adafa22cbe215a7 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Sat, 26 Sep 2026 16:30:04 +0800 Subject: [PATCH] fix(mobile): fill the note column for images on narrow screens A stored desktop width such as 35% stays in the note. Phone-width layouts show the image at the text column width. --- apps/ios/EditorSource/src/main.ts | 3 +- .../src/components/LocalTiptapEditor.tsx | 2 ++ apps/web/src/main.tsx | 3 ++ apps/web/src/mobile-edit.tsx | 3 ++ packages/shared/src/image-display.test.mjs | 19 +++++++++++ packages/shared/src/image-display.ts | 32 +++++++++++++++++++ 6 files changed, 61 insertions(+), 1 deletion(-) diff --git a/apps/ios/EditorSource/src/main.ts b/apps/ios/EditorSource/src/main.ts index 05b0f054..f1095f5f 100644 --- a/apps/ios/EditorSource/src/main.ts +++ b/apps/ios/EditorSource/src/main.ts @@ -45,11 +45,12 @@ import { import { createEdgeEverMathematics } from "@edgeever/shared/mathematics"; import { createIosImageGallery } from "./document-nodes"; import { createImageInsertTransaction, groupUploadedImages, NATIVE_IMAGE_GALLERY_CSS } from "@edgeever/shared/native-image-gallery"; -import { NEW_IMAGE_WIDTH_PERCENT } from "@edgeever/shared/image-display"; +import { installPhoneImageFillStyle, NEW_IMAGE_WIDTH_PERCENT } from "@edgeever/shared/image-display"; const galleryStyle = document.createElement("style"); galleryStyle.textContent = NATIVE_IMAGE_GALLERY_CSS; document.head.append(galleryStyle); +installPhoneImageFillStyle(); const detailsStyle = document.createElement("style"); detailsStyle.textContent = DETAILS_EDITOR_CSS; diff --git a/apps/mobile/src/components/LocalTiptapEditor.tsx b/apps/mobile/src/components/LocalTiptapEditor.tsx index 74219089..a07c934d 100644 --- a/apps/mobile/src/components/LocalTiptapEditor.tsx +++ b/apps/mobile/src/components/LocalTiptapEditor.tsx @@ -56,6 +56,7 @@ import { DEFAULT_IMAGE_WIDTH_PERCENT, NEW_IMAGE_WIDTH_PERCENT, IMAGE_WIDTH_PRESETS, + PHONE_IMAGE_FILL_CSS, clampImageWidth, parseImageWidth, } from "@edgeever/shared/image-display"; @@ -3358,5 +3359,6 @@ const getEditorStyles = (theme: "light" | "dark", options?: { viewer?: boolean } .edgeever-image-upload-spinner { width: 18px; height: 18px; border: 2px solid ${theme === "dark" ? "#475569" : "#cbd5e1"}; border-top-color: #0f766e; border-radius: 999px; animation: edgeever-image-upload-spin 0.8s linear infinite; } @keyframes edgeever-image-upload-spin { to { transform: rotate(360deg); } } .edgeever-editor-content hr { margin: 24px 0; border: 0; border-top: 1px solid #cbd5e1; } + ${PHONE_IMAGE_FILL_CSS} `; }; diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 84b7fad5..a9c7b8b1 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -8,6 +8,7 @@ import { bootstrapI18n } from "./i18n"; import { emitPwaUpdateNotice } from "./lib/pwa-update-notice"; import { withEnvironmentTitlePrefix } from "./lib/environment-title"; import { initializeTheme, ThemeProvider } from "./components/ThemeProvider"; +import { installPhoneImageFillStyle } from "@edgeever/shared/image-display"; import { applyEditorBodyFontPreference } from "./lib/editor-body-font"; import { installEditorBodyFontFaces } from "./lib/editor-body-font-faces"; import { applyUiFontPreference } from "./lib/ui-font"; @@ -16,6 +17,8 @@ import { reportDesktopRendererReadyAfterPaint } from "./lib/desktop-renderer-rea import "./styles/globals.css"; import "./styles/editor-body-fonts.css"; +installPhoneImageFillStyle(); + const PWA_UPDATE_CHECK_INTERVAL_MS = 10 * 60 * 1_000; const DEVELOPMENT_PWA_RELOAD_KEY = "edgeever.dev-pwa-reset"; const isDesktopRenderer = __EDGEEVER_DESKTOP_BUILD__ || window.edgeeverDesktop?.isAvailable === true; diff --git a/apps/web/src/mobile-edit.tsx b/apps/web/src/mobile-edit.tsx index 81c08ab9..20482e2b 100644 --- a/apps/web/src/mobile-edit.tsx +++ b/apps/web/src/mobile-edit.tsx @@ -6,11 +6,14 @@ import { initializeTheme } from "@/components/ThemeProvider"; import { applyEditorBodyFontPreference } from "@/lib/editor-body-font"; import { installEditorBodyFontFaces } from "@/lib/editor-body-font-faces"; import { applyUiFontPreference } from "@/lib/ui-font"; +import { installPhoneImageFillStyle } from "@edgeever/shared/image-display"; import { bootstrapI18n } from "@/i18n"; import { defaultLocale, getBrowserLocale } from "@/i18n/locales"; import "./styles/mobile-markdown-editor.css"; import "./styles/editor-body-fonts.css"; +installPhoneImageFillStyle(); + declare global { interface Window { edgeeverMobileEditorBootstrap?: { diff --git a/packages/shared/src/image-display.test.mjs b/packages/shared/src/image-display.test.mjs index c7ae6c65..332258f8 100644 --- a/packages/shared/src/image-display.test.mjs +++ b/packages/shared/src/image-display.test.mjs @@ -1,12 +1,17 @@ import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; import { DEFAULT_IMAGE_WIDTH_PERCENT, NEW_IMAGE_WIDTH_PERCENT, IMAGE_WIDTH_PRESETS, + PHONE_IMAGE_FILL_CSS, + PHONE_IMAGE_FILL_MAX_WIDTH_PX, clampImageWidth, parseImageWidth, } from "./image-display.ts"; +const readRepoFile = (relativePath) => readFileSync(new URL(relativePath, import.meta.url), "utf8"); + describe("shared image display widths", () => { test("keeps the four editor presets stable across clients", () => { expect(DEFAULT_IMAGE_WIDTH_PERCENT).toBe(72); @@ -26,4 +31,18 @@ describe("shared image display widths", () => { expect(clampImageWidth(24.6)).toBe(25); expect(clampImageWidth(71.6)).toBe(72); }); + + test("fills the phone column without changing the stored desktop percent", () => { + expect(PHONE_IMAGE_FILL_MAX_WIDTH_PX).toBe(640); + expect(PHONE_IMAGE_FILL_CSS).toContain("@media screen and (max-width: 640px)"); + expect(PHONE_IMAGE_FILL_CSS).toContain("width: 100% !important"); + expect(PHONE_IMAGE_FILL_CSS).toContain("max-width: 100% !important"); + expect(PHONE_IMAGE_FILL_CSS).toContain(".edgeever-image-node"); + expect(PHONE_IMAGE_FILL_CSS).toContain(".ProseMirror img[data-width]"); + expect(PHONE_IMAGE_FILL_CSS).toContain(":not(.edgeever-image-card *)"); + expect(readRepoFile("../../../apps/mobile/src/components/LocalTiptapEditor.tsx")).toContain("${PHONE_IMAGE_FILL_CSS}"); + expect(readRepoFile("../../../apps/ios/EditorSource/src/main.ts")).toContain("installPhoneImageFillStyle("); + expect(readRepoFile("../../../apps/web/src/main.tsx")).toContain("installPhoneImageFillStyle("); + expect(readRepoFile("../../../apps/web/src/mobile-edit.tsx")).toContain("installPhoneImageFillStyle("); + }); }); diff --git a/packages/shared/src/image-display.ts b/packages/shared/src/image-display.ts index 29cad803..4c962f51 100644 --- a/packages/shared/src/image-display.ts +++ b/packages/shared/src/image-display.ts @@ -11,6 +11,38 @@ export const IMAGE_WIDTH_PRESETS = [ export const NEW_IMAGE_WIDTH_PERCENT = IMAGE_WIDTH_PRESETS[0].width; +/** + * Phone columns are already narrow. A desktop percent such as 35% ("较小") + * paints a stamp there, and the image action button covers most of it. + * At or below this viewport, images fill the column. The stored percent stays + * in the note for wide layouts. + */ +export const PHONE_IMAGE_FILL_MAX_WIDTH_PX = 640; + +/** + * `!important` overrides the inline percent on the image figure. + * `screen` leaves print on the stored percent. + * Share-as-image cards (`.edgeever-image-card`) keep the stored percent: + * they are a fixed-width document, not the phone column. + */ +export const PHONE_IMAGE_FILL_CSS = ` +@media screen and (max-width: ${PHONE_IMAGE_FILL_MAX_WIDTH_PX}px) { + :is(.edgeever-image-node, .edgeever-image-upload-result, .edgeever-image-upload-placeholder, .ProseMirror img[data-width]):not(.edgeever-image-card *) { + width: 100% !important; + max-width: 100% !important; + } +} +`; + +export const installPhoneImageFillStyle = () => { + if (typeof document === "undefined") return; + if (document.head.querySelector("style[data-edgeever-phone-image-fill]")) return; + const style = document.createElement("style"); + style.dataset.edgeeverPhoneImageFill = "true"; + style.textContent = PHONE_IMAGE_FILL_CSS; + document.head.append(style); +}; + export type ImageWidthPresetId = (typeof IMAGE_WIDTH_PRESETS)[number]["id"]; export const clampImageWidth = (width: number): number =>