fix: preserve pasted content in new memos

This commit is contained in:
tianma-if committed 2026-07-29 10:05:11 +08:00
1 parent 7d471dd178
commit 36796994d5
5 files changed
+29 -11

No files matched your search

+5 -4
View File
@@ -162,6 +162,7 @@ type MemoSummaryRow = {
title: string | null;
excerpt: string;
content_text?: string | null;
content_markdown?: string | null;
tags_json: string;
is_pinned: number;
is_archived: number;
@@ -1534,7 +1535,7 @@ app.get("/api/v1/memos", async (c) => {
)
SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, mc.revision,
mc.content_text
mc.content_text, mc.content_markdown
FROM search_matches s
INNER JOIN memos m ON m.id = s.memo_id
INNER JOIN memo_contents mc ON mc.memo_id = m.id
@@ -1587,7 +1588,7 @@ app.get("/api/v1/memos", async (c) => {
c.env.storage.db.prepare(
`SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, mc.revision,
mc.content_text
mc.content_text, mc.content_markdown
FROM memos m
INNER JOIN memo_contents mc ON mc.memo_id = m.id
WHERE ${searchCursorConditions.join(" AND ")}
@@ -1616,7 +1617,7 @@ app.get("/api/v1/memos", async (c) => {
c.env.storage.db.prepare(
`SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, mc.revision,
mc.content_text
mc.content_text, mc.content_markdown
FROM memos m
INNER JOIN memo_contents mc ON mc.memo_id = m.id
WHERE ${cursorConditions.join(" AND ")}
@@ -4443,7 +4444,7 @@ const mapMemoSummary = (row: MemoSummaryRow): MemoSummary => ({
id: row.id,
notebookId: row.notebook_id,
title: row.title,
excerpt: row.excerpt || createExcerpt(row.content_text ?? ""),
excerpt: row.excerpt || createExcerpt(row.content_text ?? "") || createExcerpt(docToText(markdownToDoc(row.content_markdown ?? ""))),
tags: parseJsonArray(row.tags_json),
isPinned: Boolean(row.is_pinned),
isArchived: Boolean(row.is_archived),
+7 -3
View File
@@ -1166,6 +1166,7 @@ const RichEditorPane = ({
const [tagsText, setTagsText] = useState("");
const [saveState, setSaveState] = useState<"idle" | "saving" | "saved" | "queued" | "error" | "conflict">("idle");
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [hydratedEditorMemoId, setHydratedEditorMemoId] = useState<string | null>(null);
const [dirtyVersion, setDirtyVersion] = useState(0);
const [, setEditorStateVersion] = useState(0);
const [editorContentVersion, setEditorContentVersion] = useState(0);
@@ -1413,7 +1414,7 @@ const RichEditorPane = ({
content: memo
? resolveMemoContentDoc(memo.contentJson, memo.contentMarkdown)
: { type: "doc", content: [{ type: "paragraph" }] },
editable: Boolean(memo && !effectiveReadOnly),
editable: Boolean(memo && !effectiveReadOnly && hydratedEditorMemoId === memo.id),
editorProps: {
attributes: {
class: "prose prose-slate max-w-none focus:outline-none min-h-[300px] px-4 py-3 sm:px-7",
@@ -1887,6 +1888,7 @@ const RichEditorPane = ({
memoRef.current = null;
editSessionRef.current = null;
hydratedMemoIdRef.current = null;
setHydratedEditorMemoId(null);
editingMemoIdRef.current = null;
hasUnsavedChangesRef.current = false;
setHasUnsavedChanges(false);
@@ -1908,6 +1910,7 @@ const RichEditorPane = ({
if (!sameMemo) {
hydratedMemoIdRef.current = null;
setHydratedEditorMemoId(null);
}
if (sameMemo && hasUnsavedChangesRef.current && !memo.isDeleted) {
@@ -1969,6 +1972,7 @@ const RichEditorPane = ({
hydratedMemoIdRef.current = memo.id;
editSessionRef.current = editSessionResponse?.editSession ?? (requiresLocalEditSession(memo) ? createLocalEditSession(memo) : null);
setHydratedEditorMemoId(memo.id);
window.setTimeout(() => {
hydratingRef.current = false;
@@ -2001,9 +2005,9 @@ const RichEditorPane = ({
useEffect(() => {
if (isEditorReady(editor)) {
editor.setEditable(Boolean(memo && !effectiveReadOnly));
editor.setEditable(Boolean(memo && !effectiveReadOnly && hydratedEditorMemoId === memo.id));
}
}, [editor, effectiveReadOnly, memo]);
}, [editor, effectiveReadOnly, hydratedEditorMemoId, memo]);
useEffect(() => {
if (!isEditorReady(editor) || !memo) {
+2 -2
View File
@@ -38,7 +38,7 @@ import {
} from "@/lib/mobile-editor";
import { cn } from "@/lib/utils";
import { isBrowserOffline, isBrowserOnline, verifyBrowserConnectivity } from "@/lib/network-status";
import { createExcerpt, docToText, getNotebookDescendantIds, type Notebook, type AuthUser, type MemoSummary, type MemoDetail, type Resource, type MemoTemplate as SavedMemoTemplate } from "@edgeever/shared";
import { createExcerpt, docToText, getNotebookDescendantIds, resolveMemoContentDoc, type Notebook, type AuthUser, type MemoSummary, type MemoDetail, type Resource, type MemoTemplate as SavedMemoTemplate } from "@edgeever/shared";
import { toggleMobileMemoSelection } from "@edgeever/shared/mobile-ui";
import type {
Pane,
@@ -171,7 +171,7 @@ const memoToSummary = (memo: MemoDetail): MemoSummary => ({
id: memo.id,
notebookId: memo.notebookId,
title: memo.title,
excerpt: memo.excerpt || createExcerpt(memo.contentText || docToText(memo.contentJson) || memo.contentMarkdown),
excerpt: memo.excerpt || createExcerpt(memo.contentText || docToText(resolveMemoContentDoc(memo.contentJson, memo.contentMarkdown))),
tags: memo.tags,
isPinned: memo.isPinned,
isArchived: memo.isArchived,
+10 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { countMemoCharacters, docToMarkdown, markdownToDoc, resolveMemoContentDoc } from "./content.ts";
import { countMemoCharacters, docToMarkdown, docToText, markdownToDoc, resolveMemoContentDoc } from "./content.ts";
describe("memo character count", () => {
test("counts punctuation while excluding whitespace and formatting", () => {
@@ -59,6 +59,15 @@ describe("Markdown table conversion", () => {
});
});
describe("legacy Markdown body recovery", () => {
test("recovers a body when the stored JSON document is empty", () => {
const markdown = "权威和反中心化特征,即当一个人成为某种代表性的符号之后";
const resolved = resolveMemoContentDoc(markdownToDoc(""), markdown);
expect(docToText(resolved)).toContain(markdown);
});
});
describe("Nested list Markdown conversion", () => {
const markdown = [
"- Level 1",
+5 -1
View File
@@ -77,7 +77,11 @@ export const resolveMemoContentDoc = (
}
const markdownDoc = markdownToDoc(contentMarkdown);
return docContainsNodeType(markdownDoc, "table") ? markdownDoc : currentDoc;
// Some older saves left an empty JSON document behind while retaining the
// real body in Markdown. Treat that as a compatibility case too; otherwise
// the editor can show the Markdown body while list excerpts see an empty
// JSON document.
return docContainsNodeType(markdownDoc, "table") || !docToText(currentDoc) ? markdownDoc : currentDoc;
};
const LEGACY_ATTACHMENT_PATTERN = /^(附件:|Attachment:\s*)(.+?)\s+(\/api\/v1\/resources\/\S+|https?:\/\/\S+)$/;