diff --git a/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift b/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift index 7e9d6d26..6dddce91 100644 --- a/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift +++ b/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift @@ -64,7 +64,7 @@ enum AppUILocale { /// Local image-share choices, independent of the current note or sheet lifetime. struct NoteImageSharePreferences: Codable, Equatable { - var theme: String = "gradient" + var theme: String = "glass" var fontStyle: String = "auto" var fontSize: String = "lg" var cardWidth: String = "wide" @@ -85,7 +85,7 @@ struct NoteImageSharePreferences: Codable, Equatable { let values = try decoder.container(keyedBy: CodingKeys.self) if let value = try? values.decode(String.self, forKey: .theme), ["gradient", "glass", "quote", "book", "memo", "bento", "black-sun", "frame", "handwritten", "code"].contains(value) { - theme = value + theme = value == "gradient" ? "glass" : value } if let value = try? values.decode(String.self, forKey: .fontStyle), (["auto", "serif", "sans", "mono", "kai"] + NoteImageFontCatalog.choices.map(\.id)).contains(value) { diff --git a/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift b/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift index 07c289a3..a41732c9 100644 --- a/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift +++ b/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift @@ -187,7 +187,6 @@ struct MemoDetailView: View { preferences.noteImageShare = share } )) { - Text(env.preferences.t("默认", en: "Classic", pl: "Klasyczny")).tag("gradient") Text(env.preferences.t("透明", en: "Glass", pl: "Szkło")).tag("glass") Text(env.preferences.t("金句", en: "Quote", pl: "Cytat")).tag("quote") Text(env.preferences.t("书摘", en: "Book excerpt", pl: "Fragment książki")).tag("book") diff --git a/apps/ios/Tests/EdgeEverTests/MobileUIParityTests.swift b/apps/ios/Tests/EdgeEverTests/MobileUIParityTests.swift index a5dc0cc8..004aa954 100644 --- a/apps/ios/Tests/EdgeEverTests/MobileUIParityTests.swift +++ b/apps/ios/Tests/EdgeEverTests/MobileUIParityTests.swift @@ -27,10 +27,18 @@ final class MobileUIParityTests: XCTestCase { XCTAssertEqual(PreferencesStore(defaults: defaults).noteImageShare, NoteImageSharePreferences()) } + func testImageShareOldDefaultBecomesGlass() throws { + let raw = #"{"theme":"gradient","fontStyle":"serif","showTitle":false}"# + let settings = try JSONDecoder().decode(NoteImageSharePreferences.self, from: Data(raw.utf8)) + XCTAssertEqual(settings.theme, "glass") + XCTAssertEqual(settings.fontStyle, "serif") + XCTAssertFalse(settings.showTitle) + } + func testImageShareInvalidFieldsDoNotDiscardValidChoices() throws { let raw = #"{"theme":"removed","fontStyle":"serif","fontSize":12,"cardWidth":"compact","format":"svg","showTitle":false,"showNotebook":true,"showTags":"true","showUpdatedAt":false,"showBranding":false}"# let settings = try JSONDecoder().decode(NoteImageSharePreferences.self, from: Data(raw.utf8)) - XCTAssertEqual(settings.theme, "gradient") + XCTAssertEqual(settings.theme, "glass") XCTAssertEqual(settings.fontStyle, "serif") XCTAssertEqual(settings.fontSize, "lg") XCTAssertEqual(settings.cardWidth, "compact") diff --git a/apps/mobile/src/lib/note-image-preferences.test.mjs b/apps/mobile/src/lib/note-image-preferences.test.mjs index 0a0eac74..7b55c739 100644 --- a/apps/mobile/src/lib/note-image-preferences.test.mjs +++ b/apps/mobile/src/lib/note-image-preferences.test.mjs @@ -8,6 +8,19 @@ const changed = { showTags: true, showUpdatedAt: false, showBranding: false, }; +test('saved classic theme opens as glass and survives persistence and restart', async () => { + let raw = JSON.stringify({ ...changed, theme: 'gradient' }); + const storage = { getItem: async () => raw, setItem: async (_key, value) => { raw = value; } }; + const store = createMobileNoteImagePreferenceStore(storage); + await store.load(); + expect(store.getSnapshot()).toEqual({ ...changed, theme: 'glass' }); + store.update('showTitle', true); + await store.flush(); + const restarted = createMobileNoteImagePreferenceStore(storage); + await restarted.load(); + expect(restarted.getSnapshot()).toEqual({ ...changed, theme: 'glass', showTitle: true }); +}); + test('switching templates persists automatic typography and allows a later manual font', async () => { let raw = JSON.stringify(changed); const storage = { getItem: async () => raw, setItem: async (_key, value) => { raw = value; } }; @@ -55,7 +68,7 @@ test('slow startup reads never write defaults over saved choices', async () => { const loading = store.load(); expect(store.getSnapshot()).toBeUndefined(); expect(store.load()).toBe(loading); - store.update('theme', 'gradient'); + store.update('theme', 'glass'); finishRead(JSON.stringify(changed)); await loading; expect(store.getSnapshot()).toEqual(changed); @@ -94,7 +107,7 @@ test('corrupt fields are isolated and rejected writes keep session choices usabl setItem: async () => { throw new Error('Storage unavailable'); }, }); await store.load(); - expect(store.getSnapshot()).toEqual({ ...changed, theme: 'gradient', fontSize: 'lg', showTitle: true }); + expect(store.getSnapshot()).toEqual({ ...changed, theme: 'glass', fontSize: 'lg', showTitle: true }); store.update('showTitle', false); store.update('theme', 'code'); await store.flush(); diff --git a/apps/mobile/src/screens/WorkspaceMemoDetail.tsx b/apps/mobile/src/screens/WorkspaceMemoDetail.tsx index a283ae35..88fa079a 100644 --- a/apps/mobile/src/screens/WorkspaceMemoDetail.tsx +++ b/apps/mobile/src/screens/WorkspaceMemoDetail.tsx @@ -219,7 +219,6 @@ const loadAuthenticatedSvg = (source: AuthenticatedImageSource) => { }; const MOBILE_THEME_LABELS = { - "gradient": { labelZh: "默认", labelEn: "Classic" }, "glass": { labelZh: "透明", labelEn: "Glass" }, "quote": { labelZh: "金句", labelEn: "Quote" }, "book": { labelZh: "书摘", labelEn: "Book excerpt" }, diff --git a/apps/web/src/lib/note-image-preference.test.mjs b/apps/web/src/lib/note-image-preference.test.mjs index 32054e46..75457455 100644 --- a/apps/web/src/lib/note-image-preference.test.mjs +++ b/apps/web/src/lib/note-image-preference.test.mjs @@ -32,14 +32,20 @@ test('migrates the existing title choice and signature without losing explicit f test('invalid fields fall back independently while valid false choices survive', () => { withStorage({ [key]: JSON.stringify({ theme: 'removed-theme', fontSize: 20, cardWidth: 'compact', format: 'svg', showTags: 'true', showUpdatedAt: false, showBranding: false, author: 12 }) }, () => { - expect(readNoteImagePreferences()).toMatchObject({ theme: 'gradient', fontSize: 'lg', cardWidth: 'compact', format: 'png', showTags: false, showUpdatedAt: false, showBranding: false, author: '' }); + expect(readNoteImagePreferences()).toMatchObject({ theme: 'glass', fontSize: 'lg', cardWidth: 'compact', format: 'png', showTags: false, showUpdatedAt: false, showBranding: false, author: '' }); + }); +}); + +test('saved classic theme opens as glass while preserving custom typography and visibility', () => { + withStorage({ [key]: JSON.stringify({ theme: 'gradient', fontStyle: 'heavy', showTitle: false }) }, () => { + expect(readNoteImagePreferences()).toMatchObject({ theme: 'glass', fontStyle: 'heavy', showTitle: false }); }); }); test('malformed JSON and non-object storage retain usable legacy defaults', () => { for (const stored of ['{broken', 'null', '[]', 'false', '"book"']) { withStorage({ [key]: stored, edgeever_share_image_show_title: 'false' }, () => { - expect(readNoteImagePreferences()).toMatchObject({ theme: 'gradient', showTitle: false }); + expect(readNoteImagePreferences()).toMatchObject({ theme: 'glass', showTitle: false }); }); } }); diff --git a/packages/shared/src/note-image-card.test.mjs b/packages/shared/src/note-image-card.test.mjs index b0322f39..378a6ad6 100644 --- a/packages/shared/src/note-image-card.test.mjs +++ b/packages/shared/src/note-image-card.test.mjs @@ -53,8 +53,9 @@ describe("note image templates", () => { test("old export settings resolve to supported templates through either input", () => { const mapping = { + gradient: "glass", paper: "handwritten", press: "book", highlight: "memo", slate: "frame", mint: "quote", - aurora: "gradient", sunset: "glass", terminal: "code", midnight: "black-sun", editorial: "frame", + aurora: "glass", sunset: "glass", terminal: "code", midnight: "black-sun", editorial: "frame", film: "book", lavender: "glass", notepad: "memo", xuan: "handwritten", polaroid: "frame", warm: "glass", }; for (const [old, active] of Object.entries(mapping)) { @@ -62,9 +63,9 @@ describe("note image templates", () => { expect(resolveTheme(undefined, old)).toBe(active); expect(buildNoteImageCardMarkup({ title: "Note", bodyHtml: "
Text
", theme: old })).toContain(`data-theme="${active}"`); } - expect(resolveTheme()).toBe("gradient"); + expect(resolveTheme()).toBe("glass"); expect(resolveTheme("book", "missing")).toBe("book"); - expect(resolveTheme("__proto__", "constructor")).toBe("gradient"); + expect(resolveTheme("__proto__", "constructor")).toBe("glass"); }); test("explicit fonts override both heading and body for every template", () => { diff --git a/packages/shared/src/note-image-card.ts b/packages/shared/src/note-image-card.ts index fea0d951..af31a101 100644 --- a/packages/shared/src/note-image-card.ts +++ b/packages/shared/src/note-image-card.ts @@ -2,9 +2,9 @@ import { CATALOG_FONT_STACKS, type RemoteNoteImageFont } from "./font-catalog"; export { prepareNoteImageContent } from "./note-image-content"; export type NoteImageFormat = "jpeg" | "png"; -export const NOTE_IMAGE_THEME_OPTIONS = ["gradient", "glass", "quote", "book", "memo", "bento", "black-sun", "frame", "handwritten", "code"] as const; +export const NOTE_IMAGE_THEME_OPTIONS = ["glass", "quote", "book", "memo", "bento", "black-sun", "frame", "handwritten", "code"] as const; export type NoteImageActiveTheme = (typeof NOTE_IMAGE_THEME_OPTIONS)[number]; -type LegacyNoteImageTheme = "paper" | "press" | "highlight" | "slate" | "aurora" | "sunset" | "midnight" | "editorial" | "terminal" | "film" | "mint" | "lavender"; +type LegacyNoteImageTheme = "gradient" | "paper" | "press" | "highlight" | "slate" | "aurora" | "sunset" | "midnight" | "editorial" | "terminal" | "film" | "mint" | "lavender"; export type NoteImageTheme = NoteImageActiveTheme | LegacyNoteImageTheme; export type NoteImageBackground = NoteImageTheme | "warm" | "notepad" | "xuan" | "polaroid"; export type NoteImageFontStyle = "auto" | "sans" | "serif" | "mono" | "kai" | RemoteNoteImageFont; @@ -35,7 +35,7 @@ export const planNativeNoteImageRender = (width: number, height: number) => { }; const LEGACY_THEMES: Record