Files
emoji-glossary/script.js
2025-07-12 13:08:58 +07:00

215 lines
8.5 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// --- DOM Elements ---
const emojiGrid = document.getElementById('emoji-grid');
const searchInput = document.getElementById('search-input');
const darkModeToggle = document.getElementById('dark-mode-toggle');
const lightIcon = document.getElementById('theme-toggle-light-icon');
const darkIcon = document.getElementById('theme-toggle-dark-icon');
const modal = document.getElementById('emoji-modal');
const modalContent = document.getElementById('modal-content');
const modalCloseBtn = document.getElementById('modal-close-btn');
const modalEmoji = document.getElementById('modal-emoji');
const modalName = document.getElementById('modal-name');
const modalCategory = document.getElementById('modal-category');
const modalKeywords = document.getElementById('modal-keywords');
const modalCopyBtn = document.getElementById('modal-copy-btn');
const loadMoreBtn = document.getElementById('load-more-btn');
// --- State ---
let allEmojis = [];
let currentEmojiList = [];
let currentPage = 1;
const EMOJIS_PER_PAGE = 100;
// --- Dark Mode Logic ---
const applyTheme = (isDark) => {
document.documentElement.classList.toggle('dark', isDark);
lightIcon.classList.toggle('hidden', !isDark);
darkIcon.classList.toggle('hidden', isDark);
};
darkModeToggle.addEventListener('click', () => {
const isDark = !document.documentElement.classList.contains('dark');
localStorage.setItem('darkMode', isDark);
applyTheme(isDark);
});
// Set initial icon state on load
const initialIsDark = document.documentElement.classList.contains('dark');
applyTheme(initialIsDark);
// --- Data Fetching & Processing ---
const jsonFiles = [
'array.json',
'list.json',
'categories.json',
// Add other files if they should be loaded, keeping the initial set small
].map(file => `src/${file}`);
function parseEmojiData(data) {
let results = [];
if (Array.isArray(data)) {
data.forEach(item => results.push(...parseEmojiData(item)));
} else if (typeof data === 'object' && data !== null) {
if (data.hasOwnProperty('emoji') && data.hasOwnProperty('name')) {
results.push(data);
}
Object.values(data).forEach(value => results.push(...parseEmojiData(value)));
}
return results;
}
Promise.all(jsonFiles.map(file =>
fetch(file)
.then(response => {
if (!response.ok) throw new Error(`Failed to load ${file}`);
return response.json();
})
.catch(error => { console.error(error); return []; })
))
.then(allData => {
const uniqueEmojis = new Map();
const parsedEmojis = allData.flat().flatMap(parseEmojiData);
parsedEmojis.forEach(emoji => {
if (emoji && emoji.name && !uniqueEmojis.has(emoji.name)) {
uniqueEmojis.set(emoji.name, emoji);
}
});
allEmojis = Array.from(uniqueEmojis.values());
currentEmojiList = allEmojis;
displayPage(1);
})
.catch(error => {
console.error('Error processing emoji data:', error);
emojiGrid.innerHTML = '<p class="text-red-500 col-span-full text-center">Failed to load emoji data.</p>';
});
// --- UI Rendering ---
function updateLoadMoreButton() {
const end = currentPage * EMOJIS_PER_PAGE;
if (currentEmojiList.length > end) {
loadMoreBtn.classList.remove('hidden');
} else {
loadMoreBtn.classList.add('hidden');
}
}
function displayPage(page) {
currentPage = page;
const start = (page - 1) * EMOJIS_PER_PAGE;
const end = start + EMOJIS_PER_PAGE;
const emojisToDisplay = currentEmojiList.slice(start, end);
if (page === 1) {
emojiGrid.innerHTML = ''; // Clear the grid only for the first page
}
emojisToDisplay.forEach(emoji => {
const emojiCard = document.createElement('div');
emojiCard.className = 'flex flex-col items-center justify-center p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm cursor-pointer transition-transform duration-200 hover:scale-105 hover:shadow-md';
emojiCard.innerHTML = `
<div class="text-4xl">${emoji.emoji}</div>
<div class="mt-2 text-xs text-center text-gray-600 dark:text-gray-300 font-semibold w-full truncate">${emoji.name}</div>
`;
emojiCard.addEventListener('click', () => openModal(emoji));
emojiGrid.appendChild(emojiCard);
});
// Show 'No emojis found' only if the grid is still empty after rendering
if (emojiGrid.innerHTML === '') {
emojiGrid.innerHTML = '<p class="text-gray-500 dark:text-gray-400 col-span-full text-center">No emojis found.</p>';
}
updateLoadMoreButton();
}
loadMoreBtn.addEventListener('click', () => {
displayPage(currentPage + 1);
});
// --- Search Logic ---
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase().trim();
currentEmojiList = allEmojis.filter(emoji => {
const nameMatch = emoji.name.toLowerCase().includes(searchTerm);
const keywordMatch = emoji.keywords && Array.isArray(emoji.keywords) && emoji.keywords.some(k => k.toLowerCase().includes(searchTerm));
const categoryMatch = emoji.category && emoji.category.toLowerCase().includes(searchTerm);
const subcategoryMatch = emoji.subcategory && emoji.subcategory.toLowerCase().includes(searchTerm);
return nameMatch || keywordMatch || categoryMatch || subcategoryMatch;
});
displayPage(1);
});
// --- Modal Logic ---
function openModal(emoji) {
modalEmoji.textContent = emoji.emoji;
modalName.textContent = emoji.name;
modalCategory.textContent = [emoji.category, emoji.subcategory].filter(Boolean).join(' / ');
modalKeywords.innerHTML = '';
const tags = new Set();
if (emoji.keywords && Array.isArray(emoji.keywords)) {
emoji.keywords.forEach(k => tags.add(k));
}
if (emoji.category) tags.add(emoji.category);
if (emoji.subcategory) tags.add(emoji.subcategory);
const tagArray = Array.from(tags);
if (tagArray.length > 0) {
tagArray.forEach(tagText => {
const tag = document.createElement('button');
tag.className = 'px-2 py-1 bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200 rounded-md text-sm hover:bg-gray-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500';
tag.textContent = tagText;
tag.addEventListener('click', () => {
searchInput.value = tagText;
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
closeModal();
});
modalKeywords.appendChild(tag);
});
} else {
modalKeywords.innerHTML = '<p class="text-sm text-gray-500 dark:text-gray-400">No tags available.</p>';
}
modalCopyBtn.onclick = () => copyToClipboard(emoji.emoji);
modal.classList.remove('hidden');
modal.classList.add('flex');
setTimeout(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
}, 10);
}
function closeModal() {
modalContent.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
}, 300);
}
modalCloseBtn.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
// --- Clipboard Logic ---
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
const originalText = modalCopyBtn.textContent;
modalCopyBtn.textContent = 'Copied!';
setTimeout(() => { modalCopyBtn.textContent = originalText; }, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
const originalText = modalCopyBtn.textContent;
modalCopyBtn.textContent = 'Failed!';
setTimeout(() => { modalCopyBtn.textContent = originalText; }, 2000);
});
}
});