feat: implement dynamic training course tab system and modular PDF data migration scripts
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
templates_to_update = [
|
||||
'src/main/resources/templates/dao-tao-detail.html',
|
||||
'src/main/resources/templates/posts/full-width.html',
|
||||
'src/main/resources/templates/posts/livestream.html',
|
||||
'src/main/resources/templates/posts/sidebar.html',
|
||||
'src/main/resources/templates/posts/standard.html'
|
||||
]
|
||||
|
||||
# In dao-tao-detail, the loop is `blocks.forEach(function(block) {`
|
||||
# In others, the loop is `data.blocks.forEach(function(block) {`
|
||||
# The switch starts with `switch(block.type) {` and ends with `}` before the end of the loop.
|
||||
|
||||
for filepath in templates_to_update:
|
||||
if not os.path.exists(filepath):
|
||||
print(f"Skipping {filepath}, does not exist")
|
||||
continue
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the block loop start
|
||||
loop_start_match = re.search(r'(data\.blocks|blocks)\.forEach\(function\(block\)\s*\{', content)
|
||||
if not loop_start_match:
|
||||
print(f"Loop not found in {filepath}")
|
||||
continue
|
||||
|
||||
# We want to replace the whole forEach logic with our new state machine logic.
|
||||
# But we need the existing switch cases!
|
||||
switch_match = re.search(r'switch\s*\(\s*block\.type\s*\)\s*\{([\s\S]*?)default:', content)
|
||||
if not switch_match:
|
||||
print(f"Switch not found in {filepath}")
|
||||
continue
|
||||
|
||||
cases_content = switch_match.group(1)
|
||||
|
||||
new_parser_code = """
|
||||
var inTabs = false;
|
||||
var tabsGroup = null;
|
||||
|
||||
function renderSingleBlock(block) {
|
||||
var html = "";
|
||||
switch(block.type) {
|
||||
""" + cases_content + """
|
||||
case "tabStart":
|
||||
case "tabEnd":
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown block type", block.type);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
var blockList = data && data.blocks ? data.blocks : (typeof blocks !== 'undefined' ? blocks : []);
|
||||
var finalHtml = "";
|
||||
|
||||
blockList.forEach(function(block) {
|
||||
if (block.type === 'tabStart') {
|
||||
if (!inTabs) {
|
||||
inTabs = true;
|
||||
tabsGroup = { tabs: [] };
|
||||
}
|
||||
tabsGroup.tabs.push({
|
||||
title: block.data.title || 'Tab',
|
||||
contentHtml: ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.type === 'tabEnd') {
|
||||
if (inTabs && tabsGroup) {
|
||||
inTabs = false;
|
||||
var groupHtml = '<div class="custom-tabs-wrapper">';
|
||||
groupHtml += '<div class="tabs-container flex justify-center xl:mb-8 md:mb-6 mb-4" style="margin-bottom: 2rem; gap: 10px; display: flex; flex-wrap: wrap;">';
|
||||
tabsGroup.tabs.forEach(function(tab, index) {
|
||||
var activeClass = (index === 0) ? 'active' : '';
|
||||
groupHtml += '<button class="dao-tao-tab-btn custom-tab-btn ' + activeClass + '" data-tab-idx="' + index + '" style="padding: 10px 20px; border: 1px solid #ccc; background: ' + (index===0 ? '#0055a5' : '#f8f9fa') + '; color: ' + (index===0 ? 'white' : 'black') + '; cursor: pointer;">' + tab.title + '</button>';
|
||||
});
|
||||
groupHtml += '</div>';
|
||||
|
||||
tabsGroup.tabs.forEach(function(tab, index) {
|
||||
var displayStyle = (index === 0) ? 'block' : 'none';
|
||||
groupHtml += '<div class="dao-tao-tab-panel custom-tab-panel" data-tab-idx="' + index + '" style="display: ' + displayStyle + ';">';
|
||||
groupHtml += tab.contentHtml;
|
||||
groupHtml += '</div>';
|
||||
});
|
||||
groupHtml += '</div>';
|
||||
finalHtml += groupHtml;
|
||||
tabsGroup = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var blockHtml = renderSingleBlock(block);
|
||||
|
||||
if (inTabs && tabsGroup && tabsGroup.tabs.length > 0) {
|
||||
tabsGroup.tabs[tabsGroup.tabs.length - 1].contentHtml += blockHtml;
|
||||
} else {
|
||||
finalHtml += blockHtml;
|
||||
}
|
||||
});
|
||||
|
||||
if (inTabs && tabsGroup) {
|
||||
// Close unclosed tabs at the end
|
||||
var groupHtml = '<div class="custom-tabs-wrapper">';
|
||||
groupHtml += '<div class="tabs-container flex justify-center xl:mb-8 md:mb-6 mb-4" style="margin-bottom: 2rem; gap: 10px; display: flex; flex-wrap: wrap;">';
|
||||
tabsGroup.tabs.forEach(function(tab, index) {
|
||||
var activeClass = (index === 0) ? 'active' : '';
|
||||
groupHtml += '<button class="dao-tao-tab-btn custom-tab-btn ' + activeClass + '" data-tab-idx="' + index + '" style="padding: 10px 20px; border: 1px solid #ccc; background: ' + (index===0 ? '#0055a5' : '#f8f9fa') + '; color: ' + (index===0 ? 'white' : 'black') + '; cursor: pointer;">' + tab.title + '</button>';
|
||||
});
|
||||
groupHtml += '</div>';
|
||||
|
||||
tabsGroup.tabs.forEach(function(tab, index) {
|
||||
var displayStyle = (index === 0) ? 'block' : 'none';
|
||||
groupHtml += '<div class="dao-tao-tab-panel custom-tab-panel" data-tab-idx="' + index + '" style="display: ' + displayStyle + ';">';
|
||||
groupHtml += tab.contentHtml;
|
||||
groupHtml += '</div>';
|
||||
});
|
||||
groupHtml += '</div>';
|
||||
finalHtml += groupHtml;
|
||||
}
|
||||
|
||||
html = finalHtml; // Assign back to whatever variable the original script used.
|
||||
"""
|
||||
|
||||
# We need to replace the entire forEach loop.
|
||||
# We will use regex to find the block from data.blocks.forEach up to the end of the loop.
|
||||
# It's tricky to balance braces with regex. Let's do string replacement.
|
||||
|
||||
if filepath.endswith('dao-tao-detail.html'):
|
||||
start_marker = "blocks.forEach(function(block) {"
|
||||
end_marker = "html += \"</table></figure>\";\n break;"
|
||||
|
||||
start_idx = content.find(start_marker)
|
||||
# Find the closing brace of the switch statement after end_marker
|
||||
end_marker_idx = content.find(end_marker, start_idx) + len(end_marker)
|
||||
|
||||
# We need to replace up to the end of the forEach loop.
|
||||
end_loop_idx = content.find("});", end_marker_idx) + 3
|
||||
|
||||
content = content[:start_idx] + new_parser_code + content[end_loop_idx:]
|
||||
else:
|
||||
start_marker = "data.blocks.forEach(function(block) {"
|
||||
end_marker = "case \"raw\":\n html += block.data.html;\n break;"
|
||||
|
||||
start_idx = content.find(start_marker)
|
||||
end_marker_idx = content.find(end_marker, start_idx) + len(end_marker)
|
||||
end_loop_idx = content.find("});", end_marker_idx) + 3
|
||||
|
||||
content = content[:start_idx] + new_parser_code + content[end_loop_idx:]
|
||||
|
||||
# Inject tab switching script at the end of the file if not present
|
||||
switch_script = """
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('custom-tab-btn')) {
|
||||
var btn = e.target;
|
||||
var container = btn.closest('.custom-tabs-wrapper');
|
||||
if (!container) return;
|
||||
var idx = btn.getAttribute('data-tab-idx');
|
||||
|
||||
container.querySelectorAll('.custom-tab-btn').forEach(function(b) {
|
||||
b.classList.remove('active');
|
||||
b.style.background = '#f8f9fa';
|
||||
b.style.color = 'black';
|
||||
});
|
||||
btn.classList.add('active');
|
||||
btn.style.background = '#0055a5';
|
||||
btn.style.color = 'white';
|
||||
|
||||
container.querySelectorAll('.custom-tab-panel').forEach(function(p) {
|
||||
p.style.display = 'none';
|
||||
});
|
||||
var activePanel = container.querySelector('.custom-tab-panel[data-tab-idx="' + idx + '"]');
|
||||
if (activePanel) activePanel.style.display = 'block';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
if "custom-tab-btn" not in content:
|
||||
content = content.replace("</body>", switch_script + "</body>")
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Updated {filepath}")
|
||||
|
||||
Reference in New Issue
Block a user