feat(web): give paper themes independent palettes and tidy theme UX

Each paper layout now carries its own colors, ornaments, and reading rhythm instead of mixing with native green. Drop Classic Blue & White, unify preset names, move theme switching fully into the editor toolbar, and keep settings for custom palettes only.
This commit is contained in:
tianma-if committed 2026-09-13 18:56:14 +08:00
1 parent 1aff6ea8e0
commit dfdcfa11e4
25 files changed
+2108 -1007

No files matched your search

+3 -3
View File
@@ -271,9 +271,9 @@ Web, PWA, and desktop upload memo edits after 30 seconds of inactivity and check
- EdgeEver's note-taking product design was also informed by the publicly available product experiences of mature note-taking tools such as [Evernote](https://evernote.com/). The related features were independently designed and implemented by EdgeEver.
- The product design of mind-map and visual-diagram notes was informed by the publicly available product experiences of [XMind](https://xmind.com/) and [ProcessOn](https://www.processon.com/). These features were independently designed and implemented by EdgeEver.
- The "Minimal Emerald" theme typography layout is inspired by [obsidian-minimal](https://github.com/kepano/obsidian-minimal).
- The "Outline Emerald" theme typography layout is inspired by [Outline](https://github.com/outline/outline).
- The "Classic Blue & White" theme is inspired by the early [StackEdit](https://github.com/benweet/stackedit)/[Bootstrap](https://github.com/twbs/bootstrap) Markdown typography style, with Chinese typography details informed by [Marxico](https://maxiang.io/).
- The "Minimal Green" theme typography layout is inspired by [obsidian-minimal](https://github.com/kepano/obsidian-minimal).
- The "Ruled Green" theme typography layout is inspired by [Outline](https://github.com/outline/outline).
- Paper editor themes (Letter, Guide, Blueprint, Journal, Stance, Stub, Brief, Outline, Whitespace, and Grove) take editorial cues from the public typography presets on [墨格](https://moyufang.cn/editor). The implementations, assets, and copy are original to EdgeEver.
## Trademark and Brand Use
+3 -3
View File
@@ -268,9 +268,9 @@ Web、PWA 与桌面端会在停止编辑 30 秒后上传笔记,并在页面可
- EdgeEver 的笔记产品设计也参考了 [Evernote(印象笔记)](https://evernote.com/) 等成熟笔记工具的公开产品体验。相关功能由 EdgeEver 独立设计与实现。
- 思维导图与可视化图表笔记的产品设计参考了 [XMind](https://xmind.com/) 和 [ProcessOn](https://www.processon.com/) 等图表工具的公开产品体验。相关功能由 EdgeEver 独立设计与实现。
- “minimal品牌绿”主题排版架构借鉴于 [obsidian-minimal](https://github.com/kepano/obsidian-minimal)。
- “Outline 品牌绿”主题排版架构借鉴于 [Outline](https://github.com/outline/outline)。
- “经典蓝白”主题借鉴了早期 [StackEdit](https://github.com/benweet/stackedit)/[Bootstrap](https://github.com/twbs/bootstrap) 系 Markdown 排版风格,并参考[马克飞象](https://maxiang.io/)完善中文排版细节。
- “极简绿”主题排版架构借鉴于 [obsidian-minimal](https://github.com/kepano/obsidian-minimal)。
- “描边绿”主题排版架构借鉴于 [Outline](https://github.com/outline/outline)。
- 暖笺、教程、蓝图、刊物、立场、存根、内参、纲要、留白、草木等纸稿编辑器主题,参考了 [墨格](https://moyufang.cn/editor) 公开排版方案的标题层级、章节数字与引言结构。实现、素材与文案均由 EdgeEver 独立完成。
## 商标与品牌使用
+2 -2
View File
@@ -94,7 +94,7 @@ EdgeEver 将高效与优雅融入每一处交互细节,助你专注于思考
- **阅读保护模式 (Reading Protection)**:日常翻阅或查阅笔记时,按下 \`Cmd/Ctrl + E\` 即可一键开启只读保护,锁定当前编辑状态,避免沉浸阅读时误触键盘或意外改动笔记内容;再次按下即可随手切回编辑。
### 🎭 精选排版主题与一键自媒体发布
- **内置排版主题**:支持一键切换 \`WeChat Classic Green (微信经典绿)\`、\`Modern Mint (薄荷青)\`、\`Minimal Emerald (极简祖母绿)\`、\`Outline Emerald (大纲祖母绿)\` 等风格。
- **内置排版主题**:支持一键切换 \`微信绿\`、\`薄荷绿\`、\`极简绿\`、\`描边绿\`,以及暖笺、教程、蓝图等纸稿风格。
- **一键排版复制**:专为内容创作者设计。点击顶部工具栏的**微信公众号图标**,系统自动将当前笔记转为内联 CSS 样式的优雅富文本,直接粘贴至微信公众号后台、Substack 或 WordPress,排版与代码高亮完美保真。
### 🖼️ 8 套精美社交分享海报 (Poster Cards)
@@ -313,7 +313,7 @@ EdgeEver is engineered to provide a distraction-free, elegant writing experience
- **Reading Protection Mode**: When reading or reviewing notes, press \`Cmd/Ctrl + E\` to toggle read-only protection, locking the editor to prevent accidental edits while browsing; press it again to seamlessly resume editing.
### 🎭 Curated Typography Themes & Publishing Export
- **Preset Editor Themes**: Switch effortlessly between \`WeChat Classic Green\`, \`Modern Mint\`, \`Minimal Emerald\`, \`Outline Emerald\`, and more.
- **Preset Editor Themes**: Switch effortlessly between \`WeChat Green\`, \`Mint Green\`, \`Minimal Green\`, \`Ruled Green\`, and paper styles such as Letter, Guide, and Blueprint.
- **One-Click Publishing Export**: Built for publishers and bloggers. Click the **WeChat Icon** in the top bar to format your note with inline CSS. Paste directly into WeChat Official Account editor, Substack, Medium, or WordPress while preserving layout and syntax highlighting.
### 🖼️ 8 Exquisite Social Poster Themes
+5 -1
View File
@@ -38,6 +38,7 @@ import { wrapIndentedParagraphInList } from "@/lib/editor-shortcuts";
import {
EDITOR_THEME_NAMES,
MARKDOWN_THEME_PREFERENCES,
localizeStoredCustomThemeName,
useEditorTheme,
useMarkdownTheme,
} from "@/components/ThemeProvider";
@@ -413,7 +414,10 @@ export const EditorToolbar = ({
))}
{customEditorThemes.map((theme) => (
<SelectItem key={theme.id} value={theme.id}>
{theme.name}
{localizeStoredCustomThemeName(theme.name, {
defaultName: t("settings.customEditorTheme.defaultName"),
newName: (index) => t("settings.customEditorTheme.newName", { n: index }),
})}
</SelectItem>
))}
</SelectContent>
+14 -5
View File
@@ -12,6 +12,7 @@ export {
DEFAULT_CUSTOM_DARK_COLORS,
DEFAULT_CUSTOM_EDITOR_THEME,
DEFAULT_CUSTOM_LIGHT_COLORS,
localizeStoredCustomThemeName,
} from "@/lib/custom-editor-theme";
export type { CustomEditorTheme, ThemeColors } from "@/lib/custom-editor-theme";
@@ -98,6 +99,8 @@ export const isMarkdownLightTheme = (theme: MarkdownThemeName): boolean =>
export const EDITOR_THEME_NAMES = [
"default",
"minimal-emerald",
"outline-emerald",
"letter",
"guide",
"blueprint",
@@ -108,11 +111,8 @@ export const EDITOR_THEME_NAMES = [
"outline",
"zen",
"grove",
"minimal-emerald",
"outline-emerald",
"wechat-green",
"modern-mint",
"marxico",
"custom",
] as const;
export type EditorThemeName = string;
@@ -216,7 +216,16 @@ export const resolveMarkdownTheme = (
: preference;
export const getStoredEditorTheme = (): string => {
return readLocalStorageItem(EDITOR_THEME_STORAGE_KEY) || "default";
const stored = readLocalStorageItem(EDITOR_THEME_STORAGE_KEY) || "default";
if (stored !== "marxico") return stored;
if (typeof window !== "undefined") {
try {
window.localStorage.setItem(EDITOR_THEME_STORAGE_KEY, "default");
} catch {
// Private mode / blocked storage — preference stays session-only.
}
}
return "default";
};
const normalizeCustomEditorTheme = (theme: CustomEditorTheme): CustomEditorTheme => ({
@@ -248,7 +257,7 @@ export const getStoredCustomEditorThemes = (): CustomEditorTheme[] => {
if (oldTheme && typeof oldTheme.name === "string") {
const migratedTheme: CustomEditorTheme = {
id: "custom-migrated",
name: oldTheme.name || "My custom theme",
name: oldTheme.name || DEFAULT_CUSTOM_EDITOR_THEME.name,
light: normalizeThemeColors({
background: oldTheme.background || DEFAULT_CUSTOM_LIGHT_COLORS.background,
text: oldTheme.text || DEFAULT_CUSTOM_LIGHT_COLORS.text,
@@ -25,21 +25,16 @@ describe("appearance preference", () => {
});
describe("paper editor themes", () => {
test("offers the paper editor themes alongside existing presets", () => {
test("keeps theme switching in the editor toolbar instead of settings", () => {
const preferenceCard = readFileSync(new URL("./PreferenceCard.tsx", import.meta.url), "utf8");
const editorToolbar = readFileSync(new URL("../EditorToolbar.tsx", import.meta.url), "utf8");
expect(preferenceCard).not.toContain('t("settings.publishLayoutTitle")');
expect(preferenceCard).toContain('value="letter"');
expect(preferenceCard).toContain('t("settings.editorThemes.letter")');
expect(preferenceCard).toContain('t("settings.editorThemes.guide")');
expect(preferenceCard).toContain('t("settings.editorThemes.blueprint")');
expect(preferenceCard).toContain('t("settings.editorThemes.journal")');
expect(preferenceCard).toContain('t("settings.editorThemes.stance")');
expect(preferenceCard).toContain('t("settings.editorThemes.stub")');
expect(preferenceCard).toContain('t("settings.editorThemes.brief")');
expect(preferenceCard).toContain('t("settings.editorThemes.outline")');
expect(preferenceCard).toContain('t("settings.editorThemes.zen")');
expect(preferenceCard).toContain('t("settings.editorThemes.grove")');
expect(preferenceCard).not.toContain('t("settings.editorThemeTitle")');
expect(preferenceCard).not.toContain('t("settings.markdownThemeTitle")');
expect(preferenceCard).toContain('t("settings.customEditorTheme.settingsTitle")');
expect(editorToolbar).toContain('t(`settings.editorThemes.${theme}`)');
expect(editorToolbar).toContain("markdownThemePreference");
});
});
@@ -1,4 +1,4 @@
import { AlignHorizontalJustifyCenter, ChartNoAxesCombined, FileCode2, Image, Keyboard, Languages, MousePointerClick, Palette, Sparkles, SunMoon } from "lucide-react";
import { AlignHorizontalJustifyCenter, ChartNoAxesCombined, Image, Keyboard, Languages, MousePointerClick, Palette, Sparkles, SunMoon } from "lucide-react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import type { EditorContentAlignment, ShortcutSettings } from "@/lib/app-helpers";
@@ -40,15 +40,13 @@ import {
import { ShortcutSettingsItem } from "./ShortcutSettingsItem";
import { CustomEditorThemeDialog } from "./CustomEditorThemeDialog";
import {
MARKDOWN_THEME_PREFERENCES,
MERMAID_THEME_PREFERENCES,
useAppearanceTheme,
useEditorTheme,
useMarkdownTheme,
useMermaidTheme,
DEFAULT_CUSTOM_LIGHT_COLORS,
DEFAULT_CUSTOM_DARK_COLORS,
isNamedEditorTheme,
localizeStoredCustomThemeName,
type CustomEditorTheme,
type ThemePreference,
} from "../ThemeProvider";
@@ -79,7 +77,6 @@ export const PreferenceCard = ({
} = useEditorTheme();
const { preference: appearancePreference, setPreference: setAppearancePreference } = useAppearanceTheme();
const { mermaidThemePreference, setMermaidTheme } = useMermaidTheme();
const { markdownThemePreference, setMarkdownTheme } = useMarkdownTheme();
const [customThemeDialogOpen, setCustomThemeDialogOpen] = useState(false);
const [editingTheme, setEditingTheme] = useState<CustomEditorTheme | null>(null);
const [activeLocalePreference, setActiveLocalePreference] = useState<AppLocalePreference>(() => getAppLocalePreference());
@@ -151,15 +148,20 @@ export const PreferenceCard = ({
}, []);
const activeCustom = customEditorThemes.find((t) => t.id === editorTheme);
const isPreset = isNamedEditorTheme(editorTheme);
const customThemeLabel = (name: string) =>
localizeStoredCustomThemeName(name, {
defaultName: t("settings.customEditorTheme.defaultName"),
newName: (index) => t("settings.customEditorTheme.newName", { n: index }),
});
const handleEditClick = () => {
if (activeCustom) {
setEditingTheme(activeCustom);
const target = activeCustom ?? customEditorThemes[0];
if (target) {
setEditingTheme({ ...target, name: customThemeLabel(target.name) });
} else {
const newTheme: CustomEditorTheme = {
id: `custom-${Date.now()}`,
name: `New theme ${customEditorThemes.length + 1}`,
name: t("settings.customEditorTheme.newName", { n: customEditorThemes.length + 1 }),
light: DEFAULT_CUSTOM_LIGHT_COLORS,
dark: DEFAULT_CUSTOM_DARK_COLORS,
};
@@ -279,99 +281,24 @@ export const PreferenceCard = ({
</div>
</div>
<div className="flex min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="flex min-w-0 items-start gap-3">
<Palette className={SETTINGS_ITEM_ICON_CLASSNAME} />
<div className="min-w-0">
<div className={SETTINGS_ITEM_TITLE_CLASSNAME}>{t("settings.editorThemeTitle")}</div>
<div className={SETTINGS_ITEM_DESCRIPTION_CLASSNAME}>{t("settings.editorThemeDescription")}</div>
{!isMobile && (
<div className="flex min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="flex min-w-0 items-start gap-3">
<Palette className={SETTINGS_ITEM_ICON_CLASSNAME} />
<div className="min-w-0">
<div className={SETTINGS_ITEM_TITLE_CLASSNAME}>{t("settings.customEditorTheme.settingsTitle")}</div>
<div className={SETTINGS_ITEM_DESCRIPTION_CLASSNAME}>{t("settings.customEditorTheme.settingsDescription")}</div>
</div>
</div>
</div>
<div className="flex w-full shrink-0 flex-col gap-2 sm:w-80 sm:flex-row">
<Select
value={isMobile && !isPreset ? "default" : editorTheme}
onValueChange={(value) => {
if (value === "create-new") {
const newTheme: CustomEditorTheme = {
id: `custom-${Date.now()}`,
name: `New theme ${customEditorThemes.length + 1}`,
light: DEFAULT_CUSTOM_LIGHT_COLORS,
dark: DEFAULT_CUSTOM_DARK_COLORS,
};
setEditingTheme(newTheme);
setCustomThemeDialogOpen(true);
} else {
setEditorTheme(value);
}
}}
>
<SelectTrigger aria-label={t("settings.editorThemeTitle")} className="h-9 w-full min-w-0 flex-1 bg-card">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">{t("settings.editorThemes.default")}</SelectItem>
<SelectItem value="letter">{t("settings.editorThemes.letter")}</SelectItem>
<SelectItem value="guide">{t("settings.editorThemes.guide")}</SelectItem>
<SelectItem value="blueprint">{t("settings.editorThemes.blueprint")}</SelectItem>
<SelectItem value="journal">{t("settings.editorThemes.journal")}</SelectItem>
<SelectItem value="stance">{t("settings.editorThemes.stance")}</SelectItem>
<SelectItem value="stub">{t("settings.editorThemes.stub")}</SelectItem>
<SelectItem value="brief">{t("settings.editorThemes.brief")}</SelectItem>
<SelectItem value="outline">{t("settings.editorThemes.outline")}</SelectItem>
<SelectItem value="zen">{t("settings.editorThemes.zen")}</SelectItem>
<SelectItem value="grove">{t("settings.editorThemes.grove")}</SelectItem>
<SelectItem value="minimal-emerald">{t("settings.editorThemes.minimal-emerald")}</SelectItem>
<SelectItem value="outline-emerald">{t("settings.editorThemes.outline-emerald")}</SelectItem>
<SelectItem value="wechat-green">{t("settings.editorThemes.wechat-green")}</SelectItem>
<SelectItem value="modern-mint">{t("settings.editorThemes.modern-mint")}</SelectItem>
<SelectItem value="marxico">{t("settings.editorThemes.marxico")}</SelectItem>
{!isMobile && (
<>
{customEditorThemes.length > 0 && <div className="my-1 border-t border-slate-100" />}
{customEditorThemes.map((theme) => (
<SelectItem key={theme.id} value={theme.id}>
{theme.name}
</SelectItem>
))}
<div className="my-1 border-t border-slate-100" />
<SelectItem value="create-new" className="text-emerald-700 font-medium">
+ {t("settings.customEditorTheme.create", "Create New Theme...")}
</SelectItem>
</>
)}
</SelectContent>
</Select>
{!isMobile && (
<div className="flex w-full shrink-0 flex-col gap-2 sm:w-auto sm:flex-row">
<Button variant="outline" className="h-9 shrink-0 px-3 text-sm" onClick={handleEditClick}>
{activeCustom ? t("settings.customEditorTheme.edit") : t("settings.customEditorTheme.customize", "Customize")}
{activeCustom || customEditorThemes.length > 0
? t("settings.customEditorTheme.edit")
: t("settings.customEditorTheme.create")}
</Button>
)}
</div>
</div>
<div className="flex min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="flex min-w-0 items-start gap-3">
<FileCode2 className={SETTINGS_ITEM_ICON_CLASSNAME} />
<div className="min-w-0">
<div className={SETTINGS_ITEM_TITLE_CLASSNAME}>{t("settings.markdownThemeTitle")}</div>
<div className={SETTINGS_ITEM_DESCRIPTION_CLASSNAME}>{t("settings.markdownThemeDescription")}</div>
</div>
</div>
<div className="w-full shrink-0 sm:w-80">
<Select value={markdownThemePreference} onValueChange={(value) => setMarkdownTheme(value as typeof markdownThemePreference)}>
<SelectTrigger aria-label={t("settings.markdownThemeTitle")} className="h-9 bg-card">
<SelectValue />
</SelectTrigger>
<SelectContent>
{MARKDOWN_THEME_PREFERENCES.map((theme) => (
<SelectItem key={theme} value={theme}>
{t(`settings.markdownThemes.${theme}`)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)}
<div className="flex min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="flex min-w-0 items-start gap-3">
@@ -3,8 +3,10 @@ import {
CUSTOM_EDITOR_THEME_FILE_SCHEMA,
CUSTOM_EDITOR_THEME_FILE_VERSION,
CustomEditorThemeFileError,
DEFAULT_CUSTOM_EDITOR_THEME_NAME,
DEFAULT_CUSTOM_LIGHT_COLORS,
DEFAULT_CUSTOM_DARK_COLORS,
localizeStoredCustomThemeName,
normalizeThemeColors,
customEditorThemeFileName,
getEditorThemeContrastIssues,
@@ -106,4 +108,11 @@ describe("custom editor theme files", () => {
test("creates a filesystem-safe descriptive filename", () => {
expect(customEditorThemeFileName(theme.name)).toBe("EdgeEver-Calm - Green.json");
});
test("localizes factory custom theme names without rewriting user names", () => {
const copy = { defaultName: "自定义主题", newName: (index) => `新主题 ${index}` };
expect(localizeStoredCustomThemeName(DEFAULT_CUSTOM_EDITOR_THEME_NAME, copy)).toBe("自定义主题");
expect(localizeStoredCustomThemeName("New theme 2", copy)).toBe("新主题 2");
expect(localizeStoredCustomThemeName("Calm / Green", copy)).toBe("Calm / Green");
});
});
+15 -1
View File
@@ -41,14 +41,28 @@ export const DEFAULT_CUSTOM_DARK_COLORS: ThemeColors = {
border: "#44403c",
};
export const DEFAULT_CUSTOM_EDITOR_THEME_NAME = "My custom theme";
export const DEFAULT_CUSTOM_EDITOR_THEME: CustomEditorTheme = {
id: "custom-default",
name: "My custom theme",
name: DEFAULT_CUSTOM_EDITOR_THEME_NAME,
light: DEFAULT_CUSTOM_LIGHT_COLORS,
dark: DEFAULT_CUSTOM_DARK_COLORS,
customCss: "",
};
const NEW_THEME_NAME_PATTERN = /^New theme (\d+)$/;
export const localizeStoredCustomThemeName = (
name: string,
copy: { defaultName: string; newName: (index: number) => string },
) => {
if (name === DEFAULT_CUSTOM_EDITOR_THEME_NAME) return copy.defaultName;
const match = NEW_THEME_NAME_PATTERN.exec(name);
if (match) return copy.newName(Number(match[1]));
return name;
};
export const CUSTOM_EDITOR_THEME_FILE_SCHEMA = "edgeever.editor-theme";
export const CUSTOM_EDITOR_THEME_FILE_VERSION = 1;
export const MAX_CUSTOM_EDITOR_THEME_FILE_BYTES = 64 * 1024;
-10
View File
@@ -12,7 +12,6 @@ const BUILT_IN_EDITOR_THEMES = [
"outline-emerald",
"wechat-green",
"modern-mint",
"marxico",
];
const readDarkThemeTokens = (theme) => {
@@ -107,15 +106,6 @@ describe("dark theme contracts", () => {
}
});
test("Marxico keeps note content legible in dark mode", () => {
const { css, tokens } = readDarkThemeTokens("marxico");
expect(css).toContain(':root.dark .edgeever-editor[data-editor-theme="marxico"]');
expect(css).toContain("color: var(--editor-theme-text);");
expect(contrastRatio(tokens.text, "#191e1b")).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(tokens.heading, "#191e1b")).toBeGreaterThanOrEqual(4.5);
});
test("automatic Mermaid themes follow the resolved appearance", () => {
expect(resolveMermaidTheme("auto", "light")).toBe("zinc-light");
expect(resolveMermaidTheme("auto", "dark")).toBe("zinc-dark");
+89 -22
View File
@@ -3,6 +3,8 @@ import { readFileSync } from "node:fs";
import {
DEFAULT_PUBLISH_LAYOUT,
DEFAULT_PUBLISH_PALETTE,
PAPER_EDITOR_THEME_PALETTES,
PUBLISH_LAYOUT_CHROME,
PUBLISH_LAYOUT_IDS,
PUBLISH_LAYOUT_RHYTHM,
PUBLISH_PALETTE_IDS,
@@ -19,7 +21,7 @@ import {
} from "./publish-layout.ts";
describe("publish layout catalog", () => {
test("keeps ten paper editor themes and eight palettes", () => {
test("keeps ten paper editor themes and a palette per layout", () => {
expect(PUBLISH_LAYOUT_IDS).toEqual([
"letter",
"guide",
@@ -32,15 +34,46 @@ describe("publish layout catalog", () => {
"zen",
"grove",
]);
expect(PUBLISH_PALETTE_IDS).toHaveLength(8);
expect(PUBLISH_PALETTE_IDS).toEqual([
"emerald",
"ink",
"dawn",
"clay",
"navy",
"slate",
"plum",
"forest",
"azure",
"amber",
"teal",
"violet",
"mist",
]);
expect(DEFAULT_PUBLISH_LAYOUT).toBe("letter");
expect(DEFAULT_PUBLISH_PALETTE).toBe("emerald");
expect(PUBLISH_PALETTES.emerald.accent).toBe("#16A06E");
expect(PUBLISH_PALETTES.dawn.accent).toBe("#D9A521");
expect(PUBLISH_PALETTES.azure.accent).toBe("#2F80ED");
expect(PUBLISH_PALETTES.azure.surface).toBe("#F5F8FC");
expect(PUBLISH_PALETTES.azure.surface).not.toBe("#F0FDF4");
expect(PUBLISH_PALETTES.clay.accent).toBe("#E02D32");
});
test("maps paper editor themes to a fixed layout and palette", () => {
expect(resolvePaperEditorTheme("letter")).toEqual({ layout: "letter", palette: "dawn" });
expect(resolvePaperEditorTheme("guide")).toEqual({ layout: "guide", palette: "emerald" });
expect(resolvePaperEditorTheme("guide")).toEqual({ layout: "guide", palette: "azure" });
expect(PAPER_EDITOR_THEME_PALETTES).toEqual({
letter: "dawn",
guide: "azure",
blueprint: "navy",
journal: "slate",
stance: "clay",
stub: "amber",
brief: "teal",
outline: "violet",
zen: "mist",
grove: "forest",
});
expect(resolvePaperEditorTheme("default")).toBeNull();
});
@@ -51,13 +84,18 @@ describe("publish layout catalog", () => {
expect(resolveStoredPublishPalette("dawn")).toBe("dawn");
});
test("numbers letter and blueprint chapters without CSS counters", () => {
test("numbers magazine chapters without CSS counters", () => {
expect(formatChapterIndex(0)).toBe("01");
expect(planHeadingDecoration("h2", "letter", 0)).toEqual({ kind: "chapter", chapterLabel: "01" });
expect(planHeadingDecoration("h2", "blueprint", 1)).toEqual({ kind: "chapter", chapterLabel: "02" });
expect(planHeadingDecoration("h2", "guide", 0)).toEqual({ kind: "chapter", chapterLabel: "01" });
expect(planHeadingDecoration("h2", "blueprint", 1)).toEqual({ kind: "none" });
expect(planHeadingDecoration("h2", "stub", 0)).toEqual({ kind: "chapter", chapterLabel: "01" });
expect(planHeadingDecoration("h2", "guide", 0)).toEqual({ kind: "none" });
expect(planHeadingDecoration("h2", "grove", 0)).toEqual({ kind: "none" });
expect(planHeadingDecoration("h1", "letter", 0)).toEqual({ kind: "title" });
expect(PUBLISH_LAYOUT_CHROME.letter.chapter).toBe("aside-serif");
expect(PUBLISH_LAYOUT_CHROME.letter.quote).toBe("grid-card");
expect(PUBLISH_LAYOUT_CHROME.brief.quoteBanner).toBe("EDITOR'S NOTE");
expect(PUBLISH_LAYOUT_CHROME.grove.small).toBe("slash");
});
});
@@ -67,24 +105,43 @@ describe("publish layout CSS", () => {
const desktop = buildPublishStyles("letter", PUBLISH_PALETTES.emerald, "desktop");
expect(phone.p).toContain("font-size: 15px");
expect(phone.p).toContain("line-height: 1.9");
expect(phone.p).toContain("margin: 0 0 16px");
expect(phone.p).toContain("line-height: 2");
expect(phone.p).toContain("letter-spacing: 0.06em");
expect(phone.p).toContain("margin: 0 0 28px");
expect(phone.h1).toContain("font-size: 24px");
expect(phone.h2).toContain("font-size: 20px");
expect(desktop.p).toContain("font-size: 15px");
expect(desktop.p).toContain("line-height: 1.9");
expect(desktop.p).toContain("line-height: 2");
});
test("uses a looser reading rhythm than the note editor contract", () => {
const letter = buildPublishStyles("letter", PUBLISH_PALETTES.emerald);
const guide = buildPublishStyles("guide", PUBLISH_PALETTES.emerald);
const letter = buildPublishStyles("letter", PUBLISH_PALETTES.dawn);
const blueprint = buildPublishStyles("blueprint", PUBLISH_PALETTES.navy);
expect(letter.p).toContain("line-height: 1.9");
expect(letter.p).toContain("margin: 0 0 24px");
expect(letter.p).toContain("line-height: 2");
expect(letter.p).toContain("letter-spacing: 0.06em");
expect(letter.p).toContain("margin: 0 0 28px");
expect(letter.h1).toContain("text-align: center");
expect(guide.h2).toContain("border-left: 4px solid #16A06E");
expect(letter.blockquote).toContain("border-radius: 12px");
expect(letter.strong).toContain("color: #16A06E");
expect(letter.h1).toContain("letter-spacing: 0.08em");
expect(blueprint.h2).toContain("border-left: 3px solid #4271B3");
expect(letter.blockquote).toContain("border-radius: 18px");
expect(letter.strong).toContain("color: #D9A521");
expect(letter.th).toContain("background: #FFF7DF");
expect(letter.a).toContain("color: #7C5500");
});
test("gives each paper theme its own line height and tracking", () => {
const stance = buildPublishStyles("stance", PUBLISH_PALETTES.clay);
const zen = buildPublishStyles("zen", PUBLISH_PALETTES.mist);
const stub = buildPublishStyles("stub", PUBLISH_PALETTES.amber);
expect(PUBLISH_LAYOUT_RHYTHM.stance.lineHeight).toBe("1.68");
expect(PUBLISH_LAYOUT_RHYTHM.zen.lineHeight).toBe("2.2");
expect(stance.p).toContain("letter-spacing: 0em");
expect(zen.p).toContain("letter-spacing: 0.1em");
expect(stub.h1).toContain("letter-spacing: 0.14em");
expect(new Set(Object.values(PUBLISH_LAYOUT_RHYTHM).map((rhythm) => rhythm.lineHeight)).size).toBe(10);
expect(new Set(Object.values(PUBLISH_LAYOUT_RHYTHM).map((rhythm) => rhythm.letterSpacing)).size).toBe(10);
});
test("keeps WeChat-safe inline CSS for every layout and palette", () => {
@@ -110,11 +167,12 @@ describe("copy pipeline wiring", () => {
expect(source).toContain("ProseMirror-separator");
expect(source).toContain("findOriginalImage");
expect(source).toContain("svgToWeChatImage");
expect(source).toContain("rasterizePublishOrnamentsForWeChat");
expect(source).toContain('fillStyle = "#ffffff"');
expect(source).toContain("image/jpeg");
expect(source).toContain("text-align: center");
expect(preferenceCard).not.toContain('t("settings.publishLayoutTitle")');
expect(preferenceCard).toContain('t("settings.editorThemes.letter")');
expect(preferenceCard).not.toContain('t("settings.editorThemeTitle")');
});
test("drives the rich editor rhythm from paper editor themes", () => {
@@ -122,10 +180,19 @@ describe("copy pipeline wiring", () => {
const css = readFileSync(new URL("../styles/publish-layout.css", import.meta.url), "utf8");
const vars = publishEditorCssVars("letter", "dawn");
expect(PUBLISH_LAYOUT_RHYTHM.letter.lineHeight).toBe("1.9");
expect(PUBLISH_LAYOUT_RHYTHM.letter.paragraphSpacing).toBe("24px");
expect(vars["--editor-body-line-height"]).toBe("1.9");
expect(vars["--publish-accent"]).toBe("#C49A3C");
expect(PUBLISH_LAYOUT_RHYTHM.letter.lineHeight).toBe("2");
expect(PUBLISH_LAYOUT_RHYTHM.letter.paragraphSpacing).toBe("28px");
expect(vars["--editor-body-line-height"]).toBe("2");
expect(vars["--editor-letter-spacing"]).toBe("0.06em");
expect(vars["--editor-heading-letter-spacing"]).toBe("0.08em");
expect(css).toContain("letter-spacing: var(--editor-letter-spacing)");
expect(css).toContain("letter-spacing: var(--editor-heading-letter-spacing)");
expect(vars["--publish-accent"]).toBe("#D9A521");
expect(vars["--publish-accent-grid"]).toBe("rgba(217,165,33,0.14)");
expect(vars["--brand-green-rgb"]).toBe("217 165 33");
expect(vars["--publish-ornament-brush"]).toContain("url(\"data:image/svg+xml");
expect(css).toContain("--editor-theme-accent: var(--publish-accent)");
expect(css).toContain("--brand-green: var(--publish-accent)");
expect(editorPane).toContain("isPaperEditorTheme(editorTheme)");
expect(editorPane).toContain('data-paper-theme={isPaperEditorTheme(editorTheme) ? "true" : undefined}');
expect(editorPane).toContain("publishEditorCssVars");
@@ -135,7 +202,7 @@ describe("copy pipeline wiring", () => {
expect(css).toContain('[data-editor-theme="letter"]');
expect(css).toContain('[data-editor-theme="stance"]');
expect(css).toContain('[data-editor-theme="grove"]');
expect(css).not.toMatch(/\.ProseMirror p\s*\{[^}]*line-height\s*:/);
expect(css).toContain("line-height: var(--editor-body-line-height)");
});
test("phone preview markup applies the paper layout instead of raw editor chrome", () => {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,65 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import {
PUBLISH_LAYOUT_ORNAMENTS,
PUBLISH_ORNAMENT_IDS,
publishOrnamentCssVars,
publishOrnamentDataUri,
renderPublishOrnamentSvg,
} from "./publish-ornaments.ts";
describe("publish ornaments", () => {
test("renders original tintable stationery marks", () => {
expect(PUBLISH_ORNAMENT_IDS).toEqual([
"blossom",
"sprig",
"sun",
"tape",
"quote",
"brush",
"swoosh",
"leaf",
]);
const svg = renderPublishOrnamentSvg("blossom", "#D9A521");
expect(svg).toContain("#D9A521");
expect(svg).not.toContain("{{color}}");
expect(svg).toContain("<svg");
expect(publishOrnamentDataUri("tape", "#E3A321")).toMatch(/^data:image\/svg\+xml;charset=utf-8,/);
});
test("keeps letter, grove, and stub on the original ornament set", () => {
expect(PUBLISH_LAYOUT_ORNAMENTS.letter).toEqual({
titleEnd: "sprig",
titleRule: "brush",
chapterMark: "blossom",
chapterEnd: "sun",
quoteMark: "quote",
quoteTape: "tape",
});
expect(PUBLISH_LAYOUT_ORNAMENTS.grove.headingRule).toBe("swoosh");
expect(PUBLISH_LAYOUT_ORNAMENTS.stub.quoteTape).toBe("tape");
});
test("exposes mask URLs for the writing canvas", () => {
const vars = publishOrnamentCssVars();
expect(vars["--publish-ornament-brush"]).toContain("url(\"data:image/svg+xml");
expect(vars["--publish-ornament-sun"]).toContain("url(\"data:image/svg+xml");
});
});
describe("ornament wiring", () => {
test("paper layout and WeChat copy inject and rasterize ornaments", () => {
const layout = readFileSync(new URL("./publish-layout.ts", import.meta.url), "utf8");
const wechat = readFileSync(new URL("./wechat-copy.ts", import.meta.url), "utf8");
const css = readFileSync(new URL("../styles/publish-layout.css", import.meta.url), "utf8");
expect(layout).toContain("chromeOrnament");
expect(layout).toContain("publishOrnamentCssVars");
expect(layout).toContain("data-ee-publish-ornament");
expect(wechat).toContain("rasterizePublishOrnamentsForWeChat");
expect(wechat).toContain("image/png");
expect(css).toContain("--publish-ornament-brush");
expect(css).toContain("--publish-ornament-tape");
expect(css).toContain("--publish-ornament-sprig");
});
});
+115
View File
@@ -0,0 +1,115 @@
export const PUBLISH_ORNAMENT_IDS = [
"blossom",
"sprig",
"sun",
"tape",
"quote",
"brush",
"swoosh",
"leaf",
] as const;
export type PublishOrnamentId = (typeof PUBLISH_ORNAMENT_IDS)[number];
const COLOR_TOKEN = "{{color}}";
const ORNAMENT_SVG: Record<PublishOrnamentId, string> = {
blossom: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<g fill="${COLOR_TOKEN}">
<path d="M16.2 3.6c1.6 2.4 1.8 5.4.4 7.8-1.6-1.1-3.6-1.6-5.6-1.2 1.1-2.8 2.9-5.1 5.2-6.6Z"/>
<path d="M26.4 10.2c-1.3 2.6-3.6 4.4-6.3 5.1.3-1.9-.2-3.9-1.5-5.4 2.7-.8 5.3-.6 7.8.3Z"/>
<path d="M24.8 22.8c-2.7.8-5.6.2-7.8-1.6 1.4-1.3 2.1-3.2 2-5.2 2.6 1.5 4.4 3.9 5.8 6.8Z"/>
<path d="M8.6 24.4c-1.8-2.3-2.2-5.3-1-7.9 1.8.8 3.8.9 5.6.2-.4 2.9-1.8 5.5-4.6 7.7Z"/>
<path d="M5.2 11.2c2.2-1.9 5.2-2.6 7.9-1.8-.9 1.7-1 3.7-.3 5.5-2.8-.3-5.5-1.5-7.6-3.7Z"/>
<circle cx="16" cy="16" r="2.7"/>
</g>
</svg>`,
sprig: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 48" fill="none">
<path d="M18 46c.4-8.2 1.2-16.6-.2-24.6-1.6-9.2 2.4-16.4 8.8-21" stroke="${COLOR_TOKEN}" stroke-width="1.6" stroke-linecap="round" fill="none"/>
<path d="M17.6 28.2c-4.2-1.6-8.6.4-11.2 4.2 3.8.6 7.4-.4 11.2-4.2Z" fill="${COLOR_TOKEN}"/>
<path d="M18.4 20.4c-3.2-3.4-3-8.4.6-11.6 2.8 3.4 3.2 7.8-.6 11.6Z" fill="${COLOR_TOKEN}"/>
<path d="M20.2 14.2c2.8-3.8 7.8-5.2 12-3.2-2.2 4.2-6.6 6.2-12 3.2Z" fill="${COLOR_TOKEN}"/>
<circle cx="27.4" cy="6.4" r="2.1" fill="${COLOR_TOKEN}"/>
</svg>`,
sun: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<circle cx="16" cy="16" r="5.1" fill="${COLOR_TOKEN}"/>
<g stroke="${COLOR_TOKEN}" stroke-width="1.7" stroke-linecap="round">
<path d="M16 3.4v3.4"/>
<path d="M16 25.2v3.3"/>
<path d="M3.5 16h3.4"/>
<path d="M25.1 16h3.4"/>
<path d="M6.6 6.7 9 9.1"/>
<path d="M23 23l2.3 2.3"/>
<path d="M25.3 6.7 23 9.1"/>
<path d="M9 23 6.7 25.3"/>
</g>
</svg>`,
tape: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 22" fill="none">
<path d="M2.2 4.4 6 2.6l3.4 1.6 3.2-1.5 3.4 1.5 3.3-1.6 3.4 1.6 3.2-1.5 3.4 1.5 3.2-1.6 3.4 1.6 3.3-1.5 3.4 1.5 3.2-1.6 3.4 1.6 3.2-1.5 3.2 2.1-1.4 13.6-3.2 1.6-3.3-1.5-3.4 1.5-3.2-1.6-3.4 1.6-3.3-1.5-3.4 1.5-3.2-1.6-3.4 1.6-3.3-1.5-3.4 1.5-3.2-1.6-3.4 1.6-3.2-1.5-3.2-2.2Z" fill="${COLOR_TOKEN}" fill-opacity=".82"/>
<path d="M8 8.2h48M8 13.6h48" stroke="#fff" stroke-opacity=".35" stroke-width="1" stroke-dasharray="2 3"/>
</svg>`,
quote: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 28" fill="none">
<path fill="${COLOR_TOKEN}" d="M8.2 4.2C5.2 4.2 3 6.6 3 9.6c0 2.4 1.7 4.3 4.1 4.5-.7 2.8-2.4 5.5-4.7 8.1L7.2 24c2.8-3.4 5.2-7.6 5.2-12.2 0-4.6-1.6-7.6-4.2-7.6Zm16.8 0c-3 0-5.2 2.4-5.2 5.4 0 2.4 1.7 4.3 4.1 4.5-.7 2.8-2.4 5.5-4.7 8.1L24 24c2.8-3.4 5.2-7.6 5.2-12.2 0-4.6-1.6-7.6-4.2-7.6Z"/>
</svg>`,
brush: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 18" fill="none">
<path d="M2 11.2c18-4.6 38-6.8 58-6.4 22 .4 40 3.4 62 3.1 12-.2 24-1.6 36-4.2-8 4.8-22 6.8-36 7.4-24 1-46-1.6-70-1.2-16 .2-32 1.8-48 4.8 0-1.2-.6-2.4-2-3.5Z" fill="${COLOR_TOKEN}"/>
</svg>`,
swoosh: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 16" fill="none">
<path d="M3 11.5c22-6.4 48-8.6 72-6.2 14 1.4 28 4.6 45 3.2" stroke="${COLOR_TOKEN}" stroke-width="2.4" stroke-linecap="round" fill="none"/>
</svg>`,
leaf: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 48" fill="none">
<path d="M18 45.2c.6-8.8 1.8-16.6 8.8-25.4C33.6 12.2 32.4 5.4 27.2 2.8 21.2.2 13.6 3.2 8.6 11.4 3.2 20.4 7.8 31.6 18 45.2Z" fill="${COLOR_TOKEN}"/>
<path d="M18 45.2c-1.2-10.4 1.6-19.2 6.4-27.6" stroke="#fff" stroke-opacity=".4" stroke-width="1.4" stroke-linecap="round" fill="none"/>
</svg>`,
};
const compactSvg = (source: string) => source.replace(/\s+/g, " ").trim();
export const renderPublishOrnamentSvg = (id: PublishOrnamentId, color: string) => {
const hex = /^#([0-9a-f]{6})$/i.test(color) ? color : "#111111";
return compactSvg(ORNAMENT_SVG[id].replaceAll(COLOR_TOKEN, hex));
};
export const publishOrnamentDataUri = (id: PublishOrnamentId, color: string) =>
`data:image/svg+xml;charset=utf-8,${encodeURIComponent(renderPublishOrnamentSvg(id, color))}`;
export const publishOrnamentMaskUri = (id: PublishOrnamentId) => publishOrnamentDataUri(id, "#111111");
export type PublishOrnamentPlacement = {
titleEnd?: PublishOrnamentId;
titleRule?: PublishOrnamentId;
chapterMark?: PublishOrnamentId;
chapterEnd?: PublishOrnamentId;
quoteMark?: PublishOrnamentId;
quoteTape?: PublishOrnamentId;
headingRule?: PublishOrnamentId;
};
export const PUBLISH_LAYOUT_ORNAMENTS: Record<string, PublishOrnamentPlacement> = {
letter: {
titleEnd: "sprig",
titleRule: "brush",
chapterMark: "blossom",
chapterEnd: "sun",
quoteMark: "quote",
quoteTape: "tape",
},
grove: {
headingRule: "swoosh",
quoteMark: "quote",
titleEnd: "leaf",
},
stub: {
quoteTape: "tape",
},
};
export const publishOrnamentCssVars = () => ({
"--publish-ornament-blossom": `url("${publishOrnamentMaskUri("blossom")}")`,
"--publish-ornament-sprig": `url("${publishOrnamentMaskUri("sprig")}")`,
"--publish-ornament-sun": `url("${publishOrnamentMaskUri("sun")}")`,
"--publish-ornament-tape": `url("${publishOrnamentMaskUri("tape")}")`,
"--publish-ornament-quote": `url("${publishOrnamentMaskUri("quote")}")`,
"--publish-ornament-brush": `url("${publishOrnamentMaskUri("brush")}")`,
"--publish-ornament-swoosh": `url("${publishOrnamentMaskUri("swoosh")}")`,
"--publish-ornament-leaf": `url("${publishOrnamentMaskUri("leaf")}")`,
});
+44
View File
@@ -231,6 +231,48 @@ const canvasToJpeg = (canvas: HTMLCanvasElement, quality = 0.86) =>
);
});
const svgToTransparentPng = async (svg: string) => {
const objectUrl = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml;charset=utf-8" }));
try {
const image = new Image();
image.src = objectUrl;
await image.decode();
const canvas = document.createElement("canvas");
canvas.width = Math.max(1, image.naturalWidth);
canvas.height = Math.max(1, image.naturalHeight);
const context = canvas.getContext("2d");
if (!context) throw new Error("Could not create ornament canvas");
context.drawImage(image, 0, 0);
const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob((png) => {
if (png) {
resolve(png);
return;
}
reject(new Error("Could not convert ornament to PNG"));
}, "image/png");
});
return blobToDataUrl(blob);
} finally {
URL.revokeObjectURL(objectUrl);
}
};
const rasterizePublishOrnamentsForWeChat = async (root: HTMLElement) => {
const images = Array.from(root.querySelectorAll<HTMLImageElement>("img[data-ee-publish-ornament]"));
await Promise.all(images.map(async (image) => {
const source = image.getAttribute("src")?.trim() ?? "";
if (!source.startsWith("data:image/svg+xml")) return;
try {
const encoded = source.replace(/^data:image\/svg\+xml(?:;charset=utf-8)?,/, "");
const svg = decodeURIComponent(encoded);
image.setAttribute("src", await svgToTransparentPng(svg));
} catch {
// Keep the SVG data URI in the live preview; WeChat copy can drop a broken ornament.
}
}));
};
const svgToWeChatImage = async (svg: string) => {
const markup = prepareSvgMarkup(svg);
const size = getSvgSize(markup);
@@ -445,6 +487,7 @@ export const buildWeChatClipboardHtml = async (editor: Editor) => {
await embedMermaidForWeChat(container, editor);
const originalImages = Array.from(editor.view.dom.querySelectorAll<HTMLImageElement>("img")).filter(isContentImage);
await embedImagesForWeChat(container, originalImages);
await rasterizePublishOrnamentsForWeChat(container);
return container.outerHTML;
};
@@ -457,5 +500,6 @@ export const copyMarkdownToWeChat = async (markdown: string) => {
);
await embedMermaidForWeChat(container);
await embedImagesForWeChat(container);
await rasterizePublishOrnamentsForWeChat(container);
await copyHtmlToClipboard(container.outerHTML, container.textContent ?? "");
};
@@ -3,5 +3,4 @@
@import "./outline-emerald.css";
@import "./wechat-green.css";
@import "./modern-mint.css";
@import "./marxico.css";
@@ -1,390 +0,0 @@
/* Classic StackEdit/Bootstrap-era Markdown typography, adapted for EdgeEver with Marxico as a Chinese presentation reference. */
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) {
--editor-theme-bg: #ffffff;
--editor-theme-text: #2c3f51;
--editor-theme-muted: #668099;
--editor-theme-heading: #2c3f51;
--editor-theme-accent: #1980e6;
--editor-theme-soft: rgb(102 128 153 / 5%);
--editor-theme-border: rgb(102 128 153 / 10%);
--editor-theme-code-bg: #f9f2f4;
--editor-theme-code-text: #c7254e;
--editor-theme-table-stripe: #f9f9f9;
--editor-theme-table-hover: #f5f5f5;
--editor-theme-font: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeiti, "Microsoft YaHei", "WenQuanYi Micro Hei", SimSun, Song, sans-serif;
--editor-theme-radius: 0;
color-scheme: light;
background: #ffffff !important;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror {
max-width: 960px;
padding: 0 35px 56px;
background: #ffffff;
color: #2c3f51;
font-family: var(--editor-theme-font);
font-size: 16px;
line-height: 1.5;
letter-spacing: normal;
tab-size: 4;
text-align: start;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror p {
margin: 0 0 1.1em;
padding: 0;
color: #2c3f51;
font-size: 16px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h1,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h3,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h4,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h5,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h6 {
margin: 1.2em 0 0.6em;
border: 0;
padding: 0;
color: #2c3f51;
font-family: var(--editor-theme-font);
font-weight: 700;
text-align: start;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h1 {
font-size: 41.6px;
line-height: 1.1;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h2 {
font-size: 34.4px;
line-height: 1.1;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h3 {
font-size: 27.2px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h4 {
font-size: 20px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h5 {
font-size: 16px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h6 {
font-size: 13.6px;
line-height: 1.6;
}
@media (max-width: 767px) {
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror {
padding-right: 16px;
padding-left: 16px;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h1 {
font-size: 2.2rem;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h2 {
font-size: 1.85rem;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h3 {
font-size: 1.5rem;
}
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror strong,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror b {
color: inherit;
font-weight: 700;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror em {
color: inherit;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror a:not(.edgeever-attachment-link) {
color: #1980e6;
font-weight: 400;
text-decoration: none;
transition: background-color 150ms ease-in-out, color 150ms ease-in-out, border-color 150ms ease-in-out;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror a:not(.edgeever-attachment-link):hover {
color: #0f4d8a;
text-decoration: underline;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror ul:not([data-type="taskList"]),
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror ol {
margin: 0 0 1.1em;
padding-left: 2em;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror ul:not([data-type="taskList"]) li,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror ol li {
margin: 0;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror li::marker {
color: inherit;
font-weight: 400;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.1em;
border: 0;
border-left: 10px solid rgb(102 128 153 / 7.5%);
border-radius: 0 5px 5px 0;
background: rgb(102 128 153 / 5%);
padding: 15px 20px;
color: #2c3f51;
font-size: 16px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote p:last-child,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote ul:last-child,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote ol:last-child {
margin-bottom: 0;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror code {
border: 0;
border-radius: 4px;
background: #f9f2f4;
padding: 2px 4px;
color: #c7254e;
font-family: "Source Code Pro", Consolas, monospace;
font-size: 0.9em;
line-height: 1.6;
white-space: normal;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror pre {
margin: 0 0 1.1em;
border: 0;
border-radius: 5px;
background: #f6f6f6;
padding: 0;
color: #333333;
font-family: "Source Code Pro", Consolas, monospace;
font-size: 0.9em;
line-height: 1.6;
text-align: start;
word-break: break-word;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror pre code,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-code-source {
border: 0;
border-radius: 0;
background: #23241f;
padding: 1.3em 2em;
color: #f8f8f2;
font-family: "Source Code Pro", Consolas, monospace;
font-size: 0.9em;
line-height: 1.6;
white-space: pre-wrap;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror pre code {
display: block;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-mermaid-code-block:not(.is-source-visible) .edgeever-mermaid-source {
display: none;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-code-copy-button {
border-color: rgb(255 255 255 / 18%);
background: rgb(35 36 31 / 88%);
color: #f8f8f2;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror hr:not([data-edgeever-merge-divider]) {
height: 0;
margin: 2em 0;
border: 0;
border-top: 1px solid rgb(102 128 153 / 10%);
background: transparent;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror img {
max-width: 100%;
border: 0;
border-radius: 0;
box-shadow: none;
vertical-align: middle;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .tableWrapper {
width: fit-content;
max-width: 100%;
margin: 0 0 20px;
overflow-x: auto;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror table {
width: auto;
min-width: 0 !important;
margin: 0;
border-collapse: collapse;
border-spacing: 0;
table-layout: auto;
color: #2c3f51;
font-size: 16px;
line-height: 1.6;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror table col {
width: auto !important;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror th,
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror td {
width: auto;
min-width: 0;
border: 1px solid #dddddd;
padding: 8px;
background: transparent;
color: #2c3f51;
font-family: var(--editor-theme-font);
font-size: 16px;
line-height: 1.6;
text-align: left;
vertical-align: top;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror th {
border-bottom-width: 2px;
font-weight: 700;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror tbody tr:nth-child(even) td {
background: transparent;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror tbody tr:nth-child(odd) td {
background: #f9f9f9;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror tbody tr:hover td {
background: #f5f5f5;
}
.edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block {
--theme-block-bg: rgb(102 128 153 / 5%);
--theme-block-border: rgb(102 128 153 / 10%);
border-radius: 5px;
}
/* Marxico's source palette is light-only. Keep its typography in dark mode,
* but remap every hard-coded reading color so new and existing notes remain legible.
*/
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) {
--editor-theme-bg: var(--workspace-editor);
--editor-theme-text: #d9e1dc;
--editor-theme-muted: #99a7a0;
--editor-theme-heading: #eef2ef;
--editor-theme-accent: #78b7e8;
--editor-theme-soft: #202a25;
--editor-theme-border: #39473f;
--editor-theme-code-bg: #2a2023;
--editor-theme-code-text: #f2b0c2;
--editor-theme-table-stripe: #1d2621;
--editor-theme-table-hover: #24312a;
color-scheme: dark;
background: var(--editor-theme-bg) !important;
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror {
background: var(--editor-theme-bg);
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror p {
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h1,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h2,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h3,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h4,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h5,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror h6 {
color: var(--editor-theme-heading);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror a:not(.edgeever-attachment-link) {
color: var(--editor-theme-accent);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror a:not(.edgeever-attachment-link):hover {
color: #acd5f4;
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
border-left-color: var(--editor-theme-border);
background: var(--editor-theme-soft);
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror code {
background: var(--editor-theme-code-bg);
color: var(--editor-theme-code-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror pre {
background: #151d19;
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-code-copy-button {
border-color: var(--editor-theme-border);
background: #151d19;
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror hr:not([data-edgeever-merge-divider]) {
border-top-color: var(--editor-theme-border);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror table,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror th,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror td {
color: var(--editor-theme-text);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror th,
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror td {
border-color: var(--editor-theme-border);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror tbody tr:nth-child(odd) td {
background: var(--editor-theme-table-stripe);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror tbody tr:hover td {
background: var(--editor-theme-table-hover);
}
:root.dark .edgeever-editor[data-editor-theme="marxico"]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block {
--theme-block-bg: var(--editor-theme-soft);
--theme-block-border: var(--editor-theme-border);
}
+1 -24
View File
@@ -7,7 +7,6 @@ const PRESET_THEME_FILES = [
"outline-emerald.css",
"wechat-green.css",
"modern-mint.css",
"marxico.css",
];
const readStyle = (relativePath) => readFileSync(new URL(relativePath, import.meta.url), "utf8");
@@ -91,7 +90,7 @@ describe("editor typography contract", () => {
});
test("does not let preset themes override body rhythm", () => {
for (const filename of PRESET_THEME_FILES.filter((filename) => filename !== "marxico.css")) {
for (const filename of PRESET_THEME_FILES) {
const source = readStyle(`./editor-themes/${filename}`);
const editorRules = declarationsForSelector(source, ".ProseMirror");
const paragraphRules = declarationsForSelector(source, ".ProseMirror p");
@@ -107,26 +106,4 @@ describe("editor typography contract", () => {
}
});
test("keeps the Marxico preset aligned with the source theme", () => {
const source = readStyle("./editor-themes/marxico.css");
const themeRules = declarationsForSelector(source, '[data-editor-theme="marxico"]:not([data-editor-theme="default"])');
const headingRules = declarationsForSelector(source, ".ProseMirror h1");
const quoteRules = declarationsForSelector(source, ".ProseMirror blockquote");
const inlineCodeRules = declarationsForSelector(source, ".ProseMirror code");
const paragraphRules = declarationsForSelector(source, ".ProseMirror p");
const preCodeRules = declarationsForSelector(source, ".ProseMirror pre code");
const tableRules = declarationsForSelector(source, ".ProseMirror table");
expect(themeRules).toMatch(/--editor-theme-text\s*:\s*#2c3f51/);
expect(themeRules).toMatch(/--editor-theme-accent\s*:\s*#1980e6/);
expect(headingRules).toMatch(/font-size\s*:\s*41\.6px/);
expect(quoteRules).toMatch(/border-left\s*:\s*10px solid/);
expect(inlineCodeRules).toMatch(/color\s*:\s*#c7254e/);
expect(paragraphRules).toMatch(/font-size\s*:\s*16px/);
expect(paragraphRules).toMatch(/margin\s*:\s*0 0 1\.1em/);
expect(preCodeRules).toMatch(/background\s*:\s*#23241f/);
expect(preCodeRules).toMatch(/padding\s*:\s*1\.3em 2em/);
expect(tableRules).toMatch(/width\s*:\s*auto/);
expect(tableRules).toMatch(/min-width\s*:\s*0 !important/);
});
});
+1 -1
View File
@@ -834,7 +834,7 @@ textarea {
font-size: 17px;
font-weight: 650;
line-height: 1.35;
color: #111;
color: var(--publish-text-strong, #111);
}
.edgeever-phone-preview .edgeever-phone-mermaid {
@@ -2,7 +2,6 @@ import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
const globals = readFileSync(new URL("./globals.css", import.meta.url), "utf8");
const marxico = readFileSync(new URL("./editor-themes/marxico.css", import.meta.url), "utf8");
describe("Mermaid editor layout", () => {
test("caps wide-screen diagram blocks without narrowing compact viewports", () => {
@@ -17,9 +16,4 @@ describe("Mermaid editor layout", () => {
);
});
test("keeps Mermaid source collapsed in the classic blue and white theme", () => {
expect(marxico).toMatch(
/\.edgeever-mermaid-code-block:not\(\.is-source-visible\) \.edgeever-mermaid-source\s*{[^}]*display:\s*none;/s,
);
});
});
+451 -146
View File
@@ -1,12 +1,40 @@
/* Paper editor themes: writing canvas matches Copy to WeChat. */
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) {
--editor-theme-bg: var(--publish-bg);
--editor-theme-text: var(--publish-text);
--editor-theme-muted: var(--publish-text-muted);
--editor-theme-heading: var(--publish-text-strong);
--editor-theme-accent: var(--publish-accent);
--editor-theme-soft: var(--publish-accent-soft);
--editor-theme-border: var(--publish-accent-border);
--editor-theme-code-bg: var(--publish-code-bg);
--editor-theme-code-text: var(--publish-code-text);
--editor-theme-table-stripe: color-mix(in srgb, var(--publish-accent-soft) 72%, var(--publish-bg));
--editor-theme-table-hover: var(--publish-accent-soft);
--brand-green: var(--publish-accent);
--brand-green-hover: var(--publish-link);
--brand-green-muted: var(--publish-accent-soft);
--brand-green-soft: var(--publish-accent-soft);
--brand-green-border: var(--publish-accent-border);
--brand-green-text: var(--publish-link);
--memo-content-divider-color: var(--publish-accent-border);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror {
color: var(--publish-text);
background: var(--publish-bg);
letter-spacing: var(--editor-letter-spacing);
line-height: var(--editor-body-line-height);
counter-reset: publish-h2;
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror p {
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror p,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror li,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror blockquote {
color: var(--publish-text);
letter-spacing: var(--editor-letter-spacing);
line-height: var(--editor-body-line-height);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h1,
@@ -15,6 +43,7 @@
color: var(--publish-text-strong);
border: 0;
padding-left: 0;
letter-spacing: var(--editor-heading-letter-spacing);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror strong {
@@ -39,6 +68,7 @@
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror code {
border-color: var(--publish-accent-border);
background: var(--publish-code-bg);
color: var(--publish-code-text);
}
@@ -53,22 +83,69 @@
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror th {
background: var(--publish-accent-soft);
color: var(--publish-text-strong);
border-color: var(--publish-accent-border);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror td {
border-color: var(--publish-divider);
color: var(--publish-text);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block[data-theme-block-kind="intro"],
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block[data-theme-block-kind="key-point"] {
border-color: var(--publish-accent-border);
background: var(--publish-surface);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block:hover .edgeever-theme-block__toolbar,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-theme-block[data-theme-block-selected="true"] .edgeever-theme-block__toolbar {
border-bottom-color: var(--publish-divider);
}
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) .ProseMirror .tableWrapper {
border-color: var(--publish-accent-border);
background: var(--publish-bg);
}
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) .ProseMirror th {
background: var(--publish-accent-soft);
color: var(--publish-text-strong);
border-color: var(--publish-accent-border);
}
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) .ProseMirror td {
background: var(--publish-bg);
border-color: var(--publish-divider);
color: var(--publish-text);
}
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) .ProseMirror tbody tr:nth-child(even) td {
background: color-mix(in srgb, var(--publish-accent-soft) 72%, var(--publish-bg));
}
.edgeever-editor[data-paper-theme][data-editor-theme]:not([data-editor-theme="default"]) .ProseMirror tbody tr:hover td {
background: var(--publish-accent-soft);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-mermaid-preview {
border-color: var(--publish-accent-border);
background: var(--publish-surface);
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-mermaid-tool-button:hover,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-mermaid-tool-button:focus-visible,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror .edgeever-mermaid-tool-button[aria-pressed="true"] {
background: var(--publish-accent-soft);
color: var(--publish-link);
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror strong,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror strong,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror strong {
color: var(--publish-text-strong);
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror hr:not([data-edgeever-merge-divider]),
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror hr:not([data-edgeever-merge-divider]) {
width: 42%;
@@ -76,129 +153,384 @@
margin-right: auto;
}
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h3::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h3::after,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
color: var(--publish-accent);
font-weight: 700;
}
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1::before {
display: block;
margin: 0 0 0.45rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.16em;
line-height: 1.4;
}
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h1::before { content: "GUIDE · ARTICLE"; }
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h1::before { content: "ARTICLE"; }
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h1::before,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h1::before { content: "EDITION · FIELD NOTES"; }
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h1::before { content: "MINIMAL · ARTICLE"; }
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1::before { content: "FIELD NOTES"; }
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h1 {
text-align: center;
font-weight: 800;
border-bottom: 3px solid var(--publish-accent);
padding-bottom: 0.55rem;
margin: 0 0 1.5rem;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1::after {
display: block;
width: 42px;
height: 3px;
margin: 0.55rem 0 0;
background: var(--publish-accent);
content: "";
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h1::before {
display: inline-block;
width: 1.7rem;
height: 2.2rem;
margin-left: 0.45rem;
vertical-align: middle;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-sprig) center / contain no-repeat;
mask: var(--publish-ornament-sprig) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h1::after {
width: 9.5rem;
height: 12px;
margin: 0.55rem auto 0;
background-color: var(--publish-accent);
background-image: none;
-webkit-mask: var(--publish-ornament-brush) center / contain no-repeat;
mask: var(--publish-ornament-brush) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2::after {
display: inline-block;
width: 1.45rem;
height: 1.45rem;
margin-left: 0.5rem;
vertical-align: middle;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-sun) center / contain no-repeat;
mask: var(--publish-ornament-sun) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
position: relative;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
width: 1.7rem;
height: 1.25rem;
margin: 0 0 0.35rem;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-quote) center / contain no-repeat;
mask: var(--publish-ornament-quote) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror blockquote::after,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror blockquote::after {
position: absolute;
top: -0.45rem;
right: 1rem;
width: 3.3rem;
height: 1.15rem;
background-color: var(--publish-accent);
content: "";
transform: rotate(12deg);
-webkit-mask: var(--publish-ornament-tape) center / contain no-repeat;
mask: var(--publish-ornament-tape) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
position: relative;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h1::after {
display: inline-block;
width: 1.25rem;
height: 1.7rem;
margin-left: 0.45rem;
vertical-align: middle;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-leaf) center / contain no-repeat;
mask: var(--publish-ornament-leaf) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-bottom: 0;
padding-bottom: 0;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h2::after {
display: block;
width: 6rem;
height: 0.7rem;
margin-top: 0.3rem;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-swoosh) left / contain no-repeat;
mask: var(--publish-ornament-swoosh) left / contain no-repeat;
}
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h1::after,
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h1::after {
content: "VOL. 01";
width: auto;
height: auto;
background: none;
color: var(--publish-text-muted);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.14em;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2 {
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h2 {
counter-increment: publish-h2;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
content: counter(publish-h2, decimal-leading-zero);
display: block;
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
display: inline-block;
margin-right: 0.7rem;
vertical-align: middle;
color: var(--publish-accent);
font-size: 1.75rem;
font-family: Georgia, "Times New Roman", serif;
font-size: 2.6rem;
font-style: italic;
font-weight: 700;
line-height: 1.1;
margin: 0 0 0.35rem;
line-height: 0.9;
content: counter(publish-h2, decimal-leading-zero);
}
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
content: counter(publish-h2, decimal-leading-zero);
display: block;
color: var(--publish-accent);
font-size: 0.8rem;
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
font-size: 2.4rem;
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
display: inline-block;
min-width: 2rem;
margin-right: 0.7rem;
padding: 0.35rem 0.45rem;
vertical-align: middle;
border-radius: 6px;
background: var(--publish-accent);
color: #fff;
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.12em;
margin: 0 0 0.25rem;
line-height: 1;
text-align: center;
content: counter(publish-h2, decimal-leading-zero);
}
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
display: block;
margin: 0 0 0.35rem;
color: var(--publish-accent);
font-family: Georgia, "Times New Roman", serif;
font-size: 2.8rem;
font-style: italic;
font-weight: 700;
line-height: 0.9;
content: counter(publish-h2, decimal-leading-zero);
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h2::before,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
display: block;
margin: 0 0 0.3rem;
color: var(--publish-text-muted);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.16em;
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
content: "NO. " counter(publish-h2, decimal-leading-zero);
}
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h2::before {
content: counter(publish-h2, decimal-leading-zero) " · CHAPTER ONE";
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty {
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty {
counter-increment: none;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::before,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::before,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::before,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::before {
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h1.is-empty::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h1.is-empty::after,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h2.is-empty::after,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h3.is-empty::before,
.edgeever-editor[data-paper-theme]:not([data-editor-theme="default"]) .ProseMirror h3.is-empty::after {
content: none;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h3 {
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h3,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h3 {
text-align: center;
font-size: 0.95rem;
border-bottom: 2px solid var(--publish-accent-border);
padding-bottom: 0.45rem;
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror h3::after,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h3::after {
display: block;
width: 10rem;
height: 0;
margin: 0.55rem auto 0;
border-top: 3px solid var(--publish-accent);
box-shadow: 28px 0 0 0 var(--publish-accent-border);
content: "";
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h3,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h3,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h3 {
border-left: 3px solid var(--publish-accent);
padding-left: 0.65rem;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h3 {
color: var(--publish-accent);
text-align: center;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h3::before,
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h3::after {
content: "//";
margin: 0 0.55rem;
font-weight: 800;
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h3::before {
content: "— ";
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="letter"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 1.1rem 1rem 1.15rem 1.2rem;
border: 1px solid var(--publish-accent-border);
border-radius: 18px;
background-color: var(--publish-surface);
background-image: linear-gradient(to right, var(--publish-accent-grid) 1px, transparent 1px),
linear-gradient(to bottom, var(--publish-accent-grid) 1px, transparent 1px);
background-size: 30px 30px;
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
display: block;
margin: 0 0 0.2rem;
font-family: Georgia, "Times New Roman", serif;
font-size: 1.8rem;
font-weight: 700;
line-height: 1;
content: "“";
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
display: block;
width: 1.7rem;
height: 1.25rem;
margin: 0 0 0.35rem;
background-color: var(--publish-accent);
content: "";
-webkit-mask: var(--publish-ornament-quote) center / contain no-repeat;
mask: var(--publish-ornament-quote) center / contain no-repeat;
}
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.35rem 0 0.35rem 1rem;
border-left: 3px solid var(--publish-accent);
background: transparent;
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
border-left-width: 4px;
}
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 1rem 1.1rem;
border: 1px solid var(--publish-accent-border);
border-radius: 12px;
background: var(--publish-surface);
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
border-radius: 16px;
}
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-left: 4px solid var(--publish-accent);
padding-left: 0.75rem;
}
.edgeever-editor[data-editor-theme="guide"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror blockquote,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.75rem 1rem;
border-left: 4px solid var(--publish-accent);
background: var(--publish-accent-soft);
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h1 {
text-align: left;
border-top: 3px solid var(--publish-accent);
padding-top: 0.55rem;
}
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h3,
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h3 {
border-bottom: 2px solid var(--publish-accent);
padding-bottom: 0.35rem;
.edgeever-editor[data-editor-theme="blueprint"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-left: 3px solid var(--publish-accent);
padding-left: 0.75rem;
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h1,
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1 {
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1,
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h1,
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h1,
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h1 {
text-align: left;
font-weight: 700;
}
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h1 {
font-weight: 600;
margin: 0 0 2rem;
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-bottom: 1px solid var(--publish-divider);
padding-bottom: 0.45rem;
display: inline-block;
border-bottom: 2px solid var(--publish-accent);
padding-bottom: 0.4rem;
}
.edgeever-editor[data-editor-theme="journal"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.25rem 0 0.25rem 1rem;
border-left: 2px solid var(--publish-divider);
background: transparent;
padding: 1rem 0.5rem;
border-top: 1px solid var(--publish-accent-border);
border-bottom: 1px solid var(--publish-accent-border);
background: var(--publish-surface);
color: var(--publish-text-muted);
font-style: italic;
}
@@ -208,100 +540,73 @@
border-radius: 12px;
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h1 {
font-weight: 800;
border-bottom: 6px solid var(--publish-accent);
padding-bottom: 0.55rem;
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-left: 6px solid var(--publish-accent);
padding-left: 0.85rem;
}
.edgeever-editor[data-editor-theme="stance"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.85rem 1rem;
border-left: 6px solid var(--publish-accent);
padding: 2.4rem 1rem 1rem;
border: 0;
border-radius: 8px;
background: var(--publish-accent-soft);
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h1 {
letter-spacing: 0.12em;
font-size: 1.35rem;
border-bottom: 1px dashed var(--publish-accent);
padding-bottom: 0.55rem;
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h2,
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror h3 {
border-bottom: 1px dashed var(--publish-accent-border);
padding-bottom: 0.4rem;
}
.edgeever-editor[data-editor-theme="stub"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.85rem 1rem;
border: 1px dashed var(--publish-accent);
border-radius: 2px;
background: var(--publish-surface);
color: var(--publish-text-muted);
}
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h1 {
font-size: 1.4rem;
border-bottom: 1px solid var(--publish-accent);
padding-bottom: 0.5rem;
}
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror h2 {
color: var(--publish-accent);
font-size: 1.15rem;
}
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h1 {
letter-spacing: 0.06em;
border-bottom: 1px solid var(--publish-divider);
padding-bottom: 0.5rem;
}
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror h2 {
border-left: 2px solid var(--publish-accent);
padding-left: 0.7rem;
.edgeever-editor[data-editor-theme="brief"]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
display: block;
margin: -2.4rem -1rem 0.85rem;
padding: 0.55rem 1rem;
background: var(--publish-accent);
color: #fff;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.16em;
content: "EDITOR'S NOTE";
}
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 1.5rem;
padding: 0.75rem 1rem;
border: 1px solid var(--publish-divider);
padding: 0;
border: 0;
background: transparent;
color: var(--publish-text-strong);
font-weight: 600;
text-align: center;
}
.edgeever-editor[data-editor-theme="outline"]:not([data-editor-theme="default"]) .ProseMirror blockquote::before {
display: block;
margin: 0 0 0.65rem;
color: var(--publish-text-muted);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.18em;
content: "QUOTE";
}
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror h2 {
font-weight: 500;
margin-top: 2.4rem;
font-weight: 600;
margin-top: 2.2rem;
}
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror blockquote {
margin: 0 0 2rem;
padding: 0.5rem 0;
padding: 0.35rem 1.5rem;
border: 0;
background: transparent;
color: var(--publish-text-muted);
font-style: italic;
text-align: center;
}
.edgeever-editor[data-editor-theme="grove"]:not([data-editor-theme="default"]) .ProseMirror h1 {
border-bottom: 2px solid var(--publish-accent);
padding-bottom: 0.5rem;
.edgeever-editor[data-editor-theme="zen"]:not([data-editor-theme="default"]) .ProseMirror blockquote::after {
display: block;
margin-top: 0.5rem;
color: var(--publish-accent);
letter-spacing: 0.2em;
content: "—";
}
.edgeever-editor[data-publish-surface="phone"][data-paper-theme] .ProseMirror h1 {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: normal;
line-height: 1.4;
margin-bottom: 0.75rem;
}
@@ -322,15 +627,14 @@
padding: 0.65rem 0.75rem;
}
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="letter"] .ProseMirror h2::before {
font-size: 1.15rem;
margin-bottom: 0.2rem;
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="letter"] .ProseMirror h2::before,
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="guide"] .ProseMirror h2::before,
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="brief"] .ProseMirror h2::before {
font-size: 2rem;
}
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="blueprint"] .ProseMirror h2::before,
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="stub"] .ProseMirror h2::before,
.edgeever-editor[data-publish-surface="phone"][data-editor-theme="outline"] .ProseMirror h2::before {
font-size: 0.75rem;
font-size: 2.1rem;
}
:root.dark .edgeever-editor[data-paper-theme] {
@@ -341,6 +645,7 @@
--publish-surface: color-mix(in srgb, var(--publish-accent) 16%, var(--workspace-editor));
--publish-accent-soft: color-mix(in srgb, var(--publish-accent) 14%, var(--workspace-editor));
--publish-accent-border: color-mix(in srgb, var(--publish-accent) 36%, var(--workspace-editor));
--publish-accent-grid: color-mix(in srgb, var(--publish-accent) 18%, transparent);
--publish-divider: color-mix(in srgb, var(--publish-accent) 26%, var(--workspace-editor));
--publish-code-bg: color-mix(in srgb, var(--publish-accent) 10%, var(--workspace-editor));
--publish-code-text: var(--publish-accent);
+1 -1
View File
@@ -67,7 +67,7 @@ EdgeEver is engineered to provide a distraction-free, elegant writing experience
- **Reading Protection Mode**: When reading or reviewing notes, press `Cmd/Ctrl + E` to toggle read-only protection, locking the editor to prevent accidental edits while browsing; press it again to seamlessly resume editing.
### 🎭 Curated Typography Themes & Publishing Export
- **Preset Editor Themes**: Switch effortlessly between `WeChat Classic Green`, `Modern Mint`, `Minimal Emerald`, `Outline Emerald`, and more.
- **Preset Editor Themes**: Switch effortlessly between `WeChat Green`, `Mint Green`, `Minimal Green`, `Ruled Green`, and paper styles such as Letter, Guide, and Blueprint.
- **One-Click Publishing Export**: Built for publishers and bloggers. Click the **WeChat Icon** in the top bar to format your note with inline CSS. Paste directly into WeChat Official Account editor, Substack, Medium, or WordPress while preserving layout and syntax highlighting.
### 🖼️ 8 Exquisite Social Poster Themes
+1 -1
View File
@@ -67,7 +67,7 @@ EdgeEver 将高效与优雅融入每一处交互细节,助你专注于思考
- **阅读保护模式 (Reading Protection)**:日常翻阅或查阅笔记时,按下 `Cmd/Ctrl + E` 即可一键开启只读保护,锁定当前编辑状态,避免沉浸阅读时误触键盘或意外改动笔记内容;再次按下即可随手切回编辑。
### 🎭 精选排版主题与一键自媒体发布
- **内置排版主题**:支持一键切换 `WeChat Classic Green (微信经典绿)`、`Modern Mint (薄荷青)`、`Minimal Emerald (极简祖母绿)`、`Outline Emerald (大纲祖母绿)` 等风格。
- **内置排版主题**:支持一键切换 `微信绿`、`薄荷绿`、`极简绿`、`描边绿`,以及暖笺、教程、蓝图等纸稿风格。
- **一键排版复制**:专为内容创作者设计。点击顶部工具栏的**微信公众号图标**,系统自动将当前笔记转为内联 CSS 样式的优雅富文本,直接粘贴至微信公众号后台、Substack 或 WordPress,排版与代码高亮完美保真。
### 🖼️ 8 套精美社交分享海报 (Poster Cards)
+9 -5
View File
@@ -613,16 +613,20 @@ export const enUS = {
outline: "Outline",
zen: "Whitespace",
grove: "Grove",
"minimal-emerald": "Minimal Emerald",
"outline-emerald": "Outline Emerald",
"wechat-green": "WeChat Classic Green",
"modern-mint": "Modern Mint",
marxico: "Classic Blue & White",
"minimal-emerald": "Minimal Green",
"outline-emerald": "Ruled Green",
"wechat-green": "WeChat Green",
"modern-mint": "Mint Green",
custom: "Custom",
},
customEditorTheme: {
title: "Custom editor theme",
description: "Adjust editor colors. This setting is saved in the current browser.",
settingsTitle: "Custom theme",
settingsDescription: "Switch editor themes from the toolbar. Manage browser-only custom palettes here.",
defaultName: "Custom theme",
newName: "New theme {{n}}",
create: "Create new theme…",
edit: "Edit colors",
import: "Import",
export: "Export",
+9 -5
View File
@@ -613,16 +613,20 @@ export const zhCN = {
outline: "纲要",
zen: "留白",
grove: "草木",
"minimal-emerald": "minimal品牌绿",
"outline-emerald": "Outline 品牌绿",
"wechat-green": "微信经典绿",
"modern-mint": "极客薄荷绿",
marxico: "经典蓝白",
"minimal-emerald": "极简绿",
"outline-emerald": "描边绿",
"wechat-green": "微信绿",
"modern-mint": "薄荷绿",
custom: "自定义",
},
customEditorTheme: {
title: "自定义编辑器主题",
description: "调整正文编辑区的颜色,设置仅保存在当前浏览器中。",
settingsTitle: "自定义主题",
settingsDescription: "全部排版主题在编辑器工具栏切换。这里只管理保存在当前浏览器中的自定义配色。",
defaultName: "自定义主题",
newName: "新主题 {{n}}",
create: "新建主题…",
edit: "编辑配色",
import: "导入",
export: "导出",