X架子鼓软件
71.7MB · 2025-11-24
在小说创作过程中,作者经常需要对笔记中的关键信息进行标记和分类,比如重要人物、地点、设定等。传统的文本编辑器往往只支持单一颜色的高亮,无法满足复杂的世界观管理需求。因此,我们在 51mazi 的笔记编辑器中设计了一个多颜色文本高亮功能,让作者能够用不同颜色标记不同类型的内容。
多彩文本高亮功能 - 支持5种颜色标记不同类型的内容
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 工具栏组件 │ │ 编辑器组件 │ │ TipTap扩展 │
│ EditorMenubar │◄──►│ EditorPanel │◄──►│ Highlight │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ 高亮状态管理与命令执行 │
└─────────────────────────────────────────────────────┘
// src/renderer/src/components/EditorMenubar.vue
<template>
<div class="editor-menubar">
<!-- 高亮按钮 -->
<el-popover v-model:visible="highlightPopoverVisible">
<template #reference>
<el-button :type="isHighlight ? 'primary' : 'default'">
<SvgIcon name="highlight" />
</el-button>
</template>
<!-- 颜色选择器 -->
<div class="highlight-color-picker">
<!-- 5种颜色选项 -->
<!-- 移除高亮选项 -->
</div>
</el-popover>
</div>
</template>
TipTap 的 Highlight 扩展支持多颜色高亮,我们需要在编辑器初始化时进行配置:
// src/renderer/src/components/NoteEditorContent.vue
import Highlight from '@tiptap/extension-highlight'
// 获取笔记编辑器的扩展配置
function getNoteExtensions() {
return [
// ... 其他扩展
Highlight.configure({
multicolor: true, // 启用多颜色支持
HTMLAttributes: {
class: 'search-highlight' // 添加自定义类名
}
})
]
}
// src/renderer/src/components/ChapterEditorContent.vue
import Highlight from '@tiptap/extension-highlight'
// 获取章节编辑器的扩展配置
function getChapterExtensions() {
return [
StarterKit,
// ... 其他扩展
Highlight.configure({
multicolor: true, // 启用多颜色支持
HTMLAttributes: {
class: 'search-highlight' // 添加自定义类名
}
})
]
}
关键配置说明:
multicolor: true: 启用多颜色支持,允许为不同的高亮设置不同的颜色HTMLAttributes: 为高亮元素添加自定义 HTML 属性,这里添加了 search-highlight 类名,方便样式控制在工具栏组件中定义可用的高亮颜色:
// src/renderer/src/components/EditorMenubar.vue
// 高亮颜色选项(5个浅色、亮色)
const highlightColors = [
{ value: '#ffeb3b', label: '黄色' },
{ value: '#a8e6cf', label: '绿色' },
{ value: '#a8c8ec', label: '蓝色' },
{ value: '#ffb3ba', label: '粉色' },
{ value: '#dda0dd', label: '紫色' }
]
颜色选择原则:
检测当前选中文本是否已应用高亮,以及使用的颜色:
// src/renderer/src/components/EditorMenubar.vue
// 高亮状态(仅笔记编辑器)
const isHighlight = computed(() => {
if (!props.editor) return false
const isNoteEditor = editorStore.file?.type === 'note'
if (isNoteEditor) {
// 笔记编辑器:根据当前选中文本的格式
return props.editor.isActive('highlight')
}
return false
})
// 检查当前高亮是否使用指定颜色
function isHighlightColorActive(color) {
if (!props.editor || !isHighlight.value) return false
const attrs = props.editor.getAttributes('highlight')
// 如果没有 color 属性,默认是黄色
const currentColor = attrs.color || '#ffeb3b'
return currentColor === color
}
状态检测逻辑:
editor.isActive('highlight') 检测当前选中文本是否已应用高亮editor.getAttributes('highlight') 获取高亮的属性,包括颜色#ffeb3b)使用 Element Plus 的 Popover 组件实现颜色选择器:
<!-- src/renderer/src/components/EditorMenubar.vue -->
<el-popover
v-if="isNoteEditor"
v-model:visible="highlightPopoverVisible"
placement="bottom"
:width="230"
trigger="click"
popper-style="padding: 6px;"
>
<template #reference>
<el-button
size="small"
class="toolbar-item"
:type="isHighlight ? 'primary' : 'default'"
title="高亮"
>
<SvgIcon name="highlight" :size="12" />
</el-button>
</template>
<div class="highlight-color-picker">
<div class="highlight-colors">
<!-- 颜色选项 -->
<div
v-for="color in highlightColors"
:key="color.value"
class="highlight-color-item"
:class="{ active: isHighlightColorActive(color.value) }"
:title="color.label"
@click="applyHighlight(color.value)"
>
<div
:style="{ backgroundColor: color.value }"
class="hightlight-color-item-main"
></div>
</div>
<!-- 分隔线 -->
<div class="highlight-color-split"></div>
<!-- 移除高亮选项 -->
<div
:class="{ active: !isHighlight }"
class="highlight-color-item highlight-color-none"
title="无高亮"
>
<SvgIcon
class="hightlight-color-item-main"
:size="20"
name="ban"
@click="removeHighlight"
/>
</div>
</div>
</div>
</el-popover>
// src/renderer/src/components/EditorMenubar.vue
.highlight-colors {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 4px;
}
.highlight-color-item {
padding: 5px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-shrink: 0;
.hightlight-color-item-main {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid transparent;
}
&:hover {
transform: scale(1.15);
background: #e3e3e3;
.hightlight-color-item-main {
border-color: var(--el-color-primary);
}
}
&.active {
background: #e3e3e3;
.hightlight-color-item-main {
border-color: var(--el-color-primary);
}
}
}
.highlight-color-split {
width: 1px;
height: 15px;
background: #999;
}
样式设计要点:
// src/renderer/src/components/EditorMenubar.vue
// 应用高亮颜色
function applyHighlight(color) {
if (!props.editor) return
const isNoteEditor = editorStore.file?.type === 'note'
if (isNoteEditor) {
// 使用 setHighlight 确保应用正确的颜色
props.editor.chain().focus().setHighlight({ color }).run()
highlightPopoverVisible.value = false
}
}
// 移除高亮
function removeHighlight() {
if (!props.editor) return
const isNoteEditor = editorStore.file?.type === 'note'
if (isNoteEditor) {
props.editor.chain().focus().unsetHighlight().run()
highlightPopoverVisible.value = false
}
}
命令执行逻辑:
editor.chain().focus(): 确保编辑器获得焦点.setHighlight({ color }): 为选中文本应用指定颜色的高亮.unsetHighlight(): 移除选中文本的高亮.run(): 执行命令链搜索功能也使用 Highlight 扩展来高亮匹配项,当前匹配项使用蓝色,其他匹配项使用黄色:
// src/renderer/src/components/SearchPanel.vue
// 为所有匹配项添加视觉高亮
function addVisualHighlights() {
if (!props.editor || matches.value.length === 0) return
// 清除之前的高亮
clearVisualHighlights()
// 为每个匹配项添加高亮标记
matches.value.forEach((match, index) => {
const isCurrent = index === currentMatchIndex.value
// 使用Tiptap的setMark命令添加高亮
props.editor.commands.setTextSelection({
from: match.from,
to: match.to
})
// 添加高亮标记,使用highlight扩展
props.editor.commands.setHighlight({
color: isCurrent ? '#409eff' : '#ffeb3b'
})
})
// 恢复当前匹配项的选择
if (currentMatchIndex.value >= 0 && currentMatchIndex.value < matches.value.length) {
highlightMatch(matches.value[currentMatchIndex.value])
}
}
// 清除视觉高亮
function clearVisualHighlights() {
if (!props.editor) return
// 选择整个文档
props.editor.commands.selectAll()
// 移除所有高亮标记
props.editor.commands.unsetHighlight()
// 清除选择
props.editor.commands.setTextSelection(0)
}
搜索高亮策略:
#409eff),突出显示#409eff),统一标记搜索高亮和手动高亮使用相同的 Highlight 扩展,但通过不同的颜色进行区分:
工具栏按钮的状态需要实时反映当前选中文本的高亮状态:
// src/renderer/src/components/EditorMenubar.vue
// 高亮状态(仅笔记编辑器)
const isHighlight = computed(() => {
if (!props.editor) return false
const isNoteEditor = editorStore.file?.type === 'note'
if (isNoteEditor) {
// 笔记编辑器:根据当前选中文本的格式
return props.editor.isActive('highlight')
}
return false
})
状态同步机制:
computed 响应式计算属性isActive() 方法检测状态// 应用高亮后自动关闭颜色选择器
function applyHighlight(color) {
// ... 应用高亮逻辑
highlightPopoverVisible.value = false // 关闭弹窗
}
// 移除高亮后自动关闭颜色选择器
function removeHighlight() {
// ... 移除高亮逻辑
highlightPopoverVisible.value = false // 关闭弹窗
}
交互优化要点:
TipTap 的 Highlight 扩展通过内联样式设置背景色,确保颜色正确显示:
// src/renderer/src/components/EditorPanel.vue
// Tiptap highlight扩展的样式(支持多颜色)
// 确保有 data-color 属性的 mark 元素使用 TipTap 扩展设置的颜色
// TipTap 扩展会通过内联 style 设置 background-color,优先级高于类选择器
样式渲染机制:
style 属性设置 background-color// 搜索高亮样式 - 使用选择高亮
::selection {
background-color: #409eff;
color: white;
}
样式优化策略:
本文详细介绍了在小说写作软件的笔记编辑器中实现文本高亮功能的技术方案。通过 TipTap 的 Highlight 扩展、Vue 3 的响应式系统以及精心设计的交互逻辑,我们实现了一个功能完善、用户体验优秀的多颜色文本高亮功能。
通过这套技术方案,我们为小说创作者提供了一个强大而直观的文本标记工具,大大提升了笔记管理和世界观构建的效率。
#TipTap #文本高亮 #Vue3 #Electron #小说写作 #富文本编辑 #前端开发 #用户体验