mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
refactor: enhance generic list rendering and simplify dataset/snippet management
This commit is contained in:
@@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user