| Cỡ chữ:   
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <div class="header"> <h1>Glossary Search</h1> <div class="search-container"> <i class="fas fa-search"></i> <input type="text" id="searchInput" placeholder="Tìm kiếm thuật ngữ..."> </div> <button class="btn-add" onclick="handleAddNew()"> <i class="fas fa-plus"></i> Thêm thuật ngữ mới </button> </div> <div class="main-content"> <div class="terms-list" id="termsList"> <div class="loading">Đang tải dữ liệu...</div> </div> <div class="detail-panel" id="detailPanel"> <div class="empty-state"> <i class="fas fa-search"></i> <p>Chọn một thuật ngữ để xem chi tiết</p> </div> </div> </div> </div> <script src="script.js"></script> </body> </html>
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); overflow: hidden; } .header { background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%); padding: 30px; color: white; } .header h1 { font-size: 2.5rem; margin-bottom: 20px; font-weight: 700; } .search-container { position: relative; margin-bottom: 20px; } .search-container input { width: 100%; padding: 12px 12px 12px 45px; border: none; border-radius: 8px; font-size: 16px; outline: none; transition: all 0.3s; } .search-container input:focus { box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); } .search-container i { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: #a0aec0; } .btn-add { background: #48bb78; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 16px; display: inline-flex; align-items: center; gap: 8px; transition: all 0.3s; } .btn-add:hover { background: #38a169; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(72, 187, 120, 0.4); } .main-content { display: flex; min-height: 500px; } .terms-list { width: 45%; border-right: 2px solid #e2e8f0; overflow-y: auto; max-height: 600px; } .term-item { padding: 15px 20px; border-bottom: 1px solid #e2e8f0; cursor: pointer; transition: all 0.3s; display: flex; justify-content: space-between; align-items: center; } .term-item:hover { background: #f7fafc; } .term-item.active { background: #fed7d7; border-left: 4px solid #f56565; } .term-tag { background: #edf2f7; padding: 6px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-weight: 600; } .term-category { padding: 4px 12px; border-radius: 6px; font-size: 12px; font-weight: 700; color: white; } .category-html { background: #f56565; } .category-css { background: #4299e1; } .category-javascript { background: #ecc94b; } .detail-panel { width: 55%; padding: 30px; } .empty-state { text-align: center; color: #a0aec0; padding: 80px 20px; } .empty-state i { font-size: 64px; margin-bottom: 20px; opacity: 0.5; } .action-buttons { display: flex; gap: 10px; margin-bottom: 20px; } .btn { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; display: inline-flex; align-items: center; gap: 8px; transition: all 0.3s; font-weight: 600; } .btn-edit { background: #4299e1; color: white; } .btn-edit:hover { background: #3182ce; } .btn-delete { background: #f56565; color: white; } .btn-delete:hover { background: #e53e3e; } .btn-save { background: #48bb78; color: white; } .btn-save:hover { background: #38a169; } .btn-cancel { background: #718096; color: white; } .btn-cancel:hover { background: #4a5568; } .btn-back { background: transparent; color: #718096; padding: 5px 10px; } .btn-back:hover { background: #edf2f7; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #2d3748; } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px 12px; border: 2px solid #e2e8f0; border-radius: 6px; font-size: 14px; transition: all 0.3s; font-family: inherit; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { border-color: #4299e1; outline: none; box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1); } .form-group textarea { resize: vertical; min-height: 120px; } .detail-view h2 { font-size: 24px; color: #2d3748; margin-bottom: 15px; } .detail-view p { color: #4a5568; line-height: 1.8; font-size: 15px; } .term-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .loading { text-align: center; padding: 40px; color: #718096; } @media (max-width: 768px) { .main-content { flex-direction: column; } .terms-list, .detail-panel { width: 100%; border-right: none; } .terms-list { max-height: 400px; } }
let state = { terms: [], selectedTerm: null, isEditing: false, isAdding: false, searchQuery: '', editForm: { tag: '', category: 'html', title: '', description: '' } }; async function init() { await loadTerms(); setupEventListeners(); render(); } async function loadTerms() { try { let result = await window.storage.get('glossary-terms'); if (result && result.value) { state.terms = JSON.parse(result.value); } else { state.terms = [ { id: '1', tag: '@keyframes', category: 'css', title: '@keyframes', description: 'The @keyframes CSS at-rule controls the intermediate steps in a CSS animation sequence by defining styles for keyframes along the animation sequence.' }, { id: '2', tag: '@media', category: 'css', title: '@media', description: 'The @media CSS at-rule can be used to apply part of a style sheet based on the result of one or more media queries.' }, { id: '3', tag: '<a>', category: 'html', title: 'HTML <a> element', description: 'The HTML <a> element (which stands for "anchor") is used to create a hyperlink on a webpage. Clicking on the link will take users to another location on the internet (such as another website), or a different page on the same website.' }, { id: '4', tag: '<audio>', category: 'html', title: 'HTML <audio> element', description: 'The HTML <audio> element is used to embed sound content in documents. It may contain one or more audio sources.' }, { id: '5', tag: '<b>', category: 'html', title: 'HTML <b> element', description: 'The HTML <b> element is used to draw attention to text without indicating importance, seriousness, or emphasis.' }, { id: '6', tag: '<body>', category: 'html', title: 'HTML <body> element', description: 'The HTML <body> element represents the content of an HTML document. There can be only one <body> element in a document.' }, { id: '7', tag: '<br>', category: 'html', title: 'HTML <br> element', description: 'The HTML <br> element produces a line break in text. It is useful for writing addresses or poems where line breaks are significant.' } ]; await saveTerms(); } } catch (error) { console.error('Error loading terms:', error); state.terms = []; } } async function saveTerms() { try { await window.storage.set('glossary-terms', JSON.stringify(state.terms)); } catch (error) { console.error('Error saving terms:', error); alert('Lỗi khi lưu dữ liệu!'); } } function setupEventListeners() { document.getElementById('searchInput').addEventListener('input', function(e) { state.searchQuery = e.target.value; render(); }); } function getFilteredTerms() { let query = state.searchQuery.toLowerCase(); return state.terms.filter(function(term) { return term.tag.toLowerCase().includes(query) || term.title.toLowerCase().includes(query) || term.description.toLowerCase().includes(query); }); } function render() { renderTermsList(); renderDetailPanel(); } function renderTermsList() { let termsList = document.getElementById('termsList'); let filteredTerms = getFilteredTerms(); if (filteredTerms.length === 0) { termsList.innerHTML = '<div class="empty-state"><p>Không tìm thấy kết quả</p></div>'; return; } termsList.innerHTML = filteredTerms.map(function(term) { let isActive = state.selectedTerm && state.selectedTerm.id === term.id; return '<div class="term-item ' + (isActive ? 'active' : '') + '" onclick="selectTerm(\'' + term.id + '\')">' + '<span class="term-tag">' + escapeHtml(term.tag) + '</span>' + '<span class="term-category category-' + term.category + '">' + term.category.toUpperCase() + '</span>' + '</div>'; }).join(''); } function renderDetailPanel() { let detailPanel = document.getElementById('detailPanel'); if (!state.selectedTerm && !state.isAdding) { detailPanel.innerHTML = '<div class="empty-state">' + '<i class="fas fa-search"></i>' + '<p>Chọn một thuật ngữ để xem chi tiết</p>' + '</div>'; return; } if (state.isEditing || state.isAdding) { detailPanel.innerHTML = renderEditForm(); } else { detailPanel.innerHTML = renderDetailView(); } } function renderEditForm() { let isHtmlSelected = state.editForm.category === 'html' ? 'selected' : ''; let isCssSelected = state.editForm.category === 'css' ? 'selected' : ''; let isJsSelected = state.editForm.category === 'javascript' ? 'selected' : ''; return '<h2>' + (state.isAdding ? 'Thêm thuật ngữ mới' : 'Chỉnh sửa thuật ngữ') + '</h2>' + '<form onsubmit="handleSave(event)">' + '<div class="form-group">' + '<label>Tag/Thẻ *</label>' + '<input type="text" id="inputTag" value="' + escapeHtml(state.editForm.tag) + '" placeholder="Ví dụ: <div>, @media" required>' + '</div>' + '<div class="form-group">' + '<label>Danh mục *</label>' + '<select id="inputCategory" required>' + '<option value="html" ' + isHtmlSelected + '>HTML</option>' + '<option value="css" ' + isCssSelected + '>CSS</option>' + '<option value="javascript" ' + isJsSelected + '>JavaScript</option>' + '</select>' + '</div>' + '<div class="form-group">' + '<label>Tiêu đề *</label>' + '<input type="text" id="inputTitle" value="' + escapeHtml(state.editForm.title) + '" placeholder="Tiêu đề của thuật ngữ" required>' + '</div>' + '<div class="form-group">' + '<label>Mô tả *</label>' + '<textarea id="inputDescription" placeholder="Mô tả chi tiết về thuật ngữ" required>' + escapeHtml(state.editForm.description) + '</textarea>' + '</div>' + '<div class="action-buttons">' + '<button type="submit" class="btn btn-save">' + '<i class="fas fa-save"></i> Lưu' + '</button>' + '<button type="button" class="btn btn-cancel" onclick="handleCancel()">' + '<i class="fas fa-times"></i> Hủy' + '</button>' + '</div>' + '</form>'; } function renderDetailView() { return '<div class="action-buttons">' + '<button class="btn btn-back" onclick="handleBack()">' + '<i class="fas fa-chevron-left"></i> Quay lại' + '</button>' + '<button class="btn btn-edit" onclick="handleEdit()">' + '<i class="fas fa-edit"></i> Sửa' + '</button>' + '<button class="btn btn-delete" onclick="handleDelete()">' + '<i class="fas fa-trash"></i> Xóa' + '</button>' + '</div>' + '<div class="detail-view">' + '<div class="term-header">' + '<span class="term-tag">' + escapeHtml(state.selectedTerm.tag) + '</span>' + '<span class="term-category category-' + state.selectedTerm.category + '">' + state.selectedTerm.category.toUpperCase() + '</span>' + '</div>' + '<h2>' + escapeHtml(state.selectedTerm.title) + '</h2>' + '<p>' + escapeHtml(state.selectedTerm.description) + '</p>' + '</div>'; } function selectTerm(id) { let term = state.terms.find(function(t) { return t.id === id; }); state.selectedTerm = term; state.isEditing = false; state.isAdding = false; state.editForm = { tag: term.tag, category: term.category, title: term.title, description: term.description }; render(); } function handleAddNew() { state.isAdding = true; state.isEditing = false; state.selectedTerm = null; state.editForm = { tag: '', category: 'html', title: '', description: '' }; render(); } function handleEdit() { state.isEditing = true; state.isAdding = false; render(); } async function handleSave(event) { event.preventDefault(); let tag = document.getElementById('inputTag').value.trim(); let category = document.getElementById('inputCategory').value; let title = document.getElementById('inputTitle').value.trim(); let description = document.getElementById('inputDescription').value.trim(); if (!tag || !title || !description) { alert('Vui lòng điền đầy đủ thông tin!'); return; } if (state.isAdding) { let newTerm = { id: Date.now().toString(), tag: tag, category: category, title: title, description: description }; state.terms.push(newTerm); state.selectedTerm = newTerm; } else if (state.isEditing && state.selectedTerm) { let index = state.terms.findIndex(function(t) { return t.id === state.selectedTerm.id; }); state.terms[index] = { id: state.selectedTerm.id, tag: tag, category: category, title: title, description: description }; state.selectedTerm = state.terms[index]; } await saveTerms(); state.isEditing = false; state.isAdding = false; render(); } function handleCancel() { state.isEditing = false; state.isAdding = false; if (state.isAdding) { state.selectedTerm = null; } render(); } function handleBack() { state.selectedTerm = null; render(); } async function handleDelete() { if (!confirm('Bạn có chắc chắn muốn xóa thuật ngữ này?')) { return; } state.terms = state.terms.filter(function(t) { return t.id !== state.selectedTerm.id; }); await saveTerms(); state.selectedTerm = null; render(); } function escapeHtml(text) { let div = document.createElement('div'); div.textContent = text; return div.innerHTML; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }