fix: use glass as the default note image theme
This commit is contained in:
1 parent
f4732e3df7
commit
4c00dbb1df
11 files changed
+59
-25
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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: "<p>Text</p>", 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", () => {
|
||||
|
||||
@@ -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<Exclude<NoteImageBackground, NoteImageActiveTheme>, NoteImageActiveTheme> = {
|
||||
paper: "handwritten", press: "book", highlight: "memo", slate: "frame", aurora: "gradient",
|
||||
paper: "handwritten", press: "book", highlight: "memo", slate: "frame", aurora: "glass", gradient: "glass",
|
||||
sunset: "glass", midnight: "black-sun", editorial: "frame", terminal: "code", film: "book",
|
||||
mint: "quote", lavender: "glass", warm: "glass", notepad: "memo", xuan: "handwritten", polaroid: "frame",
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const resolveTheme = (background?: NoteImageBackground, theme?: NoteImage
|
||||
if ((NOTE_IMAGE_THEME_OPTIONS as readonly string[]).includes(candidate)) return candidate as NoteImageActiveTheme;
|
||||
if (Object.hasOwn(LEGACY_THEMES, candidate)) return LEGACY_THEMES[candidate as keyof typeof LEGACY_THEMES];
|
||||
}
|
||||
return "gradient";
|
||||
return "glass";
|
||||
};
|
||||
|
||||
export type ThemeStyleConfig = {
|
||||
@@ -73,7 +73,6 @@ const palette = (base: Pick<ThemeStyleConfig, "canvasBg" | "cardBg" | "textColor
|
||||
// Measured against the public Firefly Card templates at their 400px / 440px design widths.
|
||||
// Structure and CSS are implemented here; exports never depend on that service.
|
||||
const ACTIVE_THEMES: Record<NoteImageActiveTheme, ThemeStyleConfig> = {
|
||||
gradient: palette({ canvasBg: "linear-gradient(0deg, #5797f9, #6cd5c4)", cardBg: "#ffffff", textColor: "#000000", cardShadow: "0 5px 10px rgba(99,99,99,0.2), 0 15px 15px rgba(99,99,99,0.2)" }),
|
||||
glass: palette({ canvasBg: "linear-gradient(45deg, #5797f9, #6cd5c4)", cardBg: "rgba(255,255,255,0.7)", textColor: "#000000", cardShadow: "0 5px 10px rgba(99,99,99,0.2), 0 15px 15px rgba(99,99,99,0.2)" }),
|
||||
quote: palette({ canvasBg: "#ffffff", cardBg: "transparent", textColor: "#0a7c07", metaColor: "#497447", accentColor: "#0a7c07" }),
|
||||
book: palette({ canvasBg: "#1b1c1e", cardBg: "transparent", textColor: "#e3d2b3", metaColor: "#c5b89f", isDark: true }),
|
||||
@@ -91,7 +90,7 @@ export const NOTE_IMAGE_THEMES: Record<NoteImageTheme, ThemeStyleConfig> = {
|
||||
} as Record<NoteImageTheme, ThemeStyleConfig>;
|
||||
|
||||
const CANVAS_COLORS: Record<NoteImageActiveTheme, string> = {
|
||||
gradient: "#5797f9", glass: "#5797f9", quote: "#ffffff", book: "#1b1c1e", memo: "#ffffff",
|
||||
glass: "#5797f9", quote: "#ffffff", book: "#1b1c1e", memo: "#ffffff",
|
||||
bento: "#5797f9", "black-sun": "#101010", frame: "#ffffff", handwritten: "#e3dfd4", code: "#5797f9",
|
||||
};
|
||||
export const NOTE_IMAGE_BACKGROUND_COLORS = {
|
||||
@@ -125,7 +124,6 @@ type TemplateTypography = {
|
||||
};
|
||||
|
||||
export const NOTE_IMAGE_TEMPLATE_TYPOGRAPHY = {
|
||||
gradient: { body: ["misans"], heading: ["misans"], headingWeight: 700 },
|
||||
glass: { body: ["misans"], heading: ["mincho"], headingWeight: 400 },
|
||||
quote: { body: ["mincho"], heading: ["mincho"], headingWeight: 700 },
|
||||
book: { body: ["song"], heading: ["song"], headingWeight: 700 },
|
||||
@@ -199,7 +197,7 @@ export type NoteImageCardMarkupOptions = {
|
||||
};
|
||||
|
||||
export const buildNoteImageCardMarkup = ({
|
||||
title, notebook = "", tags = [], updatedAt = "", bodyHtml, theme = "gradient",
|
||||
title, notebook = "", tags = [], updatedAt = "", bodyHtml, theme = "glass",
|
||||
showTitle = false, showNotebook = false, showTags = false, showUpdatedAt = true,
|
||||
showBranding = true, author = "", showAuthor = false, showReadingTime = false, readingTimeText = "",
|
||||
}: NoteImageCardMarkupOptions) => {
|
||||
@@ -255,7 +253,7 @@ export const buildNoteImageCardMarkup = ({
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export const generateCardCss = ({ theme = "gradient", fontStyle = "auto", fontSize = "lg", cardWidth = "wide" }: {
|
||||
export const generateCardCss = ({ theme = "glass", fontStyle = "auto", fontSize = "lg", cardWidth = "wide" }: {
|
||||
theme: NoteImageTheme; fontStyle: NoteImageFontStyle; fontSize: NoteImageFontSize; cardWidth: NoteImageCardWidth;
|
||||
}) => {
|
||||
const active = resolveTheme(undefined, theme);
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { DEFAULT_NOTE_IMAGE_PREFERENCES, updateNoteImagePreference } from "./note-image-preferences";
|
||||
import { DEFAULT_NOTE_IMAGE_PREFERENCES, resolveNoteImagePreferences, updateNoteImagePreference } from "./note-image-preferences";
|
||||
import { NOTE_IMAGE_THEME_OPTIONS, resolveNoteImageTypography } from "./note-image-card";
|
||||
|
||||
test("glass replaces the old default without discarding other saved preferences", () => {
|
||||
expect(DEFAULT_NOTE_IMAGE_PREFERENCES.theme).toBe("glass");
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS[0]).toBe("glass");
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS).not.toContain("gradient");
|
||||
const saved = { ...DEFAULT_NOTE_IMAGE_PREFERENCES, theme: "gradient", fontStyle: "heavy", showTitle: false, author: "天马" };
|
||||
expect(resolveNoteImagePreferences(saved)).toEqual({ ...saved, theme: "glass" });
|
||||
expect(resolveNoteImagePreferences(saved, { ...DEFAULT_NOTE_IMAGE_PREFERENCES, theme: "book" }).theme).toBe("glass");
|
||||
});
|
||||
|
||||
test("selecting any template restores its typography after a custom font", () => {
|
||||
const custom = { ...DEFAULT_NOTE_IMAGE_PREFERENCES, fontStyle: "heavy", showAuthor: true, author: "天马" };
|
||||
for (const theme of NOTE_IMAGE_THEME_OPTIONS) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export type NoteImagePreferences = {
|
||||
};
|
||||
|
||||
export const DEFAULT_NOTE_IMAGE_PREFERENCES: NoteImagePreferences = {
|
||||
theme: "gradient", fontStyle: "auto", fontSize: "lg", cardWidth: "wide", format: "png",
|
||||
theme: "glass", fontStyle: "auto", fontSize: "lg", cardWidth: "wide", format: "png",
|
||||
showTitle: true, showNotebook: false, showTags: false, showUpdatedAt: true,
|
||||
showReadingTime: false, showAuthor: false, showBranding: true, author: "",
|
||||
};
|
||||
@@ -55,6 +55,7 @@ export const resolveNoteImagePreferences = (
|
||||
const preferences = { ...fallback };
|
||||
if (!stored || typeof stored !== "object" || Array.isArray(stored)) return preferences;
|
||||
const values = stored as Record<string, unknown>;
|
||||
if (values.theme === "gradient") preferences.theme = "glass";
|
||||
for (const key of Object.keys(choices) as Array<keyof typeof choices>) {
|
||||
const value = values[key];
|
||||
if (typeof value === "string" && (choices[key] as readonly string[]).includes(value)) {
|
||||
|
||||
@@ -18,7 +18,7 @@ type QualityApi = {
|
||||
setMounted: (mounted: boolean) => void;
|
||||
};
|
||||
type QualityWindow = Window & { imageQuality: QualityApi; copiedImage?: { width: number; height: number; hash: string } };
|
||||
const labels = { gradient: '默认', glass: '透明', quote: '金句', book: '书摘', memo: '备忘录', bento: '便当', 'black-sun': '黑日', frame: '框界', handwritten: '手写', code: '代码' };
|
||||
const labels = { glass: '透明', quote: '金句', book: '书摘', memo: '备忘录', bento: '便当', 'black-sun': '黑日', frame: '框界', handwritten: '手写', code: '代码' };
|
||||
const preamble = `<script type="module">import R from '/@react-refresh';R.injectIntoGlobalHook(window);window.$RefreshReg$=()=>{};window.$RefreshSig$=()=>t=>t;window.__vite_plugin_react_preamble_installed__=true;</script>`;
|
||||
const fixture = fileURLToPath(new URL('./fixtures/note-image-quality.tsx', import.meta.url));
|
||||
|
||||
@@ -82,7 +82,7 @@ for (const theme of NOTE_IMAGE_THEME_OPTIONS) {
|
||||
await boot(page);
|
||||
const preview = page.getByAltText('笔记分享图片预览');
|
||||
await expect(preview).toBeVisible();
|
||||
if (theme !== 'gradient') {
|
||||
if (theme !== 'glass') {
|
||||
const old = await preview.getAttribute('src');
|
||||
await page.getByRole('button', { name: labels[theme], exact: true }).click();
|
||||
await expect(preview).not.toHaveAttribute('src', old!);
|
||||
|
||||
Reference in new issue
Block a user