鸿蒙 PC Markdown 编辑器标签系统:十二标签上限与桌面交互
标签栏是多文档状态的可视投影。它要表达活动文档、文件名、未保存状态、关闭动作和新建入口,还要在自由窗口缩窄时保持可操作。无限标签看似自由,实际会让完整 EditorState、撤销历史和正文无界驻留内存。
本文基于 OhMarkdown,分析十二标签上限、URI去重、空白会话复用、稳定身份、活动标签切换和关闭后继。代码位于 https://gitcode.com/VON-/codex_md_oh,对应提交3a9146e。
上限是内存策略
exportconstMAX_OPEN_DOCUMENT_SESSIONS=12;每个 Web会话保存 CodeMirror EditorState、正文树、选区和撤销历史;原生会话还保存正文、持久化基线、格式与状态。标签不是只有几十字节标题。当前没有后台冻结和历史压缩,所以十二是明确保护边界。
达到上限时拒绝创建并显示:
if(this.documentSessions.length>=MAX_OPEN_DOCUMENT_SESSIONS){this.operationStatus=`Open document limit reached (`+`${MAX_OPEN_DOCUMENT_SESSIONS})`;return;}拒绝发生在修改数组之前,不创建半会话。打开文件路径也检查并抛出明确错误。未来提高上限应先测总内容和历史内存,而不是只改常量。
sessionId 是标签身份
新会话 id由单调序列产生:
privatecreateDocumentSessionId():string{this.documentSessionSequence+=1;return`document-session-`+`${this.documentSessionSequence}`;}文件名不能作为 key,多个 Untitled同名;URI不能覆盖未命名;数组索引在关闭和插入后变化。sessionId只在进程内唯一,足够对齐 ArkUI ForEach与 Web Map。
标签渲染 key:
ForEach(this.documentSessions,(session:DocumentSession)=>{this.documentTab(session)},(session:DocumentSession):string=>session.id)稳定 key避免关闭左侧标签后,框架把右侧组件状态错误复用到新索引。
新建前捕获当前正文
awaitthis.captureActiveDocumentSession();constsession=createUntitledSession(this.createDocumentSessionId());this.documentSessions=this.documentSessions.concat([session]);this.applyDocumentSession(session);this.setEditorDocument('');先从 ArkWeb读取当前全文并同步旧会话,再追加新项。若先切 active id,捕获内容会写进空会话。数组使用 concat产生新引用,触发 ArkUI更新。
新文档拥有默认 UTF-8无 BOM、LF、revision 0和 dirty false。它不是复用上一标签页面字段后只清空正文,否则格式和状态会泄漏。
打开文件先按 URI 去重
constopenedSession=this.documentSessions.find((session)=>session.uri===openedDocument.uri);if(openedSession){awaitthis.captureActiveDocumentSession();this.applyDocumentSession(openedSession);awaitthis.activateEditorSession(openedSession);return;}文件树重复点击同一 URI只激活已有标签。若创建两个副本,两个脏缓冲区覆盖同一外部文件会产生不可解释冲突。URI去重是保存安全的一部分。
URI比较依赖系统返回值规范。不同 provider若给同一文件不同 URI,需要未来引入文档标识或规范化;当前不通过字符串 path猜测身份。
首次打开复用干净空白标签
constcanReuseActiveSession=activeSession!==undefined&&activeSession.uri.length===0&&!activeSession.dirty&&activeSession.content.length===0;应用启动自带一个空标签。第一次打开文件时复用,避免留下无意义 Untitled。三个条件缺一不可:无 URI、未修改、正文为空。已经保存但内容为空的真实文件有 URI,不能被复用;未命名但脏的空内容可能经历输入再删除,仍需保留其历史与恢复语义。
复用时保持 sessionId,替换会话对象;否则追加新 id。Web使用setSessionDocument删除该 id旧快照并重建,确保撤销历史不会回到空白标签的旧操作。
标签视觉状态来自会话
活动判断是:
session.id===this.activeDocumentSessionId活动标签使用强调文字、背景和底边;非活动用次级文字。dirty由每个 session决定,标题旁显示标记,而不是读取全局documentDirty。后台标签即使不活动也能显示未保存。
名称限制单行并省略,避免超长文件名撑开整个工具栏。完整名称可通过 tooltip或无障碍文本提供。固定标签高度让 dirty标记和关闭图标不会改变布局。
点击切换有异步互斥
privateasyncactivateDocumentSession(sessionId:string):Promise<void>{if(sessionId===this.activeDocumentSessionId||this.operationInProgress){return;}// 捕获当前、应用目标、激活 EditorState}保存、文件选择或目录操作期间不切换,避免异步返回写到错误标签。点击当前标签也直接返回,不重复捕获和重绘。
Web激活恢复完整 EditorState,因此光标、选择和历史保留。切换完成后回报字数与 dirty,状态栏跟随目标。
关闭按钮不能冒泡成切换
标签行本身点击激活,关闭按钮点击请求关闭。实现需要保证关闭按钮事件不会先触发父容器切换,尤其关闭后台脏标签时。ArkUI控件的事件传播应在模拟器验证,不能只看业务函数。
关闭图标使用系统 symbol和无障碍文本:
.accessibilityText($r('app.string.close_document')).onClick(()=>this.requestCloseDocumentSession(session.id))熟悉的叉号不需要文字按钮占空间,tooltip/读屏说明动作。触控目标必须大于图标视觉尺寸,适配鼠标与触摸二合一设备。
脏标签有三分支
未保存标签显示取消、放弃、保存。取消保留;放弃明确删除;保存等待 URI选择和写入成功。点击保存不立即关闭,最终只有目标仍活动且documentDirty=false才提交。
后台脏标签先激活再保存,因为当前保存管线使用活动页面字段。激活失败则不关。选择器取消、Mixed换行取消、冲突和写入失败都保持标签。
关闭后的后继规则
constnextIndex=Math.min(sessionIndex,remainingSessions.length-1);constnextSession=remainingSessions[nextIndex];关闭中间标签,原索引的新项是右侧邻居;关闭最后项,选择新末尾。用户空间记忆比每次跳到第一个更自然。
先 apply并激活后继,再删除旧 Web快照,避免无活动 state中间帧。关闭后台标签不影响当前编辑器。
最后一个标签关闭
数组为空时创建新 Untitled:
if(remainingSessions.length===0){remainingSessions=[createUntitledSession(this.createDocumentSessionId())];}工作台始终有编辑目标,快捷键与打开命令不需要零会话分支。用户感知是回到空白文档。
窄窗口与溢出
当前最多十二项,标签栏需要水平容纳或滚动。固定上限不能保证小窗口不溢出。自由窗口最小宽度640vp,侧边栏在900以下隐藏,为标签释放空间;名称使用省略。
长期应为标签区增加水平滚动、活动标签自动滚入视图、鼠标滚轮横向切换或溢出菜单。不能让工具栏 Open/Save/模式控件被标签挤出。标签拖动排序、固定和最近关闭属于后续能力。
鸿蒙 PC 模拟器结果
下图显示两个独立标签,活动底边、文件图标、关闭按钮和新建入口均在桌面工具栏中。两个标签正文独立,dirty状态不会串联。
键盘与可访问性
成熟 PC标签需要 Ctrl/Cmd+W关闭、Ctrl+Tab切换、Ctrl/Cmd+数字定位、键盘焦点可见。当前核心快捷键集中在保存、打开和查找,标签快捷键仍需实现。新增时应由原生层统一路由,避免 ArkWeb和 ArkUI同时处理。
读屏需要宣布文件名、活动状态、未保存状态和关闭动作。只在颜色和星号表达 dirty不足以满足可访问性,标签 accessibilityText应组合这些语义。
当前边界
十二标签是固定上限,没有后台冻结;没有标签滚动和溢出菜单;不持久化跨进程会话;没有拖动、固定、批量保存和退出汇总;完整多标签崩溃恢复未实现。
边界明确后,标签系统仍可靠覆盖主要多文档写作。后续扩展应围绕 DocumentSession身份,不重新以标题或索引管理状态。
结语
标签栏不是装饰,它把会话身份、URI、dirty、EditorState和关闭事务呈现给用户。十二上限控制内存,稳定 id保证声明式身份,URI去重防止同文件冲突,空白复用减少噪音,后继规则保持桌面空间记忆。
鸿蒙 PC Markdown编辑器先保证每个可见标签都对应一份不会串线、不会误关的真实会话,再增加拖动和固定,才是正确顺序。