diff --git a/web/app/routes/$userName+/page+/$slug+/edit/functions/mutations.server.ts b/web/app/routes/$userName+/page+/$slug+/edit/functions/mutations.server.ts index 7736c1ad..1dd817c6 100644 --- a/web/app/routes/$userName+/page+/$slug+/edit/functions/mutations.server.ts +++ b/web/app/routes/$userName+/page+/$slug+/edit/functions/mutations.server.ts @@ -1,5 +1,4 @@ import { prisma } from "~/utils/prisma"; -import { generateHashForText } from "../utils/generateHashForText"; export async function upsertPageWithHtml( pageSlug: string, @@ -24,26 +23,10 @@ export async function upsertPageWithHtml( export async function upsertTitle(pageSlug: string, title: string) { const page = await prisma.page.findUnique({ where: { slug: pageSlug } }); if (!page) return; - const titleHash = generateHashForText(title, 0); await prisma.page.update({ where: { id: page.id }, data: { title: title }, }); - return await prisma.sourceText.upsert({ - where: { - pageId_textAndOccurrenceHash: { - pageId: page.id, - textAndOccurrenceHash: titleHash, - }, - }, - update: { text: title }, - create: { - pageId: page.id, - textAndOccurrenceHash: titleHash, - text: title, - number: 0, - }, - }); } export async function upsertTags(tags: string[], pageId: number) { diff --git a/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.test.ts b/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.test.ts index 8ddf8866..1056e5b1 100644 --- a/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.test.ts +++ b/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.test.ts @@ -239,4 +239,68 @@ describe("processHtmlContent", () => { // 各タイトル occurrence が異なる ID を持つことを確認 expect(titleOccurrences[0].id).not.toBe(titleOccurrences[1].id); }); + + test("同一HTMLを再度処理した場合に、編集していない箇所のsource_textsが維持されるか確認", async () => { + const pageSlug = "html-no-edit-test-page"; + const title = "No Edit Title"; + const htmlInput = ` +

Line A

+

Line B

+

Line C

+ `; + + const user = await prisma.user.upsert({ + where: { id: 13 }, + create: { + id: 13, + userName: "noedit", + email: "noedit@example.com", + displayName: "noedit", + icon: "noedit", + }, + update: {}, + }); + + // 初回処理 + await processHtmlContent(title, htmlInput, pageSlug, user.id, "en", true); + + const dbPage1 = await prisma.page.findUnique({ + where: { slug: pageSlug }, + include: { sourceTexts: true }, + }); + expect(dbPage1).not.toBeNull(); + if (!dbPage1) return; + + // 初回処理時のIDを記憶 + const originalTextIdMap = new Map(); + for (const st of dbPage1.sourceTexts) { + originalTextIdMap.set(st.text, st.id); + } + expect(originalTextIdMap.size).toBeGreaterThanOrEqual(3); + + // 変更なしで再度同一HTMLを処理 + await processHtmlContent(title, htmlInput, pageSlug, user.id, "en", true); + + const dbPage2 = await prisma.page.findUnique({ + where: { slug: pageSlug }, + include: { sourceTexts: true }, + }); + expect(dbPage2).not.toBeNull(); + if (!dbPage2) return; + + // 再処理後のIDマッピングを取得 + const afterTextIdMap = new Map(); + for (const st of dbPage2.sourceTexts) { + afterTextIdMap.set(st.text, st.id); + } + + // 全てのテキストでIDが変わっていないことを確認 + for (const [text, originalId] of originalTextIdMap.entries()) { + console.log(text, originalId); + expect(afterTextIdMap.get(text)).toBe(originalId); + } + + // source_textsの数が増減していないこと(無駄な消去がないこと) + expect(dbPage2.sourceTexts.length).toBe(dbPage1.sourceTexts.length); + }); }); diff --git a/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.ts b/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.ts index bcc21f37..6e0950a7 100644 --- a/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.ts +++ b/web/app/routes/$userName+/page+/$slug+/edit/utils/processHtmlContent.ts @@ -42,7 +42,7 @@ function extractTextFromHAST(node: Parent): string { }); return result; } -export function rehypeAddDataId(pageId: number): Plugin<[], Root> { +export function rehypeAddDataId(pageId: number, title: string): Plugin<[], Root> { return function attacher() { return async (tree: Root, file: VFile) => { const textOccurrenceMap = new Map(); @@ -74,11 +74,18 @@ export function rehypeAddDataId(pageId: number): Plugin<[], Root> { } }); + const allTextsForDb = blocks.map((block, index) => ({ text: block.text, textAndOccurrenceHash: block.textAndOccurrenceHash, number: index + 1, - })); + })) + + allTextsForDb.push({ + text: title, + textAndOccurrenceHash: generateHashForText(title, 0), + number: 0, + }); const hashToId = await synchronizePageSourceTexts(pageId, allTextsForDb); @@ -127,7 +134,7 @@ export async function processHtmlContent( .use(rehypeRemark) // HAST→MDAST .use(remarkGfm) // GFM拡張 .use(remarkRehype, { allowDangerousHtml: true }) // MDAST→HAST - .use(rehypeAddDataId(page.id)) + .use(rehypeAddDataId(page.id, title)) .use(rehypeRaw) // 生HTMLを処理 .use(rehypeStringify, { allowDangerousHtml: true }) // HAST→HTML .process(htmlInput); diff --git a/web/scripts/processMarkdownContent.ts b/web/scripts/processMarkdownContent.ts index 831847b3..c6ae97ad 100644 --- a/web/scripts/processMarkdownContent.ts +++ b/web/scripts/processMarkdownContent.ts @@ -28,7 +28,7 @@ export async function processMarkdownContent( const file = await remark() .use(remarkGfm) .use(remarkRehype) - .use(rehypeAddDataId(page.id)) + .use(rehypeAddDataId(page.id, title)) .use(rehypeRaw) .use(rehypeStringify, { allowDangerousHtml: true }) .process(body);