Skip to main content
Glama
HighlightManager.ts10.2 kB
import { Change, HighlightOptions } from '../types'; /** * 高亮管理器 * 管理Word ContentControl高亮标记 */ export class HighlightManager { private highlightMap: Map<string, string> = new Map(); // changeId -> controlId /** * 高亮颜色配置 */ readonly COLORS = { insert: '#E8F5E9', // 绿色背景 delete: '#FFEBEE', // 红色背景 modify: '#FFF9C4', // 黄色背景 format: '#E3F2FD', // 蓝色背景 }; /** * 创建高亮 */ async createHighlight(change: Change): Promise<void> { return Word.run(async (context) => { const body = context.document.body; let contentControl: Word.ContentControl; // 根据位置信息创建ContentControl if (change.position) { // 使用Range创建ContentControl const searchResults = body.search(change.content); searchResults.load('items'); await context.sync(); if (searchResults.items.length > 0) { const range = searchResults.items[0]; contentControl = range.insertContentControl(); } else { // 如果找不到内容,在文档末尾创建 const endRange = body.getRange(Word.RangeLocation.end); contentControl = endRange.insertContentControl(); contentControl.insertText(change.content, Word.InsertLocation.start); } } else { // 在文档末尾创建 const endRange = body.getRange(Word.RangeLocation.end); contentControl = endRange.insertContentControl(); contentControl.insertText(change.content, Word.InsertLocation.start); } // 配置ContentControl this.configureContentControl(contentControl, change); await context.sync(); // 保存映射关系 contentControl.load('id'); await context.sync(); this.highlightMap.set(change.id, contentControl.id); }); } /** * 配置ContentControl外观和属性 */ private configureContentControl(control: Word.ContentControl, change: Change): void { // 设置标签和标题 control.tag = change.id; control.title = change.description || this.getDefaultTitle(change.type); // 设置外观 control.appearance = Word.ContentControlAppearance.boundingBox; // 设置颜色 control.color = this.getColor(change.type); // 设置字体样式 if (change.type === 'delete') { control.font.strikeThrough = true; control.font.color = '#D32F2F'; // 红色字体 } else if (change.type === 'insert') { control.font.bold = true; control.font.color = '#388E3C'; // 绿色字体 } else if (change.type === 'modify') { control.font.italic = true; control.font.color = '#F57C00'; // 橙色字体 } // 不允许删除(用户必须通过Accept/Reject操作) control.cannotDelete = true; control.cannotEdit = true; } /** * 获取默认标题 */ private getDefaultTitle(type: Change['type']): string { const titles = { insert: '新增内容', delete: '删除内容', modify: '修改内容', format: '格式调整', }; return titles[type]; } /** * 获取高亮颜色 */ getColor(type: Change['type']): string { return this.COLORS[type] || this.COLORS.modify; } /** * 更新高亮 */ async updateHighlight(change: Change, options?: Partial<HighlightOptions>): Promise<void> { const controlId = this.highlightMap.get(change.id); if (!controlId) { throw new Error(`找不到修改的高亮: ${change.id}`); } return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); for (const control of contentControls.items) { control.load('tag'); await context.sync(); if (control.tag === change.id) { // 更新属性 if (options?.color) { control.color = options.color; } if (options?.title) { control.title = options.title; } await context.sync(); return; } } throw new Error(`找不到ContentControl: ${change.id}`); }); } /** * 移除高亮 */ async removeHighlight(changeId: string): Promise<void> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); for (const control of contentControls.items) { control.load('tag'); await context.sync(); if (control.tag === changeId) { // 保留内容,只删除ContentControl外壳 control.delete(false); await context.sync(); this.highlightMap.delete(changeId); return; } } console.warn(`找不到要删除的高亮: ${changeId}`); }); } /** * 移除所有高亮 */ async removeAllHighlights(): Promise<void> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); // 删除所有ContentControl但保留内容 for (const control of contentControls.items) { control.delete(false); } await context.sync(); this.highlightMap.clear(); }); } /** * 批量创建高亮 */ async createHighlights(changes: Change[]): Promise<void> { for (const change of changes) { try { await this.createHighlight(change); } catch (error) { console.error(`创建高亮失败 (${change.id}):`, error); } } } /** * 批量移除高亮 */ async removeHighlights(changeIds: string[]): Promise<void> { for (const id of changeIds) { try { await this.removeHighlight(id); } catch (error) { console.error(`移除高亮失败 (${id}):`, error); } } } /** * 查找高亮对应的ContentControl */ async findContentControl(changeId: string): Promise<Word.ContentControl | null> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); for (const control of contentControls.items) { control.load('tag'); await context.sync(); if (control.tag === changeId) { return control; } } return null; }); } /** * 获取所有高亮的修改ID */ getHighlightedChangeIds(): string[] { return Array.from(this.highlightMap.keys()); } /** * 检查修改是否已高亮 */ isHighlighted(changeId: string): boolean { return this.highlightMap.has(changeId); } /** * 获取高亮数量 */ getHighlightCount(): number { return this.highlightMap.size; } /** * 跳转到指定高亮 */ async scrollToHighlight(changeId: string): Promise<void> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); for (const control of contentControls.items) { control.load('tag'); await context.sync(); if (control.tag === changeId) { // 选中ContentControl control.select(Word.SelectionMode.select); await context.sync(); // 滚动到视图 control.track(); await context.sync(); return; } } throw new Error(`找不到高亮: ${changeId}`); }); } /** * 高亮文档中的搜索结果 */ async highlightSearchResults(searchText: string, color?: string): Promise<number> { return Word.run(async (context) => { const body = context.document.body; const searchResults = body.search(searchText, { matchCase: false, matchWholeWord: false }); searchResults.load('items'); await context.sync(); const count = searchResults.items.length; for (const result of searchResults.items) { const control = result.insertContentControl(); control.appearance = Word.ContentControlAppearance.boundingBox; control.color = color || '#FFEB3B'; // 黄色高亮 control.title = `搜索结果: ${searchText}`; } await context.sync(); return count; }); } /** * 清除搜索高亮 */ async clearSearchHighlights(): Promise<void> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); for (const control of contentControls.items) { control.load('title'); await context.sync(); if (control.title.startsWith('搜索结果:')) { control.delete(false); } } await context.sync(); }); } /** * 获取高亮统计 */ async getHighlightStats(): Promise<{ total: number; byType: Record<string, number>; }> { return Word.run(async (context) => { const contentControls = context.document.contentControls; contentControls.load('items'); await context.sync(); const stats = { total: 0, byType: {} as Record<string, number>, }; for (const control of contentControls.items) { control.load(['tag', 'title']); await context.sync(); if (control.tag && this.highlightMap.has(control.tag)) { stats.total++; const title = control.title; stats.byType[title] = (stats.byType[title] || 0) + 1; } } return stats; }); } /** * 导出高亮映射 */ exportHighlightMap(): Record<string, string> { return Object.fromEntries(this.highlightMap); } /** * 导入高亮映射 */ importHighlightMap(map: Record<string, string>): void { this.highlightMap.clear(); for (const [changeId, controlId] of Object.entries(map)) { this.highlightMap.set(changeId, controlId); } } } // 导出单例 export default new HighlightManager();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/walkingzzzy/office-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server