基于NextJS+TipTap v2的剧本编辑器场景标题编号与大写问题求助
TipTap场景标题编号与自动大写问题:连续编号失效、插入新标题跳号/重复
技术栈:NextJS v14.1.0、TailwindCSS、TipTap v2、Typescript、NodeJS v20.8.0
我正在开发面向创意行业的SaaS剧本编辑器MVP,最初已实现三个核心功能:
- 段落独立
- 基于关键词自动识别格式
- 匹配段落全大写转换
对应实现代码:
import { Extension } from '@tiptap/core'; import { Plugin } from 'prosemirror-state'; import { Fragment } from 'prosemirror-model'; let paragraphCount = 0; // 追踪段落数量 const SceneTitle = Extension.create({ name: 'SceneTitle', addProseMirrorPlugins() { return [ new Plugin({ appendTransaction: (_, oldState, newState) => { let tr = newState.tr; let oldParagraphCount = 0; let newParagraphCount = 0; const regex = /^(INT\.|EXT\.|int\.|ext\.|INT|EXT|int|ext|EST|EST\.|est|est\.)/; oldState.doc.descendants((node) => { if (node.type.name === 'paragraph') { oldParagraphCount++; } }); newState.doc.descendants((node, pos) => { if (node.type.name === 'paragraph') { newParagraphCount++; if (regex.test(node.textContent)) { let from = pos + 1; let to = from + node.textContent.length; let fragment = Fragment.from(newState.schema.text(node.textContent.toUpperCase())); tr.replaceWith(from, to, fragment); } } }); if (newParagraphCount > oldParagraphCount) { paragraphCount++; console.log(`插入段落。总段落数:${paragraphCount}`); // 打印段落数 } else if (newParagraphCount < oldParagraphCount) { paragraphCount = 0; console.log(`删除段落。段落数重置为0。`); // 打印段落数重置 } return tr; }, }), ]; }, }); export default SceneTitle;
添加场景标题编号功能后,出现以下问题:
- 输入关键词(如"Int")时,仅关键词大写,后续输入的文本无法自动转大写;但先写后续文本再输入关键词时,能实现全大写。
- 场景标题无法实现连续不循环编号,在已有场景标题上方插入新标题时,编号出现重复或跳号:
错误示例1:
- INT. ...
- EXT. ...
- INT. ...
- EXT. ...
错误示例2:
- INT. ...
- EXT. ...
- INT. ...
- EXT. ...
添加编号后的问题代码:
import { Extension } from '@tiptap/core'; import { Plugin } from 'prosemirror-state'; import { Fragment } from 'prosemirror-model'; let paragraphCount = 0; // 追踪段落数量 let sceneCount = 1; // 追踪场景编号 let appliedPositions = new Set(); // 追踪已应用编号的位置 const SceneTitle = Extension.create({ name: 'SceneTitle', addProseMirrorPlugins() { return [ new Plugin({ appendTransaction: (_, oldState, newState) => { let tr = newState.tr; let oldParagraphCount = 0; let newParagraphCount = 0; const regex = /^(INT\.|EXT\.|int\.|ext\.|INT|EXT|int|ext|EST|EST\.|est|est\.)/; oldState.doc.descendants((node) => { if (node.type.name === 'paragraph') { oldParagraphCount++; } }); newState.doc.descendants((node, pos) => { if (node.type.name === 'paragraph' && regex.test(node.textContent)) { if (!appliedPositions.has(pos)) { let from = pos + 1; let to = from + node.textContent.length; let fragment = Fragment.from(newState.schema.text(sceneCount + "." + " " + node.textContent.toUpperCase() + "." + " ")); tr.replaceWith(from, to, fragment); appliedPositions.add(pos); sceneCount++; } } if (node.type.name === 'paragraph') { newParagraphCount++; } }); if (newParagraphCount > oldParagraphCount) { paragraphCount++; console.log(`插入段落。总段落数:${paragraphCount}`); // 打印段落数 } else if (newParagraphCount < oldParagraphCount) { paragraphCount = 0; sceneCount = 1; // 仅删除段落时重置场景编号 appliedPositions.clear(); console.log(`删除段落。段落和场景编号重置为0。`); // 打印重置信息 } return tr; }, }), ]; }, }); export default SceneTitle;
期望实现效果示例:
- INT. PUB - NIGHT
We reveal that SHAUN is sitting with a woman, LIZ. They are
both in their late twenties. LIZ looks slightly concerned,
SHAUN looks slightly confused. They are having a drink.- INT. APARTMENT COMPLEX - MORNING
- INT. APARTMENT - MORNING
- INT. LIVING ROOM - MORNING
需要解决两个核心问题:
- 确保场景标题全文本始终自动大写,无论输入顺序如何;
- 实现场景标题的连续正确编号,插入新标题时自动更新所有后续编号,避免重复或跳号。
内容的提问来源于stack exchange,提问作者salvatorecontino
相关产品推荐
相关产品推荐

