| 1 | import * as cheerio from 'cheerio' |
| 2 | import type { AnyNode } from 'domhandler' |
| 3 | import { ensureMasterStyleLink, setMasterPageNumber } from '../presentation/html/master-link' |
| 4 | |
| 5 | /** |
| 6 | * 纯函数:把 HTML 里出现的 oldPageId(按词边界匹配)整体替换为 nextPageId, |
| 7 | * 用于派生新页时避免 pageId 身份与源页串台。只依赖 cheerio,不碰 fs / electron / db, |
| 8 | * 便于在单测环境直接验证。 |
| 9 | */ |
| 10 | export const replacePageIdentity = (html: string, oldPageId: string, nextPageId: string): string => { |
| 11 | const oldId = oldPageId.trim() |
| 12 | if (!oldId || oldId === nextPageId) return html |
| 13 | const escapedOldId = oldId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') |
| 14 | const boundaryPattern = new RegExp(`(^|[^A-Za-z0-9_-])${escapedOldId}(?=$|[^A-Za-z0-9_-])`, 'g') |
| 15 | return html.replace(boundaryPattern, `$1${nextPageId}`) |
| 16 | } |
| 17 | |
| 18 | export const clearVisibleText = ($: cheerio.CheerioAPI, root: cheerio.Cheerio<AnyNode>): void => { |
| 19 | root.find('input, textarea').each((_, node) => { |
| 20 | const el = $(node) |
| 21 | el.removeAttr('value') |
| 22 | el.removeAttr('placeholder') |
| 23 | el.text('') |
| 24 | }) |
| 25 | root.find('*').contents().each((_, node) => { |
| 26 | const parentTag = node.parent?.type === 'tag' ? node.parent.name.toLowerCase() : '' |
| 27 | if (parentTag === 'script' || parentTag === 'style') return |
| 28 | if (node.type === 'text' && node.data?.trim()) { |
| 29 | node.data = '' |
| 30 | } |
| 31 | }) |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * 基于源页 HTML 生成一个**空白页**:重写 pageId 身份 + 改 title,并清空 |
| 36 | * `.ppt-page-content` 内的可见文字、打上 data-blank-page 标记。 |
| 37 | */ |
| 38 | export function buildBlankPageHtmlFromSource(args: { |
| 39 | html: string |
| 40 | oldPageId: string |
| 41 | nextPageId: string |
| 42 | pageNumber?: number |
| 43 | title: string |
| 44 | }): string { |
| 45 | const rewritten = replacePageIdentity(args.html, args.oldPageId, args.nextPageId) |
| 46 | const $ = cheerio.load(rewritten, { scriptingEnabled: false }) |
| 47 | $('title').text(args.title) |
| 48 | $('body').attr('data-page-id', args.nextPageId) |
| 49 | $('[data-page-id]').each((_, node) => { |
| 50 | const el = $(node) |
| 51 | if ((el.attr('data-page-id') || '').trim() === args.oldPageId) { |
| 52 | el.attr('data-page-id', args.nextPageId) |
| 53 | } |
| 54 | }) |
| 55 | |
| 56 | const content = $('.ppt-page-content').first() |
| 57 | if (content.length > 0) { |
| 58 | clearVisibleText($, content) |
| 59 | content.attr('data-blank-page', '1') |
| 60 | } |
| 61 | |
| 62 | return typeof args.pageNumber === 'number' |
| 63 | ? setMasterPageNumber(ensureMasterStyleLink($.html()), args.pageNumber) |
| 64 | : ensureMasterStyleLink($.html()) |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * 复制页面用:与 buildBlankPageHtmlFromSource 共用「换 pageId 身份 + 改 title」逻辑, |
| 69 | * 但**保留全部可见内容**(不调用 clearVisibleText,不打 data-blank-page 标记), |
| 70 | * 只把 pageId 相关身份重写到新页,避免两页之间 pageId/block 引用串台。 |
| 71 | */ |
| 72 | export function buildDuplicatePageHtmlFromSource(args: { |
| 73 | html: string |
| 74 | oldPageId: string |
| 75 | nextPageId: string |
| 76 | pageNumber?: number |
| 77 | title: string |
| 78 | }): string { |
| 79 | const rewritten = replacePageIdentity(args.html, args.oldPageId, args.nextPageId) |
| 80 | const $ = cheerio.load(rewritten, { scriptingEnabled: false }) |
| 81 | $('title').text(args.title) |
| 82 | $('body').attr('data-page-id', args.nextPageId) |
| 83 | $('[data-page-id]').each((_, node) => { |
| 84 | const el = $(node) |
| 85 | if ((el.attr('data-page-id') || '').trim() === args.oldPageId) { |
| 86 | el.attr('data-page-id', args.nextPageId) |
| 87 | } |
| 88 | }) |
| 89 | return typeof args.pageNumber === 'number' |
| 90 | ? setMasterPageNumber(ensureMasterStyleLink($.html()), args.pageNumber) |
| 91 | : ensureMasterStyleLink($.html()) |
| 92 | } |
| 93 |