/* Makenotwork — Collections Picker */
'use strict';
/**
* Open a collection picker dropdown anchored to the given element.
* Works on item page, discover cards, and library rows.
*
* @param {string} itemId - The item UUID to add/remove from collections.
* @param {HTMLElement} anchor - The element to anchor the picker to.
* @param {object} [opts] - Options.
* @param {string} [opts.position] - 'below' (default) or 'above'.
*/
function openCollectionPicker(itemId, anchor, opts) {
opts = opts || {};
// Close any existing picker
closeCollectionPicker();
var wrapper = anchor.closest('.collection-picker-anchor') || anchor.parentElement;
wrapper.style.position = 'relative';
var picker = document.createElement('div');
picker.id = 'collection-picker-active';
picker.className = 'collection-picker';
if (opts.position === 'above') {
picker.style.bottom = '100%';
picker.style.top = 'auto';
}
picker.innerHTML = '
Loading...
'
+ '
'
+ '
';
wrapper.appendChild(picker);
picker.dataset.itemId = itemId;
// Attach submit handler via addEventListener (avoids inline handler XSS)
var form = picker.querySelector('.collection-picker-create form');
form.addEventListener('submit', function(e) {
e.preventDefault();
collectionPickerCreate(itemId, form);
});
// Load collections
collectionPickerLoad(itemId, picker);
// Close on outside click (deferred so this click doesn't close it)
setTimeout(function() {
document.addEventListener('click', collectionPickerOutsideClick);
}, 0);
}
function closeCollectionPicker() {
var existing = document.getElementById('collection-picker-active');
if (existing) existing.remove();
document.removeEventListener('click', collectionPickerOutsideClick);
}
function collectionPickerOutsideClick(e) {
var picker = document.getElementById('collection-picker-active');
if (picker && !picker.contains(e.target)) {
// Check if the click target is a save button (don't close if re-clicking the trigger)
if (e.target.closest('[data-collection-trigger]')) return;
closeCollectionPicker();
}
}
function collectionPickerLoad(itemId, picker) {
var list = picker.querySelector('.collection-picker-list');
fetch('/api/collections/for-item/' + itemId, { headers: csrfHeaders() })
.then(function(r) {
if (r.status === 401) {
list.innerHTML = '
';
return;
}
var html = '';
for (var i = 0; i < cols.length; i++) {
var c = cols[i];
html += '';
}
list.innerHTML = html;
})
.catch(function() {
list.innerHTML = '
Failed to load collections.
';
});
}
function collectionPickerToggle(collectionId, itemId, add) {
fetch('/api/collections/' + collectionId + '/items/' + itemId, {
method: add ? 'POST' : 'DELETE',
headers: csrfHeaders()
}).then(function(r) {
if (r.ok) {
showToast(add ? 'Added to collection' : 'Removed from collection', 'info');
collectionPickerUpdateButtons(itemId);
} else {
apiErrorMessage(r, add ? 'Failed to add to collection' : 'Failed to remove from collection')
.then(function(m) { showToast(m, 'error'); });
}
}).catch(function() {
showToast(add ? 'Failed to add to collection' : 'Failed to remove from collection', 'error');
});
}
function collectionPickerCreate(itemId, form) {
var title = form.title.value.trim();
if (!title) return;
var slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
var btn = form.querySelector('button');
btn.disabled = true;
fetch('/api/collections', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({ title: title, slug: slug, description: '', is_public: false })
})
.then(function(r) {
if (!r.ok) return apiErrorMessage(r, 'Failed to create collection').then(function(m) { throw new Error(m); });
return r.json();
})
.then(function(col) {
return fetch('/api/collections/' + col.id + '/items/' + itemId, {
method: 'POST',
headers: csrfHeaders()
}).then(function(r2) {
if (!r2.ok) return apiErrorMessage(r2, 'Failed to add item to collection').then(function(m) { throw new Error(m); });
});
})
.then(function() {
form.title.value = '';
btn.disabled = false;
showToast('Created collection and added item', 'info');
var picker = document.getElementById('collection-picker-active');
if (picker) collectionPickerLoad(itemId, picker);
collectionPickerUpdateButtons(itemId);
})
.catch(function(err) {
btn.disabled = false;
showToast(err.message || 'Failed to create collection', 'error');
});
}
/**
* After add/remove, refresh the saved state of any save buttons for this item.
* Buttons use data-item-id to identify which item they belong to.
*/
function collectionPickerUpdateButtons(itemId) {
fetch('/api/collections/for-item/' + itemId, { headers: csrfHeaders() })
.then(function(r) { return r.json(); })
.then(function(cols) {
var savedCount = 0;
for (var i = 0; i < cols.length; i++) {
if (cols[i].in_collection) savedCount++;
}
var buttons = document.querySelectorAll('[data-collection-trigger][data-item-id="' + itemId + '"]');
for (var j = 0; j < buttons.length; j++) {
var btn = buttons[j];
if (btn.dataset.collectionLabel) {
// Full label button (item page style)
btn.textContent = savedCount > 0
? 'Saved (' + savedCount + ')'
: 'Save to collection';
btn.classList.toggle('saved', savedCount > 0);
}
}
});
}
function collectionEscapeHtml(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}