fix(editor): 隐藏无语言标识代码块的空工具栏顶栏

This commit is contained in:
xfy 2026-07-23 14:00:42 +08:00
parent 67406f661a
commit f569258633
3 changed files with 68 additions and 3 deletions

View File

@ -39,6 +39,10 @@ describe('CodeBlockNodeView', () => {
} as any);
expect(view.dom.querySelector('.tiptap-codeblock-lang')?.textContent).toBe('python');
expect(view.dom.querySelector('.tiptap-codeblock-run')).not.toBeNull();
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(true);
});
it('普通块(python):显示语言标签,无运行按钮', () => {
@ -48,16 +52,47 @@ describe('CodeBlockNodeView', () => {
} as any);
expect(view.dom.querySelector('.tiptap-codeblock-lang')?.textContent).toBe('python');
expect(view.dom.querySelector('.tiptap-codeblock-run')).toBeNull();
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(true);
});
it('无 language 的块:标签为空或占位,无运行按钮', () => {
it('无 language 的块:隐藏 toolbar,无运行按钮', () => {
const view = new CodeBlockNodeView({
node: mockNode(''),
editor: mockEditor(),
} as any);
expect(view.dom.querySelector('.tiptap-codeblock-run')).toBeNull();
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'none',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(false);
});
it('node 语言由空更新为 python 时 update() 显示 toolbar', () => {
const view = new CodeBlockNodeView({
node: mockNode(''),
editor: mockEditor(),
} as any);
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'none',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(false);
view.update(mockNode('python'));
expect(view.dom.querySelector('.tiptap-codeblock-lang')?.textContent).toBe('python');
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(true);
view.update(mockNode(''));
expect(view.dom.querySelector<HTMLElement>('.tiptap-codeblock-toolbar')?.style.display).toBe(
'none',
);
expect(view.dom.classList.contains('has-toolbar')).toBe(false);
});
it('node 语言变化时 update() 刷新标签', () => {
const view = new CodeBlockNodeView({
node: mockNode('python'),

View File

@ -66,10 +66,10 @@ export class CodeBlockNodeView {
this.langBadge.classList.add('tiptap-codeblock-lang');
this.langBadge.textContent = extractLang((this.node.attrs.language as string) ?? '');
// runnable 块的语言标签可点击,触发编辑模态框(改语言/overrides
this.langBadge.addEventListener('click', () => this.openEditModal());
if (isRunnable(this.node)) {
this.langBadge.classList.add('tiptap-codeblock-lang-editable');
this.langBadge.title = '点击修改语言与运行配置';
this.langBadge.addEventListener('click', () => this.openEditModal());
}
this.toolbar.appendChild(this.langBadge);
@ -93,6 +93,9 @@ export class CodeBlockNodeView {
}
this.pre.appendChild(this.code);
this.container.appendChild(this.pre);
// 校验并设置工具栏显隐(无语言且非 runnable 时隐藏)
this.updateToolbarVisibility();
}
get dom(): HTMLElement {
@ -114,12 +117,22 @@ export class CodeBlockNodeView {
this.node = node;
if (oldLang !== newLang) {
this.langBadge.textContent = extractLang(newLang);
const runnable = isRunnable(node);
this.langBadge.classList.toggle('tiptap-codeblock-lang-editable', runnable);
if (runnable) {
this.langBadge.title = '点击修改语言与运行配置';
} else {
this.langBadge.removeAttribute('title');
}
// 更新 <code> 的 language class低亮按新语言重算
this.code.className = '';
const langClass = extractLang(newLang);
if (langClass) this.code.classList.add(`language-${langClass}`);
// runnable 状态变化时重建按钮(简化:不细粒度增删,整体重建工具栏按钮区)
this.refreshRunButton();
// 刷新工具栏显隐状态(无语言且非 runnable 时隐藏)
this.updateToolbarVisibility();
}
return true;
}
@ -137,6 +150,18 @@ export class CodeBlockNodeView {
}
}
/**
* runnable
*/
private updateToolbarVisibility(): void {
const lang = extractLang((this.node.attrs.language as string) ?? '');
const runnable = isRunnable(this.node);
const show = lang !== '' || runnable;
this.toolbar.style.display = show ? '' : 'none';
this.container.classList.toggle('has-toolbar', show);
}
/**
* DOM mutation ProseMirror
*
@ -178,6 +203,7 @@ export class CodeBlockNodeView {
/** 点击运行:调 editor.storage.__onRunCode结果填入结果区。 */
/** 点击语言标签:打开编辑模态框,修改当前 runnable 块的语言/overrides。 */
private openEditModal(): void {
if (!isRunnable(this.node)) return;
const pos = this.getPos?.();
const currentInfo = (this.node.attrs.language as string) ?? '';
if (pos === undefined) return;

View File

@ -960,9 +960,13 @@
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* pre 紧贴 toolbar 下方,合并圆角 */
/* pre 默认全圆角;有 toolbar 时与 toolbar 合并顶部圆角 */
.tiptap-codeblock > pre {
margin: 0;
border-radius: 6px;
}
.tiptap-codeblock.has-toolbar > pre {
border-radius: 0 0 6px 6px;
}