feat(editor): refine note image share themes with typography affinity and curated layout
This commit is contained in:
1 parent
5334b25fb3
commit
80038dd2d1
12 files changed
+1040
-120
No files matched your search
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
parseDiagramDocument,
|
||||
parseInfographicDocument,
|
||||
parsePosterDocument,
|
||||
parseTableDocument,
|
||||
} from "@edgeever/shared";
|
||||
import { computeDiagramLayoutResult } from "../../../packages/shared/src/diagram-layout.ts";
|
||||
@@ -22,6 +23,8 @@ describe("demo seed catalog", () => {
|
||||
expect(memoIds.size).toBe(DEMO_SEED_MEMOS.length);
|
||||
expect(memoIds.has("memo_demo_overview")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_overview_en")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_poster")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_poster_en")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_infographic")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_infographic_en")).toBe(true);
|
||||
expect(memoIds.has("memo_demo_table")).toBe(true);
|
||||
@@ -64,6 +67,18 @@ describe("demo seed catalog", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("seeds one editable example for poster notes in each language", () => {
|
||||
for (const suffix of ["", "_en"]) {
|
||||
const posterMemo = DEMO_SEED_MEMOS.find((c) => c.id === `memo_demo_poster${suffix}`);
|
||||
expect(posterMemo).toBeDefined();
|
||||
const poster = parsePosterDocument(posterMemo?.markdown);
|
||||
expect(poster).toBeDefined();
|
||||
expect(poster?.templateId).toBe("editorial");
|
||||
expect(poster?.elements.length).toBeGreaterThan(0);
|
||||
expect(poster?.copy?.title).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("seeds one editable example for every native diagram kind in each language", () => {
|
||||
for (const suffix of ["", "_en"]) {
|
||||
const examples = [
|
||||
|
||||
+633
-59
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
serializeDiagramDocument,
|
||||
serializeInfographicDocument,
|
||||
serializePosterDocument,
|
||||
serializeTableDocument,
|
||||
type DiagramDocument,
|
||||
type InfographicDocument,
|
||||
type PosterDocument,
|
||||
type TableDocument,
|
||||
} from "@edgeever/shared";
|
||||
import {
|
||||
@@ -239,14 +241,14 @@ data
|
||||
sequences
|
||||
- label v1.0 架构奠基
|
||||
desc 经典三栏美学、Serverless D1 存储与双向同步游标
|
||||
- label v1.30 原生客户端
|
||||
desc macOS 桌面端、Android (Expo) 与 iOS (SwiftUI) 全端覆盖
|
||||
- label v1.50 AI 原生赋能
|
||||
desc 原生 MCP 协议端点 (/mcp) 与端侧直连行内 AI 助手
|
||||
- label v1.70 生产力矩阵
|
||||
desc 浏览器剪藏扩展、可视化流程图、架构图与思维导图
|
||||
- label v1.88 现代知识工作台
|
||||
desc AI 智能信息图、多维结构化表格与全场景自建部署`;
|
||||
- label v1.50 智能体与全端
|
||||
desc 原生 MCP 协议端点 (/mcp)、双端原生移动客户端与剪藏矩阵
|
||||
- label v1.88 结构化知识台
|
||||
desc AI 智能信息图、多维结构化表格与可视化图表体系
|
||||
- label v1.100 双协议与生态
|
||||
desc 桌面端 ACP 深度驱动本地 Agent、知识库独立发布与全平台客户端
|
||||
- label v1.120 视觉表现与排版
|
||||
desc 原生海报笔记与画布排版、微信聊天完整归档与全场景自建部署`;
|
||||
|
||||
export const DEMO_INFOGRAPHIC_SYNTAX_EN = `infographic sequence-timeline-rounded-rect-node
|
||||
data
|
||||
@@ -254,14 +256,14 @@ data
|
||||
sequences
|
||||
- label v1.0 Foundation
|
||||
desc Classic three-pane layout, Serverless D1 storage & sync cursors
|
||||
- label v1.30 Native Apps
|
||||
desc macOS Desktop, Android (Expo) & iOS (SwiftUI) native clients
|
||||
- label v1.50 AI Native
|
||||
desc Native MCP endpoint (/mcp) & inline private AI writing assistant
|
||||
- label v1.70 Productivity
|
||||
desc Web Clipper extension, visual flowcharts, architectures & mind maps
|
||||
- label v1.88 Modern Workbench
|
||||
desc AI-driven infographics, structured tables & flexible self-hosting`;
|
||||
- label v1.50 Agents & Mobile
|
||||
desc Native MCP endpoint (/mcp), native mobile apps & clipping matrix
|
||||
- label v1.88 Structured Workspace
|
||||
desc AI-driven infographics, structured tables & visual diagram suite
|
||||
- label v1.100 Dual Protocols & Web
|
||||
desc Desktop ACP for local agents, public notebook site publishing & full platform apps
|
||||
- label v1.120 Visual Canvas & Layout
|
||||
desc Native poster notes & canvas layout, WeChat chat archiving & flexible self-hosting`;
|
||||
|
||||
export const DEMO_INFOGRAPHIC_DOCUMENT_ZH: InfographicDocument = {
|
||||
schemaVersion: 1,
|
||||
@@ -273,6 +275,484 @@ export const DEMO_INFOGRAPHIC_DOCUMENT_EN: InfographicDocument = {
|
||||
syntax: DEMO_INFOGRAPHIC_SYNTAX_EN,
|
||||
};
|
||||
|
||||
export const DEMO_POSTER_DOCUMENT_ZH: PosterDocument = {
|
||||
schemaVersion: 1,
|
||||
width: 1080,
|
||||
height: 1440,
|
||||
background: "#f4efe4",
|
||||
templateId: "editorial",
|
||||
copy: {
|
||||
badge: "EDGEEVER · 知识艺术",
|
||||
title: "极客的下一代\n数字第二大脑",
|
||||
subtitle: "重拾经典三栏工作流,融入 AI 智能体与无损开放数据资产。",
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "accent-bar",
|
||||
name: "accent-bar",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 110,
|
||||
width: 120,
|
||||
height: 12,
|
||||
fill: "#16a06e",
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "badge-text",
|
||||
name: "badge-text",
|
||||
type: "text",
|
||||
role: "badge",
|
||||
text: "EDGEEVER · 知识艺术",
|
||||
x: 96,
|
||||
y: 150,
|
||||
width: 888,
|
||||
height: 40,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#0f766e",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "heading",
|
||||
name: "heading",
|
||||
type: "text",
|
||||
role: "title",
|
||||
text: "极客的下一代\n数字第二大脑",
|
||||
x: 96,
|
||||
y: 220,
|
||||
width: 888,
|
||||
height: 220,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 84,
|
||||
fontFamily: "serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
{
|
||||
id: "subtitle",
|
||||
name: "subtitle",
|
||||
type: "text",
|
||||
role: "subtitle",
|
||||
text: "重拾经典三栏工作流,融入 AI 智能体与无损开放数据资产。",
|
||||
x: 96,
|
||||
y: 470,
|
||||
width: 888,
|
||||
height: 80,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#3f3f46",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-1-bg",
|
||||
name: "card-1-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 590,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-1-title",
|
||||
name: "card-1-title",
|
||||
type: "text",
|
||||
text: "01 · 原生 MCP / ACP 双协议支持",
|
||||
x: 136,
|
||||
y: 630,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-1-body",
|
||||
name: "card-1-body",
|
||||
type: "text",
|
||||
text: "原生对接 Claude、Cursor、Codex 等外部 Agent,桌面端无缝协同本地智能体。",
|
||||
x: 136,
|
||||
y: 695,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
{
|
||||
id: "card-2-bg",
|
||||
name: "card-2-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 840,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-2-title",
|
||||
name: "card-2-title",
|
||||
type: "text",
|
||||
text: "02 · 零成本 Serverless 与自由部署",
|
||||
x: 136,
|
||||
y: 880,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-2-body",
|
||||
name: "card-2-body",
|
||||
type: "text",
|
||||
text: "跑在 Cloudflare 免费额度内或一键 Docker 自建,标准 SQLite 存储,导出无阻碍。",
|
||||
x: 136,
|
||||
y: 945,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
{
|
||||
id: "card-3-bg",
|
||||
name: "card-3-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 1090,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-3-title",
|
||||
name: "card-3-title",
|
||||
type: "text",
|
||||
text: "03 · 全平台客户端与独立知识库",
|
||||
x: 136,
|
||||
y: 1130,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-3-body",
|
||||
name: "card-3-body",
|
||||
type: "text",
|
||||
text: "macOS、Windows、Linux、iOS 与 Android 覆盖,支持一键将笔记本发布为知识库网站。",
|
||||
x: 136,
|
||||
y: 1195,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const DEMO_POSTER_DOCUMENT_EN: PosterDocument = {
|
||||
schemaVersion: 1,
|
||||
width: 1080,
|
||||
height: 1440,
|
||||
background: "#f4efe4",
|
||||
templateId: "editorial",
|
||||
copy: {
|
||||
badge: "EDGEEVER · KNOWLEDGE ART",
|
||||
title: "Next-Gen Second Brain\nfor Geeks & Creators",
|
||||
subtitle: "Revive the beloved three-pane workflow with native AI agents and total data ownership.",
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "accent-bar",
|
||||
name: "accent-bar",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 110,
|
||||
width: 120,
|
||||
height: 12,
|
||||
fill: "#16a06e",
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "badge-text",
|
||||
name: "badge-text",
|
||||
type: "text",
|
||||
role: "badge",
|
||||
text: "EDGEEVER · KNOWLEDGE ART",
|
||||
x: 96,
|
||||
y: 150,
|
||||
width: 888,
|
||||
height: 40,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#0f766e",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "heading",
|
||||
name: "heading",
|
||||
type: "text",
|
||||
role: "title",
|
||||
text: "Next-Gen Second Brain\nfor Geeks & Creators",
|
||||
x: 96,
|
||||
y: 220,
|
||||
width: 888,
|
||||
height: 220,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 80,
|
||||
fontFamily: "serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
{
|
||||
id: "subtitle",
|
||||
name: "subtitle",
|
||||
type: "text",
|
||||
role: "subtitle",
|
||||
text: "Revive the beloved three-pane workflow with native AI agents and total data ownership.",
|
||||
x: 96,
|
||||
y: 470,
|
||||
width: 888,
|
||||
height: 80,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#3f3f46",
|
||||
fontSize: 30,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-1-bg",
|
||||
name: "card-1-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 590,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-1-title",
|
||||
name: "card-1-title",
|
||||
type: "text",
|
||||
text: "01 · Native MCP & ACP Dual Protocols",
|
||||
x: 136,
|
||||
y: 630,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-1-body",
|
||||
name: "card-1-body",
|
||||
type: "text",
|
||||
text: "Connect Claude, Cursor, and Codex via MCP, and drive local agents seamlessly via ACP on desktop.",
|
||||
x: 136,
|
||||
y: 695,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
{
|
||||
id: "card-2-bg",
|
||||
name: "card-2-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 840,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-2-title",
|
||||
name: "card-2-title",
|
||||
type: "text",
|
||||
text: "02 · 100% Free Serverless & Self-Hosting",
|
||||
x: 136,
|
||||
y: 880,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-2-body",
|
||||
name: "card-2-body",
|
||||
type: "text",
|
||||
text: "Run within Cloudflare free quotas or one-command Docker on your VPS/NAS with standard SQLite storage.",
|
||||
x: 136,
|
||||
y: 945,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
{
|
||||
id: "card-3-bg",
|
||||
name: "card-3-bg",
|
||||
type: "rect",
|
||||
x: 96,
|
||||
y: 1090,
|
||||
width: 888,
|
||||
height: 220,
|
||||
fill: "#ffffff",
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: "card-3-title",
|
||||
name: "card-3-title",
|
||||
type: "text",
|
||||
text: "03 · Full-Platform Apps & Knowledge Sites",
|
||||
x: 136,
|
||||
y: 1130,
|
||||
width: 808,
|
||||
height: 48,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#07130b",
|
||||
fontSize: 32,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
},
|
||||
{
|
||||
id: "card-3-body",
|
||||
name: "card-3-body",
|
||||
type: "text",
|
||||
text: "Native coverage on macOS, Windows, Linux, iOS & Android; publish entire notebooks as standalone sites.",
|
||||
x: 136,
|
||||
y: 1195,
|
||||
width: 808,
|
||||
height: 70,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
fill: "#71717a",
|
||||
fontSize: 24,
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
schemaVersion: 1,
|
||||
fields: [
|
||||
@@ -284,6 +764,50 @@ export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
{ id: "fld_docs", name: "规格文档", type: "attachment" },
|
||||
],
|
||||
records: [
|
||||
{
|
||||
id: "rec_poster",
|
||||
cells: {
|
||||
fld_feature: "原生海报笔记与画布排版",
|
||||
fld_category: "图表与可视化",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-05",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_acp",
|
||||
cells: {
|
||||
fld_feature: "桌面端 ACP 本地智能体协同",
|
||||
fld_category: "AI 智能体",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-08",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_notebook_site",
|
||||
cells: {
|
||||
fld_feature: "笔记本公开发布独立知识库",
|
||||
fld_category: "数据与架构",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-10",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_platforms",
|
||||
cells: {
|
||||
fld_feature: "全平台客户端 (macOS / Win / Linux / iOS / Android)",
|
||||
fld_category: "多端客户端",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-09-28",
|
||||
fld_docs: [{ resourceId: "res_demo_attachment_bundle_zip", filename: "edgeever-attachment-demo.zip", mimeType: "application/zip", byteSize: 153 }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_infographic",
|
||||
cells: {
|
||||
@@ -298,7 +822,7 @@ export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
{
|
||||
id: "rec_table",
|
||||
cells: {
|
||||
fld_feature: "多维结构化表格与表单",
|
||||
fld_feature: "多维结构化表格与公开表单",
|
||||
fld_category: "图表与可视化",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
@@ -309,7 +833,7 @@ export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
{
|
||||
id: "rec_mcp",
|
||||
cells: {
|
||||
fld_feature: "原生 MCP 端点 (/mcp)",
|
||||
fld_feature: "原生 MCP 服务端点 (/mcp)",
|
||||
fld_category: "AI 智能体",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
@@ -320,7 +844,7 @@ export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
{
|
||||
id: "rec_clipper",
|
||||
cells: {
|
||||
fld_feature: "Web Clipper 网页与推文剪藏",
|
||||
fld_feature: "Web Clipper 剪藏与微信聊天归档",
|
||||
fld_category: "多端客户端",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
@@ -328,17 +852,6 @@ export const DEMO_TABLE_DOCUMENT_ZH: TableDocument = {
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_windows",
|
||||
cells: {
|
||||
fld_feature: "Windows x64 桌面端 Preview",
|
||||
fld_category: "多端客户端",
|
||||
fld_status: "已正式发布",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-09-26",
|
||||
fld_docs: [{ resourceId: "res_demo_attachment_bundle_zip", filename: "edgeever-attachment-demo.zip", mimeType: "application/zip", byteSize: 153 }],
|
||||
},
|
||||
},
|
||||
],
|
||||
view: {
|
||||
filters: [],
|
||||
@@ -357,6 +870,50 @@ export const DEMO_TABLE_DOCUMENT_EN: TableDocument = {
|
||||
{ id: "fld_docs", name: "Documents", type: "attachment" },
|
||||
],
|
||||
records: [
|
||||
{
|
||||
id: "rec_poster",
|
||||
cells: {
|
||||
fld_feature: "Native Poster Notes & Canvas Layout",
|
||||
fld_category: "Visuals & Data",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-05",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_acp",
|
||||
cells: {
|
||||
fld_feature: "Desktop ACP Local Agent Integration",
|
||||
fld_category: "AI Agents",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-08",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_notebook_site",
|
||||
cells: {
|
||||
fld_feature: "Notebook Knowledge Base Web Publishing",
|
||||
fld_category: "Data & Architecture",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-10-10",
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_platforms",
|
||||
cells: {
|
||||
fld_feature: "Full-Platform Apps (macOS / Win / Linux / iOS / Android)",
|
||||
fld_category: "Cross-Platform",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-09-28",
|
||||
fld_docs: [{ resourceId: "res_demo_attachment_bundle_zip", filename: "edgeever-attachment-demo.zip", mimeType: "application/zip", byteSize: 153 }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_infographic",
|
||||
cells: {
|
||||
@@ -371,7 +928,7 @@ export const DEMO_TABLE_DOCUMENT_EN: TableDocument = {
|
||||
{
|
||||
id: "rec_table",
|
||||
cells: {
|
||||
fld_feature: "Structured Table & Forms",
|
||||
fld_feature: "Structured Tables & Public Forms",
|
||||
fld_category: "Visuals & Data",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
@@ -393,7 +950,7 @@ export const DEMO_TABLE_DOCUMENT_EN: TableDocument = {
|
||||
{
|
||||
id: "rec_clipper",
|
||||
cells: {
|
||||
fld_feature: "Web Clipper Page & Post Clips",
|
||||
fld_feature: "Web Clipper & WeChat Chat Archiving",
|
||||
fld_category: "Cross-Platform",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
@@ -401,17 +958,6 @@ export const DEMO_TABLE_DOCUMENT_EN: TableDocument = {
|
||||
fld_docs: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rec_windows",
|
||||
cells: {
|
||||
fld_feature: "Windows x64 Desktop Preview",
|
||||
fld_category: "Cross-Platform",
|
||||
fld_status: "Released",
|
||||
fld_verified: true,
|
||||
fld_date: "2026-09-26",
|
||||
fld_docs: [{ resourceId: "res_demo_attachment_bundle_zip", filename: "edgeever-attachment-demo.zip", mimeType: "application/zip", byteSize: 153 }],
|
||||
},
|
||||
},
|
||||
],
|
||||
view: {
|
||||
filters: [],
|
||||
@@ -440,11 +986,11 @@ export const DEMO_SEED_MEMOS_ZH = [
|
||||
| **源代码** | 闭源 | 核心闭源,文件开放 | **整栈开源 (AGPL-3.0)** |
|
||||
| **云端托管成本** | 商业订阅高昂($10+/月) | 官方云同步收费($5+/月) | **100% 永久免费 (Cloudflare 免费额度 / Docker 自建)** |
|
||||
| **数据资产所有权** | 专有格式封闭,导出困难 | 本地 Markdown,移动端同步繁琐 | **完全自主掌控 (D1 SQLite 数据库 / R2 / 无损 ZIP 导出)** |
|
||||
| **写作与表达矩阵** | 仅富文本,排版易错乱 | 纯 Markdown,缺乏沉浸所见即所得 | **双视图自由切换 (所见即所得 ⇄ 源码) + 原生架构图/流程图/思维导图/AI信息图/多维结构化表格** |
|
||||
| **写作与表达矩阵** | 仅富文本,排版易错乱 | 纯 Markdown,缺乏沉浸所见即所得 | **双视图自由切换 (所见即所得 ⇄ 源码) + 原生架构图/流程图/思维导图/AI信息图/多维结构化表格/原生海报笔记** |
|
||||
| **自媒体排版分发** | 无样式优化,格式丢失 | 需借助外部排版扩展或工具 | **一键复制到微信公众号 / Substack / Medium / WordPress** |
|
||||
| **社交卡片分享** | 截图粗糙或无排版设计 | 需第三方插件实现 | **内置 8 款精美海报主题,支持自定义字体与 PNG/JPEG 导出** |
|
||||
| **AI 原生集成生态** | 封闭收费,仅限特定功能 | 需配置复杂的第三方插件 | **原生内置 MCP 服务端点 (\`/mcp\`) 与行内 AI 智能体协同** |
|
||||
| **全平台多端覆盖** | 限制免费设备登录数量 | 多端同步与移动端体验门槛高 | **Web / PWA / Android / iOS / macOS / Windows Preview** |
|
||||
| **AI 原生集成生态** | 封闭收费,仅限特定功能 | 需配置复杂的第三方插件 | **原生内置 MCP 服务端点 (\`/mcp\`)、桌面端 ACP 本地智能体协同与行内 AI 助手** |
|
||||
| **全平台多端覆盖** | 限制免费设备登录数量 | 多端同步与移动端体验门槛高 | **Web / PWA / Android / iOS / macOS / Windows / Linux AppImage** |
|
||||
|
||||
---
|
||||
|
||||
@@ -457,7 +1003,7 @@ flowchart TD
|
||||
subgraph MultiClients["全端客户端矩阵"]
|
||||
C1["Web / PWA (离线草稿 & 增量同步)"]
|
||||
C2["macOS 桌面端 (Electron + Rust Sidecar)"]
|
||||
C3["Windows 桌面端 (Preview + 上下文集成)"]
|
||||
C3["Windows / Linux 桌面端 (ACP 智能体集成)"]
|
||||
C4["Android 原生 App (Expo + SQLite)"]
|
||||
C5["iOS 原生 App (SwiftUI + GRDB)"]
|
||||
C6["浏览器剪藏插件 (Chrome / Edge / Firefox)"]
|
||||
@@ -642,14 +1188,23 @@ sequenceDiagram
|
||||
|
||||
> 💡 **快速体验小贴士**:
|
||||
> 1. 点击右上角 **\`</>\`** 体验丝滑的双视图切换;
|
||||
> 2. 浏览侧边栏中的 **AI 信息图** 与 **多维结构化表格** 示例,感受下一代知识库形态;
|
||||
> 2. 浏览侧边栏中的 **AI 信息图**、**多维结构化表格** 与 **视觉排版海报** 示例,感受下一代知识库形态;
|
||||
> 3. 按下 **\`Cmd/Ctrl + E\`** 试一试阅读保护模式,锁定只读避免误改内容;
|
||||
> 4. 按下 **\`Cmd/Ctrl + O\`** 呼出快速切换器 (Quick Switcher);
|
||||
> 5. 点击顶部 **微信图标**,体验一键带格式排版复制;
|
||||
> 6. 点击 **“分享为卡片”**,挑选一款你心仪的社交海报风格并导出;
|
||||
> 7. 随时在侧边栏或设置中点击 **“恢复 Demo 数据”**,一键重置演示工作区。
|
||||
> 5. 体验知识库发布功能:右键笔记本可一键公开发布为带目录树的独立静态知识库网站;
|
||||
> 6. 点击顶部 **微信图标**,体验一键带格式排版复制;
|
||||
> 7. 点击 **“分享为卡片”**,挑选一款你心仪的社交海报风格并导出;
|
||||
> 8. 随时在侧边栏或设置中点击 **“恢复 Demo 数据”**,一键重置演示工作区。
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: "memo_demo_poster",
|
||||
notebookId: "nb_demo_features",
|
||||
title: "视觉排版:EdgeEver 极客第二大脑设计海报",
|
||||
tags: ["poster", "design", "visual-layout", "branding"],
|
||||
isPinned: false,
|
||||
markdown: serializePosterDocument(DEMO_POSTER_DOCUMENT_ZH),
|
||||
},
|
||||
{
|
||||
id: "memo_demo_infographic",
|
||||
notebookId: "nb_demo_features",
|
||||
@@ -709,6 +1264,20 @@ export const DEMO_SEED_REVISIONS = [
|
||||
markdown:
|
||||
"## Welcome to EdgeEver (Initial Draft)\n\n- Classic Evernote 3-pane layout & Serverless self-hosted\n- Visual table editing & Markdown source toggle\n- Native MCP & AI agent synergy",
|
||||
},
|
||||
{
|
||||
id: "rev_demo_poster_1",
|
||||
memoId: "memo_demo_poster",
|
||||
revision: 1,
|
||||
title: "视觉排版:EdgeEver 极客第二大脑设计海报",
|
||||
markdown: serializePosterDocument(DEMO_POSTER_DOCUMENT_ZH),
|
||||
},
|
||||
{
|
||||
id: "rev_demo_poster_1_en",
|
||||
memoId: "memo_demo_poster_en",
|
||||
revision: 1,
|
||||
title: "Visual Design: EdgeEver Geek Second Brain Poster Note",
|
||||
markdown: serializePosterDocument(DEMO_POSTER_DOCUMENT_EN),
|
||||
},
|
||||
{
|
||||
id: "rev_demo_infographic_1",
|
||||
memoId: "memo_demo_infographic",
|
||||
@@ -770,11 +1339,11 @@ export const DEMO_MEMO_ENGLISH = {
|
||||
| **Source Code** | Closed source | Closed-source core, open files | **Full-stack open source (AGPL-3.0)** |
|
||||
| **Hosting Cost** | Costly commercial subscriptions ($10+/mo) | Official cloud sync subscription ($5+/mo) | **100% Free Forever (Cloudflare Free Tier / Docker Self-Hosted)** |
|
||||
| **Data Ownership** | Proprietary lock-in, hard to export | Local Markdown, cumbersome mobile sync | **Full Ownership (D1 SQLite, R2 Storage, Lossless ZIP Archives)** |
|
||||
| **Writing & Expressive Matrix** | Rich text only, fragile formatting | Plain Markdown, lacks WYSIWYG flow | **Seamless Dual-View (WYSIWYG ⇄ Source) + Native Architecture/Flowchart/Mind-Map/AI Infographic/Structured Table** |
|
||||
| **Writing & Expressive Matrix** | Rich text only, fragile formatting | Plain Markdown, lacks WYSIWYG flow | **Seamless Dual-View (WYSIWYG ⇄ Source) + Native Architecture/Flowchart/Mind-Map/AI Infographic/Structured Table/Poster Notes** |
|
||||
| **Publishing Ready** | No typography styling, loses format | Requires 3rd-party plugins or scripts | **One-Click Rich Copy for WeChat, Substack, Medium & WordPress** |
|
||||
| **Social Poster Cards** | Basic screenshots without styling | Requires external tools or plugins | **8 Built-in Exquisite Poster Themes with Typography Options & PNG/JPEG Export** |
|
||||
| **Native AI Ecosystem** | Paywalled, limited features | Heavy plugin setup required | **Native MCP Server (\`/mcp\`) & Inline AI Agent Synergy** |
|
||||
| **Cross-Platform Matrix** | Restricts active free devices | Complex setup across platforms | **Web / PWA / Android / iOS / macOS / Windows Preview** |
|
||||
| **Native AI Ecosystem** | Paywalled, limited features | Heavy plugin setup required | **Native MCP Server (\`/mcp\`), Desktop ACP Local Agent Integration & Inline AI** |
|
||||
| **Cross-Platform Matrix** | Restricts active free devices | Complex setup across platforms | **Web / PWA / Android / iOS / macOS / Windows / Linux AppImage** |
|
||||
|
||||
---
|
||||
|
||||
@@ -787,7 +1356,7 @@ flowchart TD
|
||||
subgraph MultiClients["Client Matrix"]
|
||||
C1["Web / PWA (Offline Drafts & Sync Queue)"]
|
||||
C2["macOS Desktop (Electron + Rust Sidecar)"]
|
||||
C3["Windows Desktop (Preview + Context Menu)"]
|
||||
C3["Windows / Linux Desktop (ACP Agent Integration)"]
|
||||
C4["Android Native App (Expo + SQLite)"]
|
||||
C5["iOS Native App (SwiftUI + GRDB)"]
|
||||
C6["Web Clipper (Chrome / Edge / Firefox)"]
|
||||
@@ -972,14 +1541,19 @@ Export your entire library at any time from **Profile → Import and export**. T
|
||||
|
||||
> 💡 **Quick Exploration Tips**:
|
||||
> 1. Click **\`</>\`** in the top right to try seamless dual-view toggling;
|
||||
> 2. Explore the new **AI Infographic** and **Structured Table** examples in the sidebar;
|
||||
> 2. Explore the new **AI Infographic**, **Structured Table**, and **Visual Poster** examples in the sidebar;
|
||||
> 3. Press **\`Cmd/Ctrl + E\`** to test reading protection mode against accidental edits;
|
||||
> 4. Press **\`Cmd/Ctrl + O\`** to summon the Quick Switcher;
|
||||
> 5. Click the **WeChat Icon** to copy formatted rich text ready for publishing;
|
||||
> 6. Click **"Share as Card"** to export a stunning social poster card;
|
||||
> 7. Click **"Reset Demo Data"** in settings or the sidebar anytime to restore the demo workspace.
|
||||
> 5. Try Notebook Web Publishing: right-click any notebook to publish it as a standalone knowledge base site;
|
||||
> 6. Click the **WeChat Icon** to copy formatted rich text ready for publishing;
|
||||
> 7. Click **"Share as Card"** to export a stunning social poster card;
|
||||
> 8. Click **"Reset Demo Data"** in settings or the sidebar anytime to restore the demo workspace.
|
||||
`,
|
||||
},
|
||||
memo_demo_poster: {
|
||||
title: "Visual Design: EdgeEver Geek Second Brain Poster Note",
|
||||
markdown: serializePosterDocument(DEMO_POSTER_DOCUMENT_EN),
|
||||
},
|
||||
memo_demo_infographic: {
|
||||
title: "Evolution Milestones: EdgeEver Architecture & Release History",
|
||||
markdown: serializeInfographicDocument(DEMO_INFOGRAPHIC_DOCUMENT_EN),
|
||||
|
||||
@@ -219,17 +219,19 @@ const MOBILE_THEME_OPTIONS: Array<{
|
||||
labelEn: string;
|
||||
previewBg: string;
|
||||
}> = [
|
||||
{ id: "paper", labelZh: "纸间便签", labelEn: "Paper Memo", previewBg: "#fbf9f4" },
|
||||
{ id: "press", labelZh: "人文报刊", labelEn: "Press Journal", previewBg: "#f7f7f5" },
|
||||
{ id: "highlight", labelZh: "社交高光", labelEn: "Highlight", previewBg: "#fefce8" },
|
||||
{ id: "slate", labelZh: "纯白", labelEn: "Pure White", previewBg: "#ffffff" },
|
||||
{ id: "mint", labelZh: "薄荷", labelEn: "Mint", previewBg: "#ecfdf5" },
|
||||
{ id: "aurora", labelZh: "极光渐变", labelEn: "Aurora", previewBg: "#a7f3d0" },
|
||||
{ id: "sunset", labelZh: "暮色晚霞", labelEn: "Sunset", previewBg: "#fde68a" },
|
||||
{ id: "editorial", labelZh: "黑白编辑", labelEn: "Black & White", previewBg: "#f1f3f5" },
|
||||
{ id: "terminal", labelZh: "极客终端", labelEn: "Terminal", previewBg: "#0d1117" },
|
||||
{ id: "film", labelZh: "书页", labelEn: "Book Page", previewBg: "#f4ede4" },
|
||||
];
|
||||
|
||||
const MOBILE_FONT_OPTIONS: Array<{ id: NoteImageFontStyle; labelZh: string; labelEn: string }> = [
|
||||
{ id: "auto", labelZh: "跟随主题", labelEn: "Follow Theme" },
|
||||
{ id: "kai", labelZh: "文楷手书", labelEn: "Handwritten Kai" },
|
||||
{ id: "serif", labelZh: "文艺衬线", labelEn: "Serif" },
|
||||
{ id: "sans", labelZh: "现代无衬线", labelEn: "Sans" },
|
||||
{ id: "mono", labelZh: "极客等宽", labelEn: "Mono" },
|
||||
@@ -519,11 +521,11 @@ export const MemoDetailModal = ({
|
||||
const [imageExportStage, setImageExportStage] = useState<"prepare" | "render" | "transfer">("prepare");
|
||||
const [imageShareOptionsOpen, setImageShareOptionsOpen] = useState(false);
|
||||
const [imageShareFormat, setImageShareFormat] = useState<"jpeg" | "png">("png");
|
||||
const [imageShareTheme, setImageShareTheme] = useState<NoteImageTheme>("mint");
|
||||
const [imageShareTheme, setImageShareTheme] = useState<NoteImageTheme>("paper");
|
||||
const [imageShareFontStyle, setImageShareFontStyle] = useState<NoteImageFontStyle>("auto");
|
||||
const [imageShareFontSize, setImageShareFontSize] = useState<NoteImageFontSize>("lg");
|
||||
const [imageShareCardWidth, setImageShareCardWidth] = useState<NoteImageCardWidth>("wide");
|
||||
const [imageShareTitle, setImageShareTitle] = useState(true);
|
||||
const [imageShareTitle, setImageShareTitle] = useState(false);
|
||||
const [imageShareNotebook, setImageShareNotebook] = useState(false);
|
||||
const [imageShareTags, setImageShareTags] = useState(false);
|
||||
const [imageShareUpdatedAt, setImageShareUpdatedAt] = useState(true);
|
||||
|
||||
@@ -33,8 +33,9 @@ export type ShareNoteImageSource = Omit<DownloadNoteImageOptions, "background" |
|
||||
|
||||
const FONT_OPTIONS: Array<{ id: NoteImageFontStyle; labelKey: string }> = [
|
||||
{ id: "auto", labelKey: "editor.imageShare.fontStyles.auto" },
|
||||
{ id: "sans", labelKey: "editor.imageShare.fontStyles.sans" },
|
||||
{ id: "kai", labelKey: "editor.imageShare.fontStyles.kai" },
|
||||
{ id: "serif", labelKey: "editor.imageShare.fontStyles.serif" },
|
||||
{ id: "sans", labelKey: "editor.imageShare.fontStyles.sans" },
|
||||
{ id: "mono", labelKey: "editor.imageShare.fontStyles.mono" },
|
||||
];
|
||||
|
||||
@@ -61,11 +62,11 @@ export const ShareNoteImageDialog = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [format, setFormat] = useState<NoteImageFormat>("png");
|
||||
const [theme, setTheme] = useState<NoteImageTheme>("mint");
|
||||
const [theme, setTheme] = useState<NoteImageTheme>("paper");
|
||||
const [fontStyle, setFontStyle] = useState<NoteImageFontStyle>("auto");
|
||||
const [fontSize, setFontSize] = useState<NoteImageFontSize>("lg");
|
||||
const [cardWidth, setCardWidth] = useState<NoteImageCardWidth>("wide");
|
||||
const [showTitle, setShowTitle] = useState(true);
|
||||
const [showTitle, setShowTitle] = useState(false);
|
||||
const [showNotebook, setShowNotebook] = useState(false);
|
||||
const [showTags, setShowTags] = useState(false);
|
||||
const [showUpdatedAt, setShowUpdatedAt] = useState(true);
|
||||
@@ -89,11 +90,11 @@ export const ShareNoteImageDialog = ({
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setFormat("png");
|
||||
setTheme("mint");
|
||||
setTheme("paper");
|
||||
setFontStyle("auto");
|
||||
setFontSize("lg");
|
||||
setCardWidth("wide");
|
||||
setShowTitle(true);
|
||||
setShowTitle(false);
|
||||
setShowNotebook(false);
|
||||
setShowTags(false);
|
||||
setShowUpdatedAt(true);
|
||||
@@ -296,12 +297,13 @@ export const ShareNoteImageDialog = ({
|
||||
{t("editor.imageShare.fontStyle")}
|
||||
</legend>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{FONT_OPTIONS.map((item) => (
|
||||
{FONT_OPTIONS.map((item, idx) => (
|
||||
<button
|
||||
key={item.id}
|
||||
aria-pressed={fontStyle === item.id}
|
||||
className={cn(
|
||||
"h-8 rounded-md border text-xs font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-slate-900/20",
|
||||
idx === 0 && "col-span-2",
|
||||
fontStyle === item.id
|
||||
? "border-slate-900 bg-slate-100 text-slate-950 font-semibold"
|
||||
: "border-slate-200 bg-card text-slate-600 hover:bg-slate-50",
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("note image export helpers", () => {
|
||||
expect(midnightCss).toContain("JetBrains Mono");
|
||||
});
|
||||
|
||||
test("generates editorial theme with brand logo", () => {
|
||||
test("generates press theme when editorial is requested", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Editorial Note",
|
||||
notebook: "Work",
|
||||
@@ -82,7 +82,7 @@ describe("note image export helpers", () => {
|
||||
showBranding: true,
|
||||
});
|
||||
|
||||
expect(markup).toContain('data-theme="editorial"');
|
||||
expect(markup).toContain('data-theme="press"');
|
||||
expect(markup).toContain("edgeever-brand-logo");
|
||||
expect(markup).not.toContain("edgeever-meta-notebook");
|
||||
|
||||
@@ -92,11 +92,11 @@ describe("note image export helpers", () => {
|
||||
fontSize: "lg",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(editorialCss).toContain('data-theme="editorial"');
|
||||
expect(editorialCss).toContain('data-theme="press"');
|
||||
expect(editorialCss).toContain("edgeever-brand-logo");
|
||||
});
|
||||
|
||||
test("generates film theme with brand logo", () => {
|
||||
test("generates paper theme when film is requested", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Poem Note",
|
||||
bodyHtml: "<p>Kodak film aesthetic</p>",
|
||||
@@ -106,7 +106,7 @@ describe("note image export helpers", () => {
|
||||
showBranding: true,
|
||||
});
|
||||
|
||||
expect(markup).toContain('data-theme="film"');
|
||||
expect(markup).toContain('data-theme="paper"');
|
||||
expect(markup).toContain("edgeever-brand-logo");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ export const createNoteImage = async ({
|
||||
fontStyle = "auto",
|
||||
fontSize = "lg",
|
||||
cardWidth = "wide",
|
||||
showTitle = true,
|
||||
showTitle = false,
|
||||
showNotebook = false,
|
||||
showTags = false,
|
||||
showUpdatedAt = true,
|
||||
|
||||
@@ -2432,6 +2432,9 @@ export const enUS = {
|
||||
previewBody: "Ideas worth sharing",
|
||||
previewQuote: "Keep what matters.",
|
||||
themes: {
|
||||
paper: "Paper Memo",
|
||||
press: "Press Journal",
|
||||
highlight: "Social Highlight",
|
||||
slate: "Pure White",
|
||||
aurora: "Aurora",
|
||||
sunset: "Sunset",
|
||||
@@ -2450,8 +2453,9 @@ export const enUS = {
|
||||
fontStyle: "Typography",
|
||||
fontStyles: {
|
||||
auto: "Follow Theme",
|
||||
sans: "Modern Sans",
|
||||
kai: "Handwritten Kai",
|
||||
serif: "Editorial Serif",
|
||||
sans: "Modern Sans",
|
||||
mono: "Geek Mono",
|
||||
},
|
||||
fontSize: "Font Size",
|
||||
|
||||
@@ -2421,6 +2421,9 @@ export const ja = {
|
||||
previewBody: "共有したいアイデア",
|
||||
previewQuote: "大切なことを残す。",
|
||||
themes: {
|
||||
paper: "メモ用紙",
|
||||
press: "新聞・雑誌",
|
||||
highlight: "ハイライト",
|
||||
slate: "ピュアホワイト",
|
||||
aurora: "オーロラ",
|
||||
sunset: "夕焼け",
|
||||
@@ -2439,8 +2442,9 @@ export const ja = {
|
||||
fontStyle: "書体",
|
||||
fontStyles: {
|
||||
auto: "テーマに合わせる",
|
||||
sans: "モダンゴシック",
|
||||
kai: "手書き・楷書",
|
||||
serif: "本文明朝",
|
||||
sans: "モダンゴシック",
|
||||
mono: "ギーク等幅",
|
||||
},
|
||||
fontSize: "文字サイズ",
|
||||
|
||||
@@ -2421,6 +2421,9 @@ export const pl = {
|
||||
previewBody: "Pomysły warte udostępnienia",
|
||||
previewQuote: "Zachowaj to, co ważne.",
|
||||
themes: {
|
||||
paper: "Notatka papierowa",
|
||||
press: "Prasa i felieton",
|
||||
highlight: "Wyróżnienie",
|
||||
slate: "Czysta biel",
|
||||
aurora: "Zorza",
|
||||
sunset: "Zachód słońca",
|
||||
@@ -2439,8 +2442,9 @@ export const pl = {
|
||||
fontStyle: "Typografia",
|
||||
fontStyles: {
|
||||
auto: "Zgodnie z motywem",
|
||||
sans: "Nowoczesna bezszeryfowa",
|
||||
kai: "Pismo odręczne / Kaligrafia",
|
||||
serif: "Redakcyjna szeryfowa",
|
||||
sans: "Nowoczesna bezszeryfowa",
|
||||
mono: "Geekowska o stałej szerokości",
|
||||
},
|
||||
fontSize: "Rozmiar czcionki",
|
||||
|
||||
@@ -2430,6 +2430,9 @@ export const zhCN = {
|
||||
previewBody: "值得分享的想法",
|
||||
previewQuote: "留住重要的事。",
|
||||
themes: {
|
||||
paper: "纸间便签",
|
||||
press: "人文报刊",
|
||||
highlight: "社交高光",
|
||||
slate: "纯白",
|
||||
aurora: "极光渐变",
|
||||
sunset: "暮色晚霞",
|
||||
@@ -2448,8 +2451,9 @@ export const zhCN = {
|
||||
fontStyle: "字体风格",
|
||||
fontStyles: {
|
||||
auto: "跟随主题",
|
||||
sans: "现代无衬线",
|
||||
kai: "文楷手书",
|
||||
serif: "文艺衬线",
|
||||
sans: "现代无衬线",
|
||||
mono: "极客等宽",
|
||||
},
|
||||
fontSize: "字号大小",
|
||||
|
||||
@@ -14,19 +14,20 @@ import {
|
||||
|
||||
describe("note-image-card shared module", () => {
|
||||
test("resolves theme correctly with fallbacks", () => {
|
||||
expect(resolveTheme(undefined, "editorial")).toBe("editorial");
|
||||
expect(resolveTheme(undefined, "editorial")).toBe("press");
|
||||
expect(resolveTheme(undefined, "terminal")).toBe("terminal");
|
||||
expect(resolveTheme(undefined, "film")).toBe("film");
|
||||
expect(resolveTheme(undefined, "notepad")).toBe("editorial");
|
||||
expect(resolveTheme(undefined, "xuan")).toBe("editorial");
|
||||
expect(resolveTheme(undefined, "polaroid")).toBe("film");
|
||||
expect(resolveTheme(undefined, "film")).toBe("paper");
|
||||
expect(resolveTheme(undefined, "notepad")).toBe("press");
|
||||
expect(resolveTheme(undefined, "xuan")).toBe("press");
|
||||
expect(resolveTheme(undefined, "polaroid")).toBe("paper");
|
||||
expect(resolveTheme("warm", undefined)).toBe("sunset");
|
||||
expect(resolveTheme(undefined, undefined)).toBe("mint");
|
||||
expect(resolveTheme(undefined, undefined)).toBe("paper");
|
||||
});
|
||||
|
||||
test("retired themes resolve to active choices in both legacy inputs", () => {
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS).toHaveLength(7);
|
||||
for (const [legacy, active] of [["midnight", "terminal"], ["lavender", "mint"]]) {
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS).toHaveLength(8);
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS).toEqual(["paper", "press", "highlight", "slate", "mint", "aurora", "sunset", "terminal"]);
|
||||
for (const [legacy, active] of [["midnight", "terminal"], ["lavender", "mint"], ["editorial", "press"], ["film", "paper"]]) {
|
||||
expect(NOTE_IMAGE_THEME_OPTIONS).not.toContain(legacy);
|
||||
expect(resolveTheme(undefined, legacy)).toBe(active);
|
||||
expect(resolveTheme(legacy)).toBe(active);
|
||||
@@ -40,9 +41,25 @@ describe("note-image-card shared module", () => {
|
||||
test("theme typography pairs headings and body while explicit fonts override both", () => {
|
||||
for (const theme of NOTE_IMAGE_THEME_OPTIONS) {
|
||||
const auto = resolveNoteImageTypography(theme);
|
||||
expect(auto.bodyFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[theme === "film" || theme === "sunset" ? "serif" : "sans"]);
|
||||
expect(auto.headingFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[theme === "editorial" ? "serif" : theme === "terminal" ? "mono" : theme === "film" || theme === "sunset" ? "serif" : "sans"]);
|
||||
for (const fontStyle of ["sans", "serif", "mono"]) {
|
||||
const expectedBody = theme === "paper"
|
||||
? "kai"
|
||||
: theme === "terminal"
|
||||
? "mono"
|
||||
: theme === "film" || theme === "sunset" || theme === "press"
|
||||
? "serif"
|
||||
: "sans";
|
||||
const expectedHeading = theme === "paper"
|
||||
? "kai"
|
||||
: theme === "terminal"
|
||||
? "mono"
|
||||
: theme === "editorial" || theme === "film" || theme === "sunset" || theme === "press"
|
||||
? "serif"
|
||||
: "sans";
|
||||
|
||||
expect(auto.bodyFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[expectedBody]);
|
||||
expect(auto.headingFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[expectedHeading]);
|
||||
|
||||
for (const fontStyle of ["sans", "serif", "mono", "kai"]) {
|
||||
const manual = resolveNoteImageTypography(theme, fontStyle);
|
||||
expect(manual.bodyFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[fontStyle]);
|
||||
expect(manual.headingFamily).toBe(NOTE_IMAGE_FONT_FAMILIES[fontStyle]);
|
||||
@@ -58,8 +75,18 @@ describe("note-image-card shared module", () => {
|
||||
expect(markup).not.toContain("edgeever-terminal-header");
|
||||
});
|
||||
|
||||
test("defaults to hiding note title unless explicitly enabled", () => {
|
||||
const defaultMarkup = buildNoteImageCardMarkup({ title: "Hidden Title", bodyHtml: "<p>Content</p>" });
|
||||
expect(defaultMarkup).not.toContain("edgeever-card-title");
|
||||
expect(defaultMarkup).not.toContain("Hidden Title");
|
||||
|
||||
const visibleMarkup = buildNoteImageCardMarkup({ title: "Visible Title", bodyHtml: "<p>Content</p>", showTitle: true });
|
||||
expect(visibleMarkup).toContain("edgeever-card-title");
|
||||
expect(visibleMarkup).toContain("Visible Title");
|
||||
});
|
||||
|
||||
test("retains valid colors for current themes and legacy slate exports", () => {
|
||||
const expectedThemes = ["slate", "aurora", "sunset", "midnight", "editorial", "terminal", "film", "mint", "lavender"];
|
||||
const expectedThemes = ["paper", "press", "highlight", "slate", "aurora", "sunset", "midnight", "editorial", "terminal", "film", "mint", "lavender"];
|
||||
for (const theme of expectedThemes) {
|
||||
expect(NOTE_IMAGE_THEMES[theme]).toBeDefined();
|
||||
expect(NOTE_IMAGE_BACKGROUND_COLORS[theme]).toBeDefined();
|
||||
@@ -92,7 +119,7 @@ describe("note-image-card shared module", () => {
|
||||
expect(markup).not.toContain("edgeever-meta-tag");
|
||||
});
|
||||
|
||||
test("generates editorial theme card with distinct borders and styling", () => {
|
||||
test("generates press styling when legacy editorial theme is requested", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Editorial Dispatch",
|
||||
bodyHtml: "<p>Deep analytical reporting on modern engineering.</p>",
|
||||
@@ -102,7 +129,7 @@ describe("note-image-card shared module", () => {
|
||||
author: "@tianma",
|
||||
showAuthor: true,
|
||||
});
|
||||
expect(markup).toContain('data-theme="editorial"');
|
||||
expect(markup).toContain('data-theme="press"');
|
||||
expect(markup).toContain("@tianma");
|
||||
|
||||
const css = generateCardCss({
|
||||
@@ -111,8 +138,8 @@ describe("note-image-card shared module", () => {
|
||||
fontSize: "md",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(css).toContain('data-theme="editorial"');
|
||||
expect(css).toContain("box-shadow: 6px 6px 0px #111827");
|
||||
expect(css).toContain('data-theme="press"');
|
||||
expect(css).toContain("border-top: 3.5px solid #111412");
|
||||
});
|
||||
|
||||
test("generates terminal header for the terminal theme", () => {
|
||||
@@ -135,7 +162,7 @@ describe("note-image-card shared module", () => {
|
||||
expect(css).toContain('data-theme="terminal"');
|
||||
});
|
||||
|
||||
test("generates film theme card with custom author and reading time", () => {
|
||||
test("generates paper styling when legacy film theme is requested", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Memories in Summer",
|
||||
notebook: "Journal",
|
||||
@@ -150,7 +177,7 @@ describe("note-image-card shared module", () => {
|
||||
});
|
||||
|
||||
expect(markup).toContain("edgeever-grain-overlay");
|
||||
expect(markup).toContain('data-theme="film"');
|
||||
expect(markup).toContain('data-theme="paper"');
|
||||
expect(markup).toContain("@tianma");
|
||||
expect(markup).toContain("edgeever-footer-author");
|
||||
expect(markup).toContain("edgeever-meta-reading");
|
||||
@@ -161,8 +188,8 @@ describe("note-image-card shared module", () => {
|
||||
fontSize: "md",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(css).toContain('data-theme="film"');
|
||||
expect(css).toContain("edgeever-grain-overlay");
|
||||
expect(css).toContain('data-theme="paper"');
|
||||
expect(css).toContain("background-color: #fbf9f4");
|
||||
});
|
||||
|
||||
test("calculates reading stats for mixed Chinese and English text", () => {
|
||||
@@ -171,6 +198,59 @@ describe("note-image-card shared module", () => {
|
||||
expect(stats.minutes).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("generates paper theme with warm background and borderless structure", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Daily Reflections",
|
||||
bodyHtml: "<p>Thoughts on a quiet morning.</p>",
|
||||
theme: "paper",
|
||||
});
|
||||
expect(markup).toContain('data-theme="paper"');
|
||||
const css = generateCardCss({
|
||||
theme: "paper",
|
||||
fontStyle: "sans",
|
||||
fontSize: "md",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(css).toContain('.edgeever-image-wrapper[data-theme="paper"]');
|
||||
expect(css).toContain("background-color: #fbf9f4");
|
||||
expect(css).toContain("radial-gradient");
|
||||
});
|
||||
|
||||
test("generates press theme with classic double rule and editorial styling", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "The Architecture of Thought",
|
||||
bodyHtml: "<blockquote>A good system makes right paths easy.</blockquote>",
|
||||
theme: "press",
|
||||
fontStyle: "serif",
|
||||
});
|
||||
expect(markup).toContain('data-theme="press"');
|
||||
const css = generateCardCss({
|
||||
theme: "press",
|
||||
fontStyle: "serif",
|
||||
fontSize: "md",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(css).toContain('.edgeever-image-card[data-theme="press"]');
|
||||
expect(css).toContain("border-top: 3.5px solid #111412");
|
||||
});
|
||||
|
||||
test("generates highlight theme with vivid marker styles on strong text", () => {
|
||||
const markup = buildNoteImageCardMarkup({
|
||||
title: "Key Takeaways",
|
||||
bodyHtml: "<p>Remember to <strong>focus on what matters</strong>.</p>",
|
||||
theme: "highlight",
|
||||
});
|
||||
expect(markup).toContain('data-theme="highlight"');
|
||||
const css = generateCardCss({
|
||||
theme: "highlight",
|
||||
fontStyle: "sans",
|
||||
fontSize: "md",
|
||||
cardWidth: "standard",
|
||||
});
|
||||
expect(css).toContain('.edgeever-image-card[data-theme="highlight"]');
|
||||
expect(css).toContain("background: linear-gradient(104deg, rgba(254, 240, 138");
|
||||
});
|
||||
|
||||
test("sanitizes filenames and guards against reserved Windows names", () => {
|
||||
expect(buildImageExportBasename("CON", "fallback")).toBe("_CON");
|
||||
expect(buildImageExportBasename("My Note/Title?*", "fallback")).toBe("My Note-Title--");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export type NoteImageFormat = "jpeg" | "png";
|
||||
export type NoteImageTheme = "slate" | "aurora" | "sunset" | "midnight" | "editorial" | "terminal" | "film" | "mint" | "lavender";
|
||||
export type NoteImageTheme = "paper" | "press" | "highlight" | "slate" | "aurora" | "sunset" | "midnight" | "editorial" | "terminal" | "film" | "mint" | "lavender";
|
||||
export type NoteImageBackground = NoteImageTheme | "warm" | "notepad" | "xuan" | "polaroid";
|
||||
export type NoteImageFontStyle = "auto" | "sans" | "serif" | "mono";
|
||||
export const NOTE_IMAGE_THEME_OPTIONS = ["slate", "mint", "aurora", "sunset", "editorial", "film", "terminal"] as const;
|
||||
export type NoteImageFontStyle = "auto" | "sans" | "serif" | "mono" | "kai";
|
||||
export const NOTE_IMAGE_THEME_OPTIONS = ["paper", "press", "highlight", "slate", "mint", "aurora", "sunset", "terminal"] as const;
|
||||
export type NoteImageFontSize = "sm" | "md" | "lg";
|
||||
export type NoteImageCardWidth = "compact" | "standard" | "wide";
|
||||
|
||||
@@ -38,6 +38,9 @@ export const planNativeNoteImageRender = (width: number, height: number) => {
|
||||
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
|
||||
|
||||
export const NOTE_IMAGE_BACKGROUND_COLORS: Record<NoteImageBackground, string> = {
|
||||
paper: "#fbf9f4",
|
||||
press: "#f7f7f5",
|
||||
highlight: "#fefce8",
|
||||
slate: "#ffffff",
|
||||
aurora: "#a7f3d0",
|
||||
sunset: "#fde68a",
|
||||
@@ -80,6 +83,78 @@ export type ThemeStyleConfig = {
|
||||
};
|
||||
|
||||
export const NOTE_IMAGE_THEMES: Record<NoteImageTheme, ThemeStyleConfig> = {
|
||||
paper: {
|
||||
canvasBg: "#fbf9f4",
|
||||
cardBg: "#fbf9f4",
|
||||
cardBorder: "none",
|
||||
cardShadow: "none",
|
||||
textColor: "#2c2925",
|
||||
headingColor: "#1a1816",
|
||||
metaColor: "#8c8275",
|
||||
metaBg: "rgba(140, 130, 117, 0.12)",
|
||||
metaBorder: "rgba(140, 130, 117, 0.22)",
|
||||
accentColor: "#b45309",
|
||||
accentSubtle: "rgba(180, 83, 9, 0.15)",
|
||||
linkBg: "rgba(180, 83, 9, 0.08)",
|
||||
dividerColor: "rgba(140, 130, 117, 0.3)",
|
||||
codeBg: "rgba(140, 130, 117, 0.12)",
|
||||
codeBorder: "rgba(140, 130, 117, 0.25)",
|
||||
codeColor: "#2c2925",
|
||||
quoteBg: "rgba(255, 255, 255, 0.65)",
|
||||
quoteBorder: "#a89f91",
|
||||
tableThBg: "rgba(140, 130, 117, 0.12)",
|
||||
tableBorder: "rgba(140, 130, 117, 0.25)",
|
||||
brandText: "#2c2925",
|
||||
brandMark: "#8c8275",
|
||||
},
|
||||
press: {
|
||||
canvasBg: "linear-gradient(180deg, #f7f7f5 0%, #ededeb 100%)",
|
||||
cardBg: "#ffffff",
|
||||
cardBorder: "1px solid #e2e2e0",
|
||||
cardShadow: "0 12px 32px -4px rgba(0, 0, 0, 0.05), 0 2px 8px rgba(0, 0, 0, 0.02)",
|
||||
textColor: "#1f2421",
|
||||
headingColor: "#111412",
|
||||
metaColor: "#64748b",
|
||||
metaBg: "#f8fafc",
|
||||
metaBorder: "#e2e8f0",
|
||||
accentColor: "#b91c1c",
|
||||
accentSubtle: "rgba(185, 28, 28, 0.12)",
|
||||
linkBg: "rgba(185, 28, 28, 0.06)",
|
||||
dividerColor: "#111412",
|
||||
codeBg: "#f8fafc",
|
||||
codeBorder: "#e2e8f0",
|
||||
codeColor: "#0f172a",
|
||||
quoteBg: "#fcfcfb",
|
||||
quoteBorder: "#111412",
|
||||
tableThBg: "#f1f5f9",
|
||||
tableBorder: "#e2e8f0",
|
||||
brandText: "#111412",
|
||||
brandMark: "#b91c1c",
|
||||
},
|
||||
highlight: {
|
||||
canvasBg: "linear-gradient(135deg, #fef9c3 0%, #fef08a 35%, #e0e7ff 100%)",
|
||||
cardBg: "#ffffff",
|
||||
cardBorder: "1px solid rgba(254, 240, 138, 0.6)",
|
||||
cardShadow: "0 20px 48px -12px rgba(234, 179, 8, 0.18), 0 4px 16px rgba(0, 0, 0, 0.04)",
|
||||
textColor: "#1e293b",
|
||||
headingColor: "#0f172a",
|
||||
metaColor: "#475569",
|
||||
metaBg: "rgba(254, 240, 138, 0.45)",
|
||||
metaBorder: "rgba(250, 204, 21, 0.5)",
|
||||
accentColor: "#ca8a04",
|
||||
accentSubtle: "rgba(202, 138, 4, 0.16)",
|
||||
linkBg: "rgba(202, 138, 4, 0.08)",
|
||||
dividerColor: "rgba(254, 215, 170, 0.7)",
|
||||
codeBg: "#fefce8",
|
||||
codeBorder: "#fef08a",
|
||||
codeColor: "#854d0e",
|
||||
quoteBg: "#fefce8",
|
||||
quoteBorder: "#eab308",
|
||||
tableThBg: "#fef9c3",
|
||||
tableBorder: "#fef08a",
|
||||
brandText: "#0f172a",
|
||||
brandMark: "#ca8a04",
|
||||
},
|
||||
slate: {
|
||||
canvasBg: "#ffffff",
|
||||
cardBg: "#ffffff",
|
||||
@@ -301,25 +376,61 @@ export const NOTE_IMAGE_THEMES: Record<NoteImageTheme, ThemeStyleConfig> = {
|
||||
};
|
||||
|
||||
export const NOTE_IMAGE_FONT_FAMILIES: Record<Exclude<NoteImageFontStyle, "auto">, string> = {
|
||||
sans: 'system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
|
||||
serif: '"Songti SC", "Noto Serif SC", "Source Han Serif SC", "STSong", "Hiragino Mincho ProN", Georgia, "Times New Roman", serif',
|
||||
mono: '"JetBrains Mono", "SF Mono", "Fira Code", Menlo, Monaco, Consolas, "Liberation Mono", monospace',
|
||||
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif',
|
||||
serif: 'Georgia, "Nimbus Roman No9 L", "Songti SC", "Noto Serif SC", "Source Han Serif SC", "Source Han Serif CN", "STSong", "AR PL New Sung", "SimSun", "Hiragino Mincho ProN", serif',
|
||||
mono: '"JetBrains Mono", "SF Mono", "Fira Code", "Cascadia Code", Menlo, Monaco, Consolas, "Liberation Mono", monospace',
|
||||
kai: '"LXGW WenKai", "霞鹜文楷", "Kaiti SC", "STKaiti", "KaiTi", "KaiTi_GB2312", "BiauKai", "楷体", "STFangsong", "FangSong", serif',
|
||||
};
|
||||
|
||||
/** Theme typography is shared by previews, Web exports and native WebViews. */
|
||||
export const resolveNoteImageTypography = (theme: NoteImageTheme, fontStyle: NoteImageFontStyle = "auto") => {
|
||||
const resolvedTheme = resolveTheme(undefined, theme);
|
||||
const bodyStyle = fontStyle === "auto"
|
||||
? (resolvedTheme === "film" || resolvedTheme === "sunset" ? "serif" : "sans")
|
||||
: fontStyle;
|
||||
const headingStyle = fontStyle === "auto"
|
||||
? (resolvedTheme === "editorial" ? "serif" : resolvedTheme === "terminal" ? "mono" : bodyStyle)
|
||||
: bodyStyle;
|
||||
|
||||
let bodyStyle: Exclude<NoteImageFontStyle, "auto">;
|
||||
let headingStyle: Exclude<NoteImageFontStyle, "auto">;
|
||||
|
||||
if (fontStyle !== "auto") {
|
||||
bodyStyle = fontStyle;
|
||||
headingStyle = fontStyle;
|
||||
} else {
|
||||
switch (resolvedTheme) {
|
||||
case "paper":
|
||||
bodyStyle = "kai";
|
||||
headingStyle = "kai";
|
||||
break;
|
||||
case "press":
|
||||
bodyStyle = "serif";
|
||||
headingStyle = "serif";
|
||||
break;
|
||||
case "terminal":
|
||||
bodyStyle = "mono";
|
||||
headingStyle = "mono";
|
||||
break;
|
||||
case "film":
|
||||
case "sunset":
|
||||
bodyStyle = "serif";
|
||||
headingStyle = "serif";
|
||||
break;
|
||||
case "editorial":
|
||||
bodyStyle = "sans";
|
||||
headingStyle = "serif";
|
||||
break;
|
||||
case "highlight":
|
||||
case "slate":
|
||||
case "mint":
|
||||
case "aurora":
|
||||
default:
|
||||
bodyStyle = "sans";
|
||||
headingStyle = "sans";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bodyFamily: NOTE_IMAGE_FONT_FAMILIES[bodyStyle],
|
||||
headingFamily: NOTE_IMAGE_FONT_FAMILIES[headingStyle],
|
||||
headingWeight: headingStyle === "serif" ? 700 : 720,
|
||||
lineHeightAdjustment: bodyStyle === "serif" ? 0.08 : bodyStyle === "mono" ? 0.02 : 0,
|
||||
headingWeight: headingStyle === "serif" ? 720 : headingStyle === "kai" ? 650 : 700,
|
||||
lineHeightAdjustment: bodyStyle === "serif" ? 0.08 : bodyStyle === "kai" ? 0.06 : bodyStyle === "mono" ? 0.02 : 0,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -383,14 +494,18 @@ export const NOTE_IMAGE_FONT_SIZES: Record<
|
||||
export const resolveTheme = (background?: NoteImageBackground, theme?: NoteImageTheme): NoteImageTheme => {
|
||||
if (theme === "midnight") return "terminal";
|
||||
if (theme === "lavender") return "mint";
|
||||
if (theme === "editorial") return "press";
|
||||
if (theme === "film") return "paper";
|
||||
if (theme === ("notepad" as unknown as NoteImageTheme) || theme === ("xuan" as unknown as NoteImageTheme)) return "press";
|
||||
if (theme === ("polaroid" as unknown as NoteImageTheme)) return "paper";
|
||||
if (theme && NOTE_IMAGE_THEMES[theme]) return theme;
|
||||
if (theme === ("notepad" as unknown as NoteImageTheme) || theme === ("xuan" as unknown as NoteImageTheme)) return "editorial";
|
||||
if (theme === ("polaroid" as unknown as NoteImageTheme)) return "film";
|
||||
if (background === "warm") return "sunset";
|
||||
if (background === "notepad" || background === "xuan") return "editorial";
|
||||
if (background === "polaroid") return "film";
|
||||
if (background === "notepad" || background === "xuan") return "press";
|
||||
if (background === "polaroid") return "paper";
|
||||
if (background === "editorial") return "press";
|
||||
if (background === "film") return "paper";
|
||||
if (background && NOTE_IMAGE_THEMES[background as NoteImageTheme]) return resolveTheme(undefined, background as NoteImageTheme);
|
||||
return "mint";
|
||||
return "paper";
|
||||
};
|
||||
|
||||
export const buildImageExportBasename = (title: string, fallback: string) => {
|
||||
@@ -430,7 +545,7 @@ export const buildNoteImageCardMarkup = ({
|
||||
bodyHtml,
|
||||
theme = "mint",
|
||||
fontStyle = "auto",
|
||||
showTitle = true,
|
||||
showTitle = false,
|
||||
showNotebook = false,
|
||||
showTags = false,
|
||||
showUpdatedAt = true,
|
||||
@@ -531,7 +646,7 @@ export const buildNoteImageCardMarkup = ({
|
||||
</footer>`
|
||||
: "";
|
||||
|
||||
return `<div class="edgeever-image-wrapper">
|
||||
return `<div class="edgeever-image-wrapper" data-theme="${theme}">
|
||||
<div class="edgeever-grain-overlay" aria-hidden="true"></div>
|
||||
<article class="edgeever-image-card" data-theme="${theme}">
|
||||
${tearStripHtml}
|
||||
@@ -1029,6 +1144,122 @@ export const generateCardCss = ({
|
||||
color: var(--ee-brand-text);
|
||||
}
|
||||
|
||||
${theme === "paper" ? `
|
||||
.edgeever-image-wrapper[data-theme="paper"] {
|
||||
background-color: #fbf9f4;
|
||||
background-image: radial-gradient(#d5cdc0 0.85px, transparent 0.85px);
|
||||
background-size: 20px 20px;
|
||||
padding: 32px 28px;
|
||||
}
|
||||
.edgeever-image-wrapper[data-theme="paper"] .edgeever-grain-overlay {
|
||||
opacity: 0.03;
|
||||
}
|
||||
.edgeever-image-card[data-theme="paper"] {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
padding: 16px 8px 12px;
|
||||
}
|
||||
.edgeever-image-card[data-theme="paper"] .edgeever-card-title {
|
||||
font-size: calc(var(--ee-title-size) * 0.94);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
.edgeever-image-card[data-theme="paper"] .edgeever-card-divider {
|
||||
height: 1px;
|
||||
background: none;
|
||||
border-top: 1px dashed rgba(140, 130, 117, 0.4);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
.edgeever-image-card[data-theme="paper"] blockquote {
|
||||
border-left: 3px solid #b4a796;
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.edgeever-image-card[data-theme="paper"] blockquote::before {
|
||||
display: none;
|
||||
}
|
||||
` : ""}
|
||||
|
||||
${theme === "press" ? `
|
||||
.edgeever-image-card[data-theme="press"] {
|
||||
border: 1px solid #d4d4d8;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 10px 30px -4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] .edgeever-card-header {
|
||||
border-top: 3.5px solid #111412;
|
||||
padding-top: 14px;
|
||||
position: relative;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] .edgeever-card-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 4.5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: #111412;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] .edgeever-card-title {
|
||||
font-family: var(--ee-heading-font-family);
|
||||
letter-spacing: -0.01em;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] .edgeever-card-divider {
|
||||
height: 1px;
|
||||
background: #111412;
|
||||
margin: 16px 0 24px;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] blockquote {
|
||||
border-left: 2px solid #111412;
|
||||
background: #fafaf9;
|
||||
border-radius: 0;
|
||||
padding: 16px 20px 16px 30px;
|
||||
font-style: normal;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] blockquote::before {
|
||||
content: "“";
|
||||
font-family: Georgia, "Songti SC", serif;
|
||||
font-size: 46px;
|
||||
top: -14px;
|
||||
left: 6px;
|
||||
color: #b91c1c;
|
||||
opacity: 0.45;
|
||||
}
|
||||
.edgeever-image-card[data-theme="press"] .edgeever-card-body h1 {
|
||||
border-left: 3px solid #b91c1c;
|
||||
}
|
||||
` : ""}
|
||||
|
||||
${theme === "highlight" ? `
|
||||
.edgeever-image-card[data-theme="highlight"] {
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(253, 224, 71, 0.7);
|
||||
box-shadow: 0 20px 50px -12px rgba(202, 138, 4, 0.16), 0 2px 8px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
.edgeever-image-card[data-theme="highlight"] strong,
|
||||
.edgeever-image-card[data-theme="highlight"] b {
|
||||
background: linear-gradient(104deg, rgba(254, 240, 138, 0.25) 1%, rgba(254, 240, 138, 0.8) 4%, rgba(254, 240, 138, 0.7) 96%);
|
||||
padding: 0.05em 0.35em;
|
||||
border-radius: 4px;
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
.edgeever-image-card[data-theme="highlight"] mark {
|
||||
background: linear-gradient(104deg, rgba(250, 204, 21, 0.35) 1%, rgba(250, 204, 21, 0.9) 3%, rgba(250, 204, 21, 0.8) 98%);
|
||||
font-weight: 600;
|
||||
}
|
||||
.edgeever-image-card[data-theme="highlight"] .edgeever-card-title {
|
||||
font-weight: 800;
|
||||
}
|
||||
.edgeever-image-card[data-theme="highlight"] .edgeever-card-body h1 {
|
||||
border-left: 4px solid #eab308;
|
||||
}
|
||||
` : ""}
|
||||
|
||||
${theme === "editorial" ? `
|
||||
.edgeever-image-card[data-theme="editorial"] {
|
||||
border: 2px solid #111827;
|
||||
|
||||
Reference in new issue
Block a user