<!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();
}