refactor: enhance generic list rendering and simplify dataset/snippet management

This commit is contained in:
2025-10-19 02:49:20 +03:00
parent 60e8f9a066
commit 690d1c5953
5 changed files with 65 additions and 340 deletions

View File

@@ -278,16 +278,16 @@ async function fetchURLMetadata(url, format) {
// Render dataset list in modal
async function renderDatasetList() {
const datasets = await DatasetStorage.listDatasets();
const listContainer = document.getElementById('dataset-list');
if (datasets.length === 0) {
listContainer.innerHTML = '<div class="dataset-empty">No datasets yet. Click "New Dataset" to create one.</div>';
document.getElementById('dataset-list').innerHTML = '<div class="dataset-empty">No datasets yet. Click "New Dataset" to create one.</div>';
return;
}
// Sort by modified date (most recent first)
datasets.sort((a, b) => new Date(b.modified) - new Date(a.modified));
// Format individual dataset items
const formatDatasetItem = (dataset) => {
let metaText;
if (dataset.source === 'url') {
@@ -318,15 +318,10 @@ async function renderDatasetList() {
`;
};
const html = datasets.map(formatDatasetItem).join('');
listContainer.innerHTML = html;
// Attach click handlers
document.querySelectorAll('.dataset-item').forEach(item => {
item.addEventListener('click', function() {
const datasetId = parseFloat(this.dataset.itemId);
selectDataset(datasetId);
});
// Use generic list renderer
renderGenericList('dataset-list', datasets, formatDatasetItem, selectDataset, {
emptyMessage: 'No datasets yet. Click "New Dataset" to create one.',
itemSelector: '.dataset-item'
});
}

View File

@@ -3,29 +3,56 @@
// Used by both snippet-manager.js and dataset-manager.js
/**
* Generic list rendering function
* Generic list rendering function with support for ghost cards and custom selectors
* @param {string} containerId - ID of container to render list into
* @param {Array} items - Array of items to render
* @param {Function} formatItem - Function that takes item and returns HTML string
* @param {Function} onSelectItem - Callback when item is selected, receives item ID
* @param {string} emptyMessage - Message to show when list is empty
* @param {Object} options - Optional configuration
* - emptyMessage: Message when list is empty
* - ghostCard: HTML string for "create new" card (prepended to list)
* - onGhostCardClick: Callback for ghost card click
* - itemSelector: CSS selector for clickable items (default: '[data-item-id]')
* - ghostCardSelector: CSS selector for ghost card (default: '.ghost-card')
* - parseId: Function to parse ID from string (default: parseFloat)
*/
function renderGenericList(containerId, items, formatItem, onSelectItem, emptyMessage = 'No items found') {
function renderGenericList(containerId, items, formatItem, onSelectItem, options = {}) {
const {
emptyMessage = 'No items found',
ghostCard = null,
onGhostCardClick = null,
itemSelector = '[data-item-id]',
ghostCardSelector = '.ghost-card',
parseId = parseFloat
} = options;
const container = document.getElementById(containerId);
if (!container) return;
if (items.length === 0) {
if (items.length === 0 && !ghostCard) {
container.innerHTML = `<div class="list-empty">${emptyMessage}</div>`;
return;
}
const html = items.map(formatItem).join('');
container.innerHTML = html;
// Render ghost card + items
const itemsHtml = items.map(formatItem).join('');
container.innerHTML = (ghostCard || '') + itemsHtml;
// Attach click handler to ghost card
if (ghostCard && onGhostCardClick) {
const ghostElement = container.querySelector(ghostCardSelector);
if (ghostElement) {
ghostElement.addEventListener('click', onGhostCardClick);
}
}
// Attach click handlers to regular items
container.querySelectorAll(itemSelector).forEach(item => {
// Skip ghost cards
if (item.matches(ghostCardSelector)) return;
// Attach click handlers to items
container.querySelectorAll('[data-item-id]').forEach(item => {
item.addEventListener('click', function() {
const itemId = this.dataset.itemId;
const itemId = parseId(this.dataset.itemId);
onSelectItem(itemId);
});
});

View File

@@ -315,42 +315,28 @@ function renderSnippetList(searchQuery = null) {
}
const snippets = SnippetStorage.listSnippets(null, null, searchQuery);
const snippetList = document.querySelector('.snippet-list');
const placeholder = document.querySelector('.placeholder');
// Handle empty state with placeholder
if (snippets.length === 0) {
snippetList.innerHTML = '';
document.querySelector('.snippet-list').innerHTML = '';
placeholder.style.display = 'block';
// Show different message for search vs empty state
if (searchQuery && searchQuery.trim()) {
placeholder.textContent = 'No snippets match your search';
} else {
placeholder.textContent = 'No snippets found';
}
placeholder.textContent = searchQuery && searchQuery.trim()
? 'No snippets match your search'
: 'No snippets found';
return;
}
placeholder.style.display = 'none';
const ghostCard = `
<li class="snippet-item ghost-card" id="new-snippet-card">
<div class="snippet-name">+ Create New Snippet</div>
<div class="snippet-date">Click to create</div>
</li>
`;
const currentSort = AppSettings.get('sortBy');
// Use generic formatter
// Format individual snippet items
const formatSnippetItem = (snippet) => {
// Show appropriate date based on current sort
let dateText;
if (currentSort === 'created') {
dateText = formatSnippetDate(snippet.created);
} else {
dateText = formatSnippetDate(snippet.modified);
}
const dateText = currentSort === 'created'
? formatSnippetDate(snippet.created)
: formatSnippetDate(snippet.modified);
// Calculate snippet size
const snippetSize = new Blob([JSON.stringify(snippet)]).size;
@@ -377,11 +363,20 @@ function renderSnippetList(searchQuery = null) {
`;
};
const snippetItems = snippets.map(formatSnippetItem).join('');
snippetList.innerHTML = ghostCard + snippetItems;
// Ghost card for creating new snippets
const ghostCard = `
<li class="snippet-item ghost-card" id="new-snippet-card">
<div class="snippet-name">+ Create New Snippet</div>
<div class="snippet-date">Click to create</div>
</li>
`;
// Re-attach event listeners for snippet selection
attachSnippetEventListeners();
// Use generic list renderer
renderGenericList('snippet-list', snippets, formatSnippetItem, selectSnippet, {
ghostCard: ghostCard,
onGhostCardClick: createNewSnippet,
itemSelector: '.snippet-item'
});
}
// Initialize sort controls
@@ -558,26 +553,6 @@ function clearSelection() {
}
}
// Attach event listeners to snippet items
function attachSnippetEventListeners() {
const snippetItems = document.querySelectorAll('.snippet-item');
snippetItems.forEach(item => {
// Handle ghost card for new snippet creation
if (item.id === 'new-snippet-card') {
item.addEventListener('click', function () {
createNewSnippet();
});
return;
}
// Left click to select
item.addEventListener('click', function () {
const snippetId = parseFloat(this.dataset.itemId);
selectSnippet(snippetId);
});
});
}
// Select and load a snippet into the editor
function selectSnippet(snippetId, updateURL = true) {
const snippet = SnippetStorage.getSnippet(snippetId);