// ==UserScript==
// @name Hack olm
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Hack olm.vn
// @author Bảo Ngọc & druslla
// @match *://*.olm.vn/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const HOOK_CODE = `(() => {
if (window.__olmHooked) return; window.__olmHooked = true;
const tryParseJSON = (text) => { try { return JSON.parse(text); } catch { return null; } };
const looksLikeBase64 = (s) => typeof s === 'string' && s.length > 16 && /^[A-Za-z0-9+/=]+$/.test(s);
const collectContents = (obj, out) => {
if (!obj) return;
if (Array.isArray(obj)) { for (const it of obj) collectContents(it, out); return; }
if (typeof obj === 'object') {
for (const [k, v] of Object.entries(obj)) {
if (k === 'content' && looksLikeBase64(v)) out.push(v);
collectContents(v, out);
}
}
};
const postIfHasContents = (meta, text) => {
const json = tryParseJSON(text); if (!json) return;
const contents = []; collectContents(json, contents);
if (contents.length) {
window.postMessage({ type: 'OLM_SNIF_CONTENTS', meta, contents }, '*');
}
};
// XHR hook
(() => {
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
const origSetHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
this.__olm = { method, url, headers: {}, body: null };
try { this.addEventListener('load', () => {
if (this.responseType && this.responseType !== 'text') return;
postIfHasContents(this.__olm, this.responseText || '');
}); } catch {}
return origOpen.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.setRequestHeader = function(h, v) {
try { if (this.__olm) this.__olm.headers[h] = v; } catch {}
return origSetHeader.call(this, h, v);
};
XMLHttpRequest.prototype.send = function(body) {
try { if (this.__olm) this.__olm.body = body; } catch {}
return origSend.call(this, body);
};
})();
(() => {
const origFetch = window.fetch;
window.fetch = function(input, init) {
const method = (init && init.method) || (input && input.method) || 'GET';
const url = typeof input === 'string' ? input : (input && input.url) || '';
const headers = (init && init.headers) || (input && input.headers) || {};
const body = (init && init.body) || (input && input.body) || null;
const meta = { method, url, headers, body };
const p = origFetch(input, init);
p.then(r => {
try {
const c = r.clone();
c.text().then(t => postIfHasContents(meta, t)).catch(() => {});
} catch {}
}).catch(() => {});
return p;
};
})();
})();`;
const inject = () => {
const s = document.createElement('script');
s.textContent = HOOK_CODE;
(document.head || document.documentElement).appendChild(s);
s.remove();
};
inject();
const state = { isVisible: true, items: [], originals: [], firstMeta: null };
let beautifierLoading = null;
function ensureBeautifier() {
if (window.html_beautify) return Promise.resolve();
if (beautifierLoading) return beautifierLoading;
beautifierLoading = new Promise((resolve) => {
const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/[email protected]/js/lib/beautify-html.min.js';
s.async = true;
s.onload = () => resolve();
s.onerror = () => resolve();
document.head.appendChild(s);
});
return beautifierLoading;
}
let mathjaxLoading = null;
function ensureMathJax() {
if (window.MathJax && window.MathJax.typesetPromise) return Promise.resolve();
if (mathjaxLoading) return mathjaxLoading;
window.MathJax = {
tex: {
inlineMath: [["$","$"],["\\(","\\)"]],
displayMath: [["$$","$$"],["\\[","\\]"]],
processEscapes: true,
packages: { '[+]': ['mhchem'] }
},
loader: { load: ['[tex]/mhchem'] }
};
mathjaxLoading = new Promise((resolve) => {
const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js';
s.async = true;
s.onload = () => resolve();
s.onerror = () => resolve();
document.head.appendChild(s);
});
return mathjaxLoading;
}
const utils = {
safeDecode(base64) {
try { return decodeURIComponent(escape(atob(base64))); } catch { return null; }
}
};
const fuzzyMatch = {
normalize(text) {
return text.toLowerCase()
.replace(/\s+/g, ' ')
.replace(/[^\w\s]/g, '')
.trim();
},
similarity(s1, s2) {
const n1 = this.normalize(s1);
const n2 = this.normalize(s2);
if (n1 === n2) return 1;
if (!n1 || !n2) return 0;
const len1 = n1.length;
const len2 = n2.length;
const maxLen = Math.max(len1, len2);
if (n1.includes(n2) || n2.includes(n1)) {
return 0.8 + (0.2 * Math.min(len1, len2) / maxLen);
}
const chars1 = new Set(n1.split(''));
const chars2 = new Set(n2.split(''));
const intersection = new Set([...chars1].filter(x => chars2.has(x)));
const union = new Set([...chars1, ...chars2]);
return intersection.size / union.size;
},
findBestMatch(searchText) {
const candidates = [];
const minLength = 3;
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
const text = node.textContent.trim();
if (text.length < minLength) return NodeFilter.FILTER_REJECT;
const parent = node.parentElement;
if (!parent || parent.closest('#olm-answers-container')) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
}
}
);
let node;
while (node = walker.nextNode()) {
const text = node.textContent.trim();
const parent = node.parentElement;
if (parent) {
candidates.push({ element: parent, text });
}
}
let bestMatch = null;
let bestScore = 0;
for (const candidate of candidates) {
const score = this.similarity(searchText, candidate.text);
if (score > bestScore) {
bestScore = score;
bestMatch = candidate.element;
}
}
return bestScore > 0.3 ? bestMatch : null;
}
};
function scrollToElement(element) {
if (!element) return false;
document.querySelectorAll('.olm-highlight').forEach(el => {
el.classList.remove('olm-highlight');
});
element.classList.add('olm-highlight');
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
setTimeout(() => {
element.classList.remove('olm-highlight');
}, 3000);
return true;
}
async function processAndAppend(htmlSnippets) {
await ensureBeautifier();
const beautify = window.html_beautify || ((s)=>s);
const options = {
indent_size: 2,
preserve_newlines: true,
max_preserve_newlines: 2,
wrap_line_length: 0,
content_unformatted: ['pre','code','textarea']
};
for (const html of htmlSnippets) {
const container = document.createElement('div');
container.innerHTML = html;
try {
state.originals.push(beautify(container.innerHTML, options));
} catch {}
const segments = [];
let current = document.createElement('div');
const pushCurrentIfAny = () => {
const s = current.innerHTML.trim();
if (s) segments.push(beautify(s, options));
current = document.createElement('div');
};
Array.from(container.childNodes).forEach((node) => {
if (node.nodeType === 1 && node.classList && node.classList.contains('exp')) {
pushCurrentIfAny();
return;
}
current.appendChild(node.cloneNode(true));
});
pushCurrentIfAny();
if (segments.length) {
state.items.push(...segments);
} else {
container.querySelectorAll('.exp').forEach(el => el.remove());
state.items.push(beautify(container.innerHTML, options));
}
}
ui.update();
}
window.addEventListener('message', (ev) => {
const msg = ev.data;
if (!msg || msg.type !== 'OLM_SNIF_CONTENTS') return;
if (!state.firstMeta) state.firstMeta = msg.meta || null;
const decoded = (msg.contents || []).map(utils.safeDecode).filter(Boolean);
if (!decoded.length) return;
processAndAppend(decoded);
}, false);
async function downloadWordFile(event) {
const button = event.target;
const originalText = button.textContent;
button.textContent = 'Đang xử lý...';
button.disabled = true;
try {
const match = window.location.pathname.match(/(\d+)$/);
if (!match || !match[0]) {
alert('Lỗi: Không tìm thấy ID chủ đề (dãy số ở cuối link) trong URL.');
throw new Error('Không tìm thấy ID chủ đề trong pathname.');
}
const id_cate = match[0];
button.textContent = 'Đang lấy link...';
const apiUrl = `https://olm.vn/download-word-for-user?id_cate=${id_cate}&showAns=1&questionNotApproved=0`;
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`Lỗi server OLM: ${response.statusText}`);
}
const data = await response.json();
if (!data || !data.file) {
throw new Error('Response JSON không hợp lệ hoặc không có link file.');
}
const fileUrl = data.file;
button.textContent = 'Đang tải về...';
const link = document.createElement('a');
link.href = fileUrl;
link.target = '_blank';
let filename = fileUrl.split('/').pop();
if (!filename || !filename.includes('.')) {
filename = `olm-answers-${id_cate}.docx`;
}
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error('Lỗi khi tải file Word:', error);
alert(`Đã xảy ra lỗi: ${error.message}`);
} finally {
button.textContent = originalText;
button.disabled = false;
}
}
// BIẾN TOÀN CỤC: Đánh dấu người dùng đã resize
let hasUserResized = false;
const ui = {
el: {},
//resizer
setupResize() {
let isResizing = false;
let startX, startY, startW, startH;
const minW = 280, minH = 160;
const maxW = window.innerWidth * 0.92;
const maxH = window.innerHeight * 0.92;
const startResize = (e) => {
if (e.target.closest('a')) return;
hasUserResized = true; // ĐÁNH DẤU: người dùng đã tự resize
isResizing = true;
this.el.panel.classList.add('resizing');
startX = e.clientX || (e.touches && e.touches[0].clientX);
startY = e.clientY || (e.touches && e.touches[0].clientY);
const rect = this.el.panel.getBoundingClientRect();
startW = rect.width;
startH = rect.height;
document.addEventListener('mousemove', onResize);
document.addEventListener('touchmove', onResize, { passive: false });
document.addEventListener('mouseup', stopResize);
document.addEventListener('touchend', stopResize);
e.preventDefault();
};
const onResize = (e) => {
if (!isResizing) return;
const currentX = e.clientX || (e.touches && e.touches[0].clientX);
const currentY = e.clientY || (e.touches && e.touches[0].clientY);
const newW = Math.max(minW, Math.min(maxW, startW + (currentX - startX)));
const newH = Math.max(minH, Math.min(maxH, startH + (currentY - startY)));
this.el.panel.style.width = `${newW}px`;
this.el.panel.style.height = `${newH}px`;
this.el.panel.style.right = 'auto';
this.el.panel.style.bottom = 'auto';
};
const stopResize = () => {
if (!isResizing) return;
isResizing = false;
this.el.panel.classList.remove('resizing');
// LƯU KÍCH THƯỚC VÀO LOCALSTORAGE
const panel = this.el.panel;
const rect = panel.getBoundingClientRect();
localStorage.setItem('olm-panel-size', JSON.stringify({
w: rect.width,
h: rect.height,
x: rect.left,
y: rect.top
}));
document.removeEventListener('mousemove', onResize);
document.removeEventListener('touchmove', onResize);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener('touchend', stopResize);
};
this.el.resize.addEventListener('mousedown', startResize);
this.el.resize.addEventListener('touchstart', startResize, { passive: false });
},
//drag
setupDrag() {
let isDragging = false;
let startX, startY, initialX, initialY;
const startDrag = (e) => {
if (e.target.classList.contains('olm-mini-resize') || e.target.closest('a')) return;
isDragging = true;
this.el.panel.classList.add('dragging');
startX = e.clientX || (e.touches && e.touches[0].clientX);
startY = e.clientY || (e.touches && e.touches[0].clientY);
const rect = this.el.panel.getBoundingClientRect();
initialX = rect.left;
initialY = rect.top;
document.addEventListener('mousemove', onDrag);
document.addEventListener('touchmove', onDrag, { passive: false });
document.addEventListener('mouseup', stopDrag);
document.addEventListener('touchend', stopDrag);
if (!e.target.closest('a')) {
e.preventDefault();
}
};
const onDrag = (e) => {
if (!isDragging) return;
const currentX = e.clientX || (e.touches && e.touches[0].clientX);
const currentY = e.clientY || (e.touches && e.touches[0].clientY);
const dx = currentX - startX;
const dy = currentY - startY;
let newX = initialX + dx;
let newY = initialY + dy;
const maxX = window.innerWidth - this.el.panel.offsetWidth;
const maxY = window.innerHeight - this.el.panel.offsetHeight;
newX = Math.max(10, Math.min(newX, maxX - 10));
newY = Math.max(10, Math.min(newY, maxY - 10));
this.el.panel.style.left = `${newX}px`;
this.el.panel.style.top = `${newY}px`;
this.el.panel.style.right = 'auto';
this.el.panel.style.bottom = 'auto';
};
const stopDrag = () => {
if (!isDragging) return;
isDragging = false;
this.el.panel.classList.remove('dragging');
// LƯU VỊ TRÍ KHI KÉO
const panel = this.el.panel;
const rect = panel.getBoundingClientRect();
localStorage.setItem('olm-panel-size', JSON.stringify({
w: rect.width,
h: rect.height,
x: rect.left,
y: rect.top
}));
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('touchmove', onDrag);
document.removeEventListener('mouseup', stopDrag);
document.removeEventListener('touchend', stopDrag);
};
this.el.header.addEventListener('mousedown', startDrag);
this.el.header.addEventListener('touchstart', startDrag, { passive: false });
},
init() {
const style = document.createElement('style');
style.textContent = `
@keyframes gradient-animation {
0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; }
}
@keyframes answer-gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
#olm-answers-container {
position: fixed; top: 10px; right: 10px; width: 450px; max-height: 90vh;
border: 1px solid #cccccc; border-radius: 8px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
z-index: 10000; display: flex; flex-direction: column;
font-size: 16px; overflow: hidden;
color: #333;
background: linear-gradient(270deg, #e0f7fa, #d1c4e9, #fce4ec, #e0f7fa);
background-size: 400% 400%; animation: gradient-animation 4s ease infinite;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
touch-action: none;
user-select: none;
}
#olm-answers-container.dragging,
#olm-answers-container.resizing {
transition: none !important;
cursor: grabbing !important;
}
.olm-answers-header {
padding: 10px 15px; background-color: rgba(255, 255, 255, 0.4); border-bottom: 1px solid #cccccc;
cursor: move !important; user-select: none; font-weight: bold; text-align: center;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
color: #000;
}
#olm-answers-content {
padding: 10px; margin: 0; flex-grow: 1; overflow-y: auto; background-color: rgba(255, 255, 255, 0.8);
}
#olm-answers-footer {
padding: 8px; background-color: rgba(255, 255, 255, 0.4); border-top: 1px solid #cccccc; text-align: center;
backdrop-filter: blur(8px);
}
#olm-dl-word {
padding: 8px 16px; border: none; background-color: #28a745; color: white;
border-radius: 6px; cursor: pointer; font-weight: bold; transition: background-color 0.2s, transform 0.1s;
font-size: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
#olm-dl-word:hover { background-color: #218838; transform: translateY(-1px); }
#olm-dl-word:disabled { background-color: #9e9e9e; cursor: not-allowed; transform: none; }
#olm-answers-content::-webkit-scrollbar { width: 8px; }
#olm-answers-content::-webkit-scrollbar-track { background: #f1f1f1; }
#olm-answers-content::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
#olm-answers-content::-webkit-scrollbar-thumb:hover { background: #555; }
.olm-highlight {
background-color: rgba(255, 255, 0, 0.4) !important;
outline: 2px solid #ffd700 !important;
outline-offset: 2px;
transition: all 0.3s ease;
}
.olm-item{
background:#f8f8f8;
border-left:3px solid #007bff;
padding:10px;
border-radius:0 6px 6px 0;
margin-bottom:12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.olm-item .question-content {
font-weight: bold;
color: #0056b3;
margin-bottom: 8px;
}
.olm-item .content-container {
padding: 8px 0 0 15px;
border-top: 1px dashed #e0e0e0;
margin-top: 8px;
}
.olm-item .content-container[data-type="solution"] {
color: #28a745;
}
.olm-item .content-container[data-type="not-found"] {
color: #777; font-style: italic;
}
.olm-item .content-container[data-type="answer"] {
font-size: 1.35em !important;
font-weight: 800 !important;
color: white !important;
padding: 12px 16px !important;
border-radius: 8px !important;
margin: 10px 0 !important;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
background: linear-gradient(45deg, #56ab2f, #4facfe, #00c9ff, #56ab2f);
background-size: 300% 300% !important;
animation: answer-gradient 6s ease infinite !important;
box-shadow: 0 4px 12px rgba(86, 171, 47, 0.3) !important;
border: 1px solid rgba(255,255,255,0.3);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
.olm-item img{max-width:100%;height:auto;}
.olm-item li.correctAnswer{color:#48bb78;font-weight:600}
.olm-item .fill-answer{color:#48bb78}
.olm-item [dir="ltr"]{cursor:pointer;transition:background 0.2s;padding:2px;border-radius:3px}
.olm-item [dir="ltr"]:hover{background:#d0eaff}
.olm-item ol.quiz-list{margin:0 0 6px 20px;padding:0}
.olm-item ol.quiz-list li{margin:0;padding:0}
.olm-mini-resize {
position: absolute;
bottom: 0; right: 0;
width: 36px; height: 36px;
cursor: nwse-resize;
opacity: 0.7;
touch-action: none;
-webkit-user-select: none;
user-select: none;
border-bottom-right-radius: 8px;
overflow: hidden;
}
.olm-mini-resize::before,
.olm-mini-resize::after {
content: '';
position: absolute;
width: 36px; height: 1px;
background: #999;
border-radius: 8px;
transition: background 0.2s;
}
.olm-mini-resize::before {
bottom: 12px; right: 1px;
transform: rotate(135deg);
}
.olm-mini-resize::after {
bottom: 1px; right: 1px;
transform: rotate(135deg);
}
.olm-mini-resize:hover::before,
.olm-mini-resize:hover::after {
background: #555;
}
.olm-mini-resize:hover,
.olm-mini-resize:active {
opacity: 1;
}
#olm-toggle-float {
position: fixed;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
background: linear-gradient(135deg, #9c27b0, #673ab7, #311b92, #000000, #673ab7, #9c27b0);
background-size: 300% 300%;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
cursor: pointer;
z-index: 9999;
transition: all 0.3s ease;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
user-select: none;
animation: gradient-flow 4s ease infinite;
}
#olm-toggle-float:hover {
transform: scale(1.1) translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
background-size: 400% 400%;
}
#olm-toggle-float:active {
transform: scale(0.98);
}
.olm-author-link {
color: transparent;
background: linear-gradient(90deg, #9c27b0, #673ab7, #311b92, #000000, #673ab7, #9c27b0);
background-size: 300% 300%;
-webkit-background-clip: text;
background-clip: text;
font-weight: bold;
text-decoration: none;
animation: gradient-flow 4s ease infinite;
transition: transform 0.2s ease;
}
.olm-author-link:hover {
transform: scale(1.05);
}
@keyframes gradient-flow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
`;
document.head.appendChild(style);
const panel = document.createElement('div');
panel.id = 'olm-answers-container';
panel.innerHTML = `
<div class="olm-answers-header">
Code by <a href="https://guns.lol/druslla" target="_blank" class="olm-author-link">Bảo Ngọc & Druslla</a> (Shift phải/nút để ẩn/hiện)
</div>
<div id="olm-answers-content"><div>Không có dữ liệu. Bắt đầu một bài tập hoặc trắc nghiệm.</div></div>
<div id="olm-answers-footer">
<button id="olm-dl-word">Tải đáp án (Word)</button>
</div>
`;
document.body.appendChild(panel);
const toggleBtn = document.createElement('div');
toggleBtn.id = 'olm-toggle-float';
document.body.appendChild(toggleBtn);
const resize = document.createElement('div');
resize.className = 'olm-mini-resize';
panel.appendChild(resize);
const saved = localStorage.getItem('olm-panel-size');
if (saved) {
try {
const { w, h, x, y } = JSON.parse(saved);
panel.style.width = `${w}px`;
panel.style.height = `${h}px`;
panel.style.left = `${x}px`;
panel.style.top = `${y}px`;
panel.style.right = 'auto';
panel.style.bottom = 'auto';
hasUserResized = true;
} catch(e) {}
}
window.addEventListener('keydown', (event) => {
if (event.code === 'ShiftRight') {
ui.toggleVisibility();
}
});
this.el = {
panel,
header: panel.querySelector('.olm-answers-header'),
body: panel.querySelector('#olm-answers-content'),
dlWord: panel.querySelector('#olm-dl-word'),
resize,
toggleBtn
};
this.el.toggleBtn.addEventListener('click', () => {
ui.toggleVisibility();
});
this.updateToggleIcon = () => {
this.el.toggleBtn.innerHTML = state.isVisible
? '<span style="font-size:18px;">👀</span>'
: '<span style="font-size:18px;">🎃</span>';
};
this.updateToggleIcon();
this.setupDrag();
this.setupResize();
this.el.dlWord.onclick = downloadWordFile;
this.el.body.addEventListener('click', (e) => {
const ltrEl = e.target.closest('[dir="ltr"]');
if (!ltrEl) return;
const itemEl = ltrEl.closest('.olm-item');
if (!itemEl) return;
e.stopPropagation();
const searchText = ltrEl.textContent.trim();
if (!searchText) return;
const matchEl = fuzzyMatch.findBestMatch(searchText);
if (matchEl) {
scrollToElement(matchEl);
} else {
const origBg = ltrEl.style.backgroundColor;
ltrEl.style.backgroundColor = 'rgba(255, 136, 136, 0.3)';
setTimeout(() => { ltrEl.style.backgroundColor = origBg; }, 300);
}
});
const adjustForMobile = () => {
if (window.innerWidth > 768) return;
if (hasUserResized) return;
panel.style.width = `${Math.min(450, window.innerWidth * 0.92)}px`;
panel.style.height = `${window.innerHeight * 0.75}px`;
panel.style.left = `${(window.innerWidth - panel.offsetWidth) / 2}px`;
panel.style.top = `${window.innerHeight * 0.1}px`;
panel.style.right = 'auto';
panel.style.bottom = 'auto';
};
setTimeout(adjustForMobile, 500);
window.addEventListener('resize', adjustForMobile);
window.addEventListener('orientationchange', adjustForMobile);
},
toggleVisibility() {
state.isVisible = !state.isVisible;
this.el.panel.style.display = state.isVisible ? 'flex' : 'none';
this.updateToggleIcon();
if (state.isVisible) {
this.update();
}
},
update() {
if (!this.el.body) return;
if (!state.items.length) {
this.el.body.innerHTML = '<div>Không có dữ liệu. Bắt đầu một bài tập hoặc trắc nghiệm.</div>';
return;
}
const renderSegment = (html) => {
const tmp = document.createElement('div');
tmp.innerHTML = html;
if (!tmp.querySelector('li.correctAnswer')) {
tmp.querySelectorAll('input[data-accept]').forEach(inp => {
const v = inp.getAttribute('data-accept') || '';
const span = document.createElement('span');
span.className = 'fill-answer';
span.textContent = v;
inp.replaceWith(span);
});
}
return tmp.innerHTML;
};
this.el.body.innerHTML = state.items.map((html, i) => `
<div class="olm-item">
<div style="opacity:.7;margin-bottom:4px; font-size: 13px; color: #555;">Câu ${i+1}</div>
${renderSegment(html)}
</div>
`).join('');
ensureMathJax().then(() => {
try {
window.MathJax.typesetPromise && window.MathJax.typesetPromise([this.el.body]);
} catch {}
});
}
};
const onReady = () => ui.init();
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', onReady);
else onReady();
})();