You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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;

添加场景标题编号功能后,出现以下问题:

  1. 输入关键词(如"Int")时,仅关键词大写,后续输入的文本无法自动转大写;但先写后续文本再输入关键词时,能实现全大写。
  2. 场景标题无法实现连续不循环编号,在已有场景标题上方插入新标题时,编号出现重复或跳号:

错误示例1:

  1. INT. ...
  2. EXT. ...
  3. INT. ...
  4. EXT. ...

错误示例2:

  1. INT. ...
  2. EXT. ...
  3. INT. ...
  4. 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;

期望实现效果示例:

  1. 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.
  2. INT. APARTMENT COMPLEX - MORNING
  3. INT. APARTMENT - MORNING
  4. INT. LIVING ROOM - MORNING

需要解决两个核心问题:

  • 确保场景标题全文本始终自动大写,无论输入顺序如何;
  • 实现场景标题的连续正确编号,插入新标题时自动更新所有后续编号,避免重复或跳号。

内容的提问来源于stack exchange,提问作者salvatorecontino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 07:07:50