美化Block Editor 选中文字后的工具栏

This commit is contained in:
jingrow 2026-06-02 04:17:11 +08:00
parent 80a71c6d46
commit 6b8a88ee42

View File

@ -267,7 +267,7 @@ onMounted(() => {
codeBlock: false, // Use CodeBlockLowlight instead
link: false, // Configure separately
dropcursor: false, // Configure separately with custom options
heading: { levels: [1, 2, 3] },
heading: { levels: [1, 2, 3, 4, 5, 6] },
}),
// Additional text formatting
@ -551,6 +551,15 @@ function exec(name: string) {
case 'h3':
chain.toggleHeading({ level: 3 }).run()
break
case 'h4':
chain.toggleHeading({ level: 4 }).run()
break
case 'h5':
chain.toggleHeading({ level: 5 }).run()
break
case 'h6':
chain.toggleHeading({ level: 6 }).run()
break
case 'quote':
chain.toggleBlockquote().run()
break
@ -594,6 +603,20 @@ function exec(name: string) {
const activeDropdown = ref<string | null>(null)
const bubbleHeadingOpen = ref(false)
const currentHeadingIcon = computed(() => {
const ed = editorRef.value
if (!ed) return 'format_paragraph'
for (let level = 1; level <= 6; level++) {
if (ed.isActive('heading', { level })) return `h${level}`
}
return 'format_paragraph'
})
function toggleBubbleHeading() {
bubbleHeadingOpen.value = !bubbleHeadingOpen.value
}
function toggleDropdown(name: string | null) {
activeDropdown.value = activeDropdown.value === name ? null : name
}
@ -884,9 +907,23 @@ function setHighlightColor(color: string) {
<button class="be-tb-btn" :class="{ active: isActive('strike') }" :title="t('Strikethrough')" @click="exec('strike')" v-html="icon('strike')"></button>
<button class="be-tb-btn" :class="{ active: isActive('code') }" :title="t('Inline Code')" @click="exec('code')" v-html="icon('code')"></button>
<span class="be-tb-divider"></span>
<button class="be-tb-btn" :class="{ active: isActive('heading', { level: 1 }) }" :title="t('Heading 1')" @click="exec('h1')" v-html="icon('h1')"></button>
<button class="be-tb-btn" :class="{ active: isActive('heading', { level: 2 }) }" :title="t('Heading 2')" @click="exec('h2')" v-html="icon('h2')"></button>
<button class="be-tb-btn" :class="{ active: isActive('heading', { level: 3 }) }" :title="t('Heading 3')" @click="exec('h3')" v-html="icon('h3')"></button>
<div class="be-tb-dropdown be-bubble-dropdown">
<button class="be-tb-btn" :class="{ active: bubbleHeadingOpen }" :title="t('Heading')" @click="toggleBubbleHeading" v-html="icon(currentHeadingIcon)"></button>
<div v-if="bubbleHeadingOpen" class="be-tb-dropdown-menu">
<div
v-for="level in headingLevels" :key="level"
class="be-tb-dropdown-item"
:class="{ 'be-tb-dropdown-item-active': isActive('heading', { level }) }"
@mousedown.prevent="setHeading(level)"
><span v-html="icon('h' + level)"></span>{{ t('Heading ' + level) }}</div>
<div class="be-tb-dropdown-divider"></div>
<div
class="be-tb-dropdown-item"
:class="{ 'be-tb-dropdown-item-active': isActive('paragraph') }"
@mousedown.prevent="setParagraph()"
><span v-html="icon('format_paragraph')"></span>{{ t('Paragraph') }}</div>
</div>
</div>
<span class="be-tb-divider"></span>
<button class="be-tb-btn" :class="{ active: isActive('link') }" :title="t('Link')" @click="openLinkPanel" v-html="icon('link')"></button>
<button class="be-tb-btn" :class="{ active: isActive('blockquote') }" :title="t('Blockquote')" @click="exec('quote')" v-html="icon('quote')"></button>
@ -935,6 +972,9 @@ const ICONS: Record<string, string> = {
h1: makeTextIcon('H1'),
h2: makeTextIcon('H2'),
h3: makeTextIcon('H3'),
h4: makeTextIcon('H4'),
h5: makeTextIcon('H5'),
h6: makeTextIcon('H6'),
format_paragraph: '<path d="M6 4v16m0-8h12m0-8v16" stroke-width="2"/>',
link: '<path d="m9 15l6-6m-4-3l.463-.536a5 5 0 0 1 7.071 7.072L18 13m-5 5l-.397.534a5.07 5.07 0 0 1-7.127 0a4.97 4.97 0 0 1 0-7.071L6 11"/>',
format_size: '<text x="0.5" y="19" font-family="system-ui,-apple-system,sans-serif" font-size="20" font-weight="700" fill="currentColor" stroke="none">A</text><text x="13" y="19" font-family="system-ui,-apple-system,sans-serif" font-size="14" font-weight="600" fill="currentColor" stroke="none">a</text>',
@ -1155,14 +1195,23 @@ function icon(name: string): string {
align-items: center;
gap: 1px;
padding: 4px 6px;
background: #1e1e1e;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.25);
box-shadow: 0 4px 20px rgba(0,0,0,0.12);
}
.blockeditor-bubble-menu .be-tb-btn { color: #6b7280; }
.blockeditor-bubble-menu .be-tb-btn:hover { background: #e5e7eb; color: #1f2937; }
.blockeditor-bubble-menu .be-tb-btn.active { background: #2383e2; color: #fff; }
.blockeditor-bubble-menu .be-tb-divider { background: #d1d5db; }
/* Bubble menu heading dropdown - appear above trigger */
.be-bubble-dropdown { position: relative; }
.be-bubble-dropdown .be-tb-dropdown-menu {
bottom: 100%;
top: auto;
margin-bottom: 4px;
}
.blockeditor-bubble-menu .be-tb-btn { color: #b3b3b3; }
.blockeditor-bubble-menu .be-tb-btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
.blockeditor-bubble-menu .be-tb-btn.active { background: rgba(255,255,255,0.12); color: #fff; }
.blockeditor-bubble-menu .be-tb-divider { background: rgba(255,255,255,0.1); }
/* ── Link Panel ── */
.blockeditor-link-panel {