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:
1 parent
1aff6ea8e0
commit
dfdcfa11e4
25 files changed
+2108
-1007
No files matched your search
@@ -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
@@ -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 独立完成。
|
||||
|
||||
## 商标与品牌使用
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
+1240
-271
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");
|
||||
});
|
||||
});
|
||||
@@ -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")}")`,
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "导出",
|
||||
|
||||
Reference in new issue
Block a user