This commit is contained in:
parent
1e8473eb7b
commit
00c45a870f
518
sync.py
518
sync.py
|
|
@ -488,6 +488,10 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
|
||||
return ''.join(result)
|
||||
|
||||
# 收集变更块信息用于导航
|
||||
change_blocks = []
|
||||
change_block_id = 0
|
||||
|
||||
# 生成HTML
|
||||
html = f'''<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
|
@ -823,6 +827,166 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
.line-wrapper.blank-placeholder:hover .main-line {{
|
||||
background-color: rgba(0, 123, 255, 0.02);
|
||||
}}
|
||||
|
||||
/* 变更块高亮样式 */
|
||||
.line-wrapper.change-block {{
|
||||
border-left: 3px solid #007bff;
|
||||
}}
|
||||
|
||||
/* 导航浮窗样式 */
|
||||
.navigation-float {{
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 280px;
|
||||
max-height: 70vh;
|
||||
background-color: #fff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}}
|
||||
|
||||
.navigation-header {{
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}}
|
||||
|
||||
.navigation-toggle {{
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}}
|
||||
|
||||
.navigation-toggle:hover {{
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}}
|
||||
|
||||
.navigation-content {{
|
||||
max-height: calc(70vh - 50px);
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}}
|
||||
|
||||
.navigation-item {{
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
}}
|
||||
|
||||
.navigation-item:hover {{
|
||||
background-color: #e9ecef;
|
||||
border-left-color: #007bff;
|
||||
}}
|
||||
|
||||
.navigation-item.active {{
|
||||
background-color: #e3f2fd;
|
||||
border-left-color: #007bff;
|
||||
}}
|
||||
|
||||
.navigation-item-header {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}}
|
||||
|
||||
.navigation-item-number {{
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.navigation-item-type {{
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}}
|
||||
|
||||
.navigation-item-type.added {{
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}}
|
||||
|
||||
.navigation-item-type.replaced {{
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}}
|
||||
|
||||
.navigation-item-type.removed {{
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}}
|
||||
|
||||
.navigation-item-preview {{
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
line-height: 1.3;
|
||||
word-break: break-word;
|
||||
}}
|
||||
|
||||
.navigation-item-line {{
|
||||
font-size: 10px;
|
||||
color: #adb5bd;
|
||||
margin-top: 2px;
|
||||
}}
|
||||
|
||||
/* 导航浮窗收起状态 */
|
||||
.navigation-float.collapsed .navigation-content {{
|
||||
display: none;
|
||||
}}
|
||||
|
||||
.navigation-float.collapsed {{
|
||||
width: auto;
|
||||
min-width: 160px;
|
||||
}}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {{
|
||||
.navigation-float {{
|
||||
left: 10px;
|
||||
width: 240px;
|
||||
}}
|
||||
|
||||
.navigation-float.collapsed {{
|
||||
min-width: 120px;
|
||||
}}
|
||||
}}
|
||||
|
||||
@media (max-width: 480px) {{
|
||||
.navigation-float {{
|
||||
position: relative;
|
||||
left: auto;
|
||||
top: auto;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -834,6 +998,16 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navigation-float" id="navigation-float">
|
||||
<div class="navigation-header">
|
||||
<span>变更导航</span>
|
||||
<button class="navigation-toggle" id="navigation-toggle">−</button>
|
||||
</div>
|
||||
<div class="navigation-content" id="navigation-content">
|
||||
<!-- Navigation items will be generated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-container">
|
||||
<div class="content-header">中文翻译(含英文变更批注)</div>
|
||||
<div class="diff-content">
|
||||
|
|
@ -845,8 +1019,20 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
# 检查是否需要在此行之前插入空白行
|
||||
if i in blank_lines_to_insert:
|
||||
additions_list = blank_lines_to_insert[i]
|
||||
for addition_content in additions_list:
|
||||
html += f'<div class="line-wrapper blank-placeholder">'
|
||||
change_block_id += 1
|
||||
|
||||
# 添加变更块到导航列表
|
||||
preview_text = additions_list[0][:50] + "..." if len(additions_list[0]) > 50 else additions_list[0]
|
||||
change_blocks.append({
|
||||
'id': change_block_id,
|
||||
'line': i,
|
||||
'type': '新增',
|
||||
'preview': preview_text,
|
||||
'count': len(additions_list)
|
||||
})
|
||||
|
||||
for idx, addition_content in enumerate(additions_list):
|
||||
html += f'<div class="line-wrapper blank-placeholder change-block" data-change-id="{change_block_id}">'
|
||||
html += '<div class="main-line">'
|
||||
html += '<span class="line-number"> </span>' # 不显示行号
|
||||
html += f'<span class="line-content">(新增英文内容占位)</span>'
|
||||
|
|
@ -870,7 +1056,21 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
# 判断是否为空行
|
||||
is_empty = not line.strip()
|
||||
|
||||
html += f'<div class="line-wrapper {"has-changes" if has_changes else ""} {"empty-line" if is_empty else ""}">'
|
||||
# 如果有变更(除了新增),添加到导航列表
|
||||
if has_changes and any(change['type'] in ['replaced', 'removed'] for change in changes):
|
||||
change_block_id += 1
|
||||
preview_text = line[:50] + "..." if len(line) > 50 else line
|
||||
change_type = "替换" if any(change['type'] == 'replaced' for change in changes) else "删除"
|
||||
|
||||
change_blocks.append({
|
||||
'id': change_block_id,
|
||||
'line': i,
|
||||
'type': change_type,
|
||||
'preview': preview_text,
|
||||
'count': 1
|
||||
})
|
||||
|
||||
html += f'<div class="line-wrapper {"has-changes" if has_changes else ""} {"empty-line" if is_empty else ""} {"change-block" if has_changes else ""}" data-change-id="{change_block_id if has_changes else ""}">'
|
||||
html += f'<div class="main-line">'
|
||||
html += f'<span class="line-number">{i}</span>'
|
||||
html += f'<span class="line-content">{escaped_line if not is_empty else "(空行)"}</span>'
|
||||
|
|
@ -903,23 +1103,321 @@ def create_diff_html(title, en_diff, en_old_lines, en_new_lines, cn_content=None
|
|||
else:
|
||||
html += '<div class="no-translation">未找到对应的中文翻译页面</div>'
|
||||
|
||||
# 调试日志:打印change_blocks信息
|
||||
print(f"DEBUG: Final change_blocks length = {len(change_blocks)}")
|
||||
for i, block in enumerate(change_blocks):
|
||||
print(f"DEBUG: Final block {i}: {block}")
|
||||
|
||||
html += '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 点击有变更的行时高亮
|
||||
document.querySelectorAll('.line-wrapper.has-changes').forEach(lineWrapper => {{
|
||||
lineWrapper.addEventListener('click', () => {{
|
||||
// 动态生成导航项
|
||||
function generateNavigationItems() {
|
||||
const navContent = document.getElementById('navigation-content');
|
||||
const changeBlocks = document.querySelectorAll('.change-block');
|
||||
|
||||
// 清空现有内容
|
||||
navContent.innerHTML = '';
|
||||
|
||||
// 变更块信息已经在HTML中通过data属性标记
|
||||
const changes = [];
|
||||
|
||||
// 收集所有变更块信息
|
||||
changeBlocks.forEach((block, index) => {
|
||||
const changeId = block.getAttribute('data-change-id');
|
||||
if (changeId) {
|
||||
// 获取变更类型和预览文本
|
||||
const annotation = block.querySelector('.annotation');
|
||||
if (annotation) {
|
||||
const addedItem = annotation.querySelector('.annotation-item.added .annotation-header');
|
||||
const replacedItem = annotation.querySelector('.annotation-item.replaced .annotation-header');
|
||||
const removedItem = annotation.querySelector('.annotation-item.removed .annotation-header');
|
||||
|
||||
let type = '变更';
|
||||
let preview = '';
|
||||
|
||||
if (addedItem) {
|
||||
type = '新增';
|
||||
const content = annotation.querySelector('.annotation-item.added > div:last-child');
|
||||
preview = content ? content.textContent.substring(0, 50) : '';
|
||||
} else if (replacedItem) {
|
||||
type = '替换';
|
||||
const content = annotation.querySelector('.annotation-item.replaced .old-content');
|
||||
preview = content ? content.textContent.substring(0, 50) : '';
|
||||
} else if (removedItem) {
|
||||
type = '删除';
|
||||
const content = annotation.querySelector('.annotation-item.removed > div:last-child');
|
||||
preview = content ? content.textContent.substring(0, 50) : '';
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
preview += '...';
|
||||
}
|
||||
|
||||
// 获取行号
|
||||
const lineNumber = block.querySelector('.line-number');
|
||||
const line = lineNumber ? lineNumber.textContent : '?';
|
||||
|
||||
changes.push({
|
||||
id: changeId,
|
||||
type: type,
|
||||
preview: preview || '变更内容',
|
||||
line: line
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有变更,显示未发现变更
|
||||
if (changes.length === 0) {
|
||||
navContent.innerHTML = '<div class="navigation-item"><div class="navigation-item-preview">未发现变更</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成导航项
|
||||
changes.forEach(change => {
|
||||
const navItem = document.createElement('div');
|
||||
navItem.className = 'navigation-item';
|
||||
navItem.setAttribute('data-change-id', change.id);
|
||||
|
||||
navItem.innerHTML = `
|
||||
<div class="navigation-item-header">
|
||||
<span class="navigation-item-number">${change.id}</span>
|
||||
<span class="navigation-item-type ${change.type.toLowerCase()}">${change.type}</span>
|
||||
</div>
|
||||
<div class="navigation-item-preview">${change.preview}</div>
|
||||
<div class="navigation-item-line">行 ${change.line}</div>
|
||||
`;
|
||||
|
||||
// 添加点击事件
|
||||
navItem.addEventListener('click', () => {
|
||||
const targetBlock = document.querySelector(`[data-change-id="${change.id}"].change-block`);
|
||||
if (targetBlock) {
|
||||
targetBlock.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
updateActiveNavItem();
|
||||
highlightBlock(targetBlock);
|
||||
}
|
||||
});
|
||||
|
||||
navContent.appendChild(navItem);
|
||||
});
|
||||
}
|
||||
|
||||
// 导航功能
|
||||
class DiffNavigation {
|
||||
constructor() {
|
||||
this.navFloat = document.getElementById('navigation-float');
|
||||
this.navToggle = document.getElementById('navigation-toggle');
|
||||
// 延迟获取导航项,确保它们已经生成
|
||||
this.updateNavigationElements();
|
||||
this.isCollapsed = false;
|
||||
|
||||
// 调试日志
|
||||
console.log('DiffNavigation Constructor:');
|
||||
console.log(' navFloat:', this.navFloat);
|
||||
console.log(' navToggle:', this.navToggle);
|
||||
console.log(' navItems length:', this.navItems.length);
|
||||
console.log(' changeBlocks length:', this.changeBlocks.length);
|
||||
|
||||
// 打印所有导航项的详细信息
|
||||
this.navItems.forEach((item, index) => {
|
||||
console.log(` Navigation item ${index}:`, {
|
||||
id: item.getAttribute('data-change-id'),
|
||||
hasType: !!item.querySelector('.navigation-item-type'),
|
||||
hasPreview: !!item.querySelector('.navigation-item-preview')
|
||||
});
|
||||
});
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
updateNavigationElements() {
|
||||
this.navItems = document.querySelectorAll('.navigation-item');
|
||||
this.changeBlocks = document.querySelectorAll('.change-block');
|
||||
}
|
||||
|
||||
init() {
|
||||
// 绑定收起/展开按钮
|
||||
this.navToggle.addEventListener('click', () => {
|
||||
this.toggleCollapse();
|
||||
});
|
||||
|
||||
// 绑定导航项点击事件
|
||||
this.navItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
this.navigateToChange(item);
|
||||
});
|
||||
});
|
||||
|
||||
// 监听滚动,更新当前激活的导航项
|
||||
window.addEventListener('scroll', () => {
|
||||
this.updateActiveNavItem();
|
||||
});
|
||||
|
||||
// 初始化当前激活项
|
||||
this.updateActiveNavItem();
|
||||
}
|
||||
|
||||
toggleCollapse() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
if (this.isCollapsed) {
|
||||
this.navFloat.classList.add('collapsed');
|
||||
this.navToggle.textContent = '+';
|
||||
} else {
|
||||
this.navFloat.classList.remove('collapsed');
|
||||
this.navToggle.textContent = '−';
|
||||
}
|
||||
}
|
||||
|
||||
navigateToChange(navItem) {
|
||||
const changeId = navItem.getAttribute('data-change-id');
|
||||
const targetBlock = document.querySelector(`[data-change-id="${changeId}"].change-block`);
|
||||
|
||||
if (targetBlock) {
|
||||
// 滚动到目标位置
|
||||
targetBlock.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
|
||||
// 更新激活状态
|
||||
this.updateActiveNavItem();
|
||||
|
||||
// 添加临时高亮效果
|
||||
this.highlightBlock(targetBlock);
|
||||
}
|
||||
}
|
||||
|
||||
updateActiveNavItem() {
|
||||
const scrollPosition = window.scrollY + window.innerHeight / 2;
|
||||
let activeItem = null;
|
||||
|
||||
this.changeBlocks.forEach(block => {
|
||||
const blockTop = block.offsetTop;
|
||||
const blockBottom = blockTop + block.offsetHeight;
|
||||
|
||||
if (scrollPosition >= blockTop && scrollPosition <= blockBottom) {
|
||||
const changeId = block.getAttribute('data-change-id');
|
||||
activeItem = document.querySelector(`[data-change-id="${changeId}"].navigation-item`);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新激活状态
|
||||
this.navItems.forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
|
||||
if (activeItem) {
|
||||
activeItem.classList.add('active');
|
||||
// 确保激活项在视口内
|
||||
this.ensureNavItemVisible(activeItem);
|
||||
}
|
||||
}
|
||||
|
||||
ensureNavItemVisible(navItem) {
|
||||
const navContent = document.getElementById('navigation-content');
|
||||
const itemTop = navItem.offsetTop;
|
||||
const itemBottom = itemTop + navItem.offsetHeight;
|
||||
const scrollTop = navContent.scrollTop;
|
||||
const contentHeight = navContent.clientHeight;
|
||||
|
||||
if (itemTop < scrollTop) {
|
||||
navContent.scrollTop = itemTop - 10;
|
||||
} else if (itemBottom > scrollTop + contentHeight) {
|
||||
navContent.scrollTop = itemBottom - contentHeight + 10;
|
||||
}
|
||||
}
|
||||
|
||||
highlightBlock(block) {
|
||||
// 移除所有高亮
|
||||
document.querySelectorAll('.line-wrapper.highlight').forEach(line => {{
|
||||
document.querySelectorAll('.line-wrapper.highlight').forEach(line => {
|
||||
line.classList.remove('highlight');
|
||||
}});
|
||||
});
|
||||
|
||||
// 添加高亮效果
|
||||
block.classList.add('highlight');
|
||||
|
||||
// 2秒后移除高亮
|
||||
setTimeout(() => {
|
||||
block.classList.remove('highlight');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 公共方法:跳转到指定变更
|
||||
goToChange(changeId) {
|
||||
const navItem = document.querySelector(`[data-change-id="${changeId}"].navigation-item`);
|
||||
if (navItem) {
|
||||
this.navigateToChange(navItem);
|
||||
}
|
||||
}
|
||||
|
||||
// 公共方法:获取所有变更列表
|
||||
getChanges() {
|
||||
const changes = [];
|
||||
this.navItems.forEach(item => {
|
||||
const changeId = item.getAttribute('data-change-id');
|
||||
const typeElement = item.querySelector('.navigation-item-type');
|
||||
const previewElement = item.querySelector('.navigation-item-preview');
|
||||
const lineElement = item.querySelector('.navigation-item-line');
|
||||
|
||||
if (changeId && typeElement && previewElement && lineElement) {
|
||||
changes.push({
|
||||
id: parseInt(changeId),
|
||||
type: typeElement.textContent,
|
||||
preview: previewElement.textContent,
|
||||
line: lineElement.textContent
|
||||
});
|
||||
}
|
||||
});
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化导航
|
||||
let diffNavigation;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 首先生成导航项
|
||||
generateNavigationItems();
|
||||
|
||||
// 调试日志
|
||||
console.log('=== Diff Navigation Debug ===');
|
||||
console.log('Navigation float:', document.getElementById('navigation-float'));
|
||||
console.log('Navigation content:', document.getElementById('navigation-content'));
|
||||
console.log('Navigation items:', document.querySelectorAll('.navigation-item'));
|
||||
console.log('Change blocks:', document.querySelectorAll('.change-block'));
|
||||
console.log('Has changes lines:', document.querySelectorAll('.line-wrapper.has-changes'));
|
||||
|
||||
diffNavigation = new DiffNavigation();
|
||||
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.key >= '1' && e.key <= '9') {
|
||||
e.preventDefault();
|
||||
const changeId = parseInt(e.key);
|
||||
diffNavigation.goToChange(changeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 原有的点击高亮功能
|
||||
document.querySelectorAll('.line-wrapper.has-changes').forEach(lineWrapper => {
|
||||
lineWrapper.addEventListener('click', () => {
|
||||
// 移除所有高亮
|
||||
document.querySelectorAll('.line-wrapper.highlight').forEach(line => {
|
||||
line.classList.remove('highlight');
|
||||
});
|
||||
|
||||
// 高亮当前行
|
||||
lineWrapper.classList.add('highlight');
|
||||
}});
|
||||
}});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>'''
|
||||
|
|
|
|||
Loading…
Reference in New Issue