291 lines
14 KiB
HTML
291 lines
14 KiB
HTML
<style>
|
|
.hm-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
height: 28px;
|
|
padding: 0 12px;
|
|
border-radius: 3px;
|
|
font-size: 0.85em;
|
|
font-family: var(--font);
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
box-sizing: border-box;
|
|
line-height: 1;
|
|
}
|
|
</style>
|
|
<div class="container" style="padding-top: 20px;">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:16px;">
|
|
<h3 style="margin:0;">{{ t('hall.user_halls_title', { user: ownerUser.user }) }}</h3>
|
|
@if(isOwner || (session && session.admin))
|
|
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
|
<input type="text" id="uh-new-name" placeholder="{{ t('hall.new_hall_placeholder') }}" style="flex:1;min-width:180px;background:var(--badge-bg);border:1px solid rgba(255,255,255,0.15);color:var(--white);padding:0 10px;height:28px;border-radius:3px;font-family:var(--font);font-size:0.9em;">
|
|
<button id="btn-create-uh" class="hm-btn" style="background:rgba(255,255,255,0.1);color:var(--white);border:1px solid rgba(255,255,255,0.2);font-weight:bold;">{{ t('hall.new_hall_btn') }}</button>
|
|
<span id="uh-create-status" style="font-size:0.8em;color:#888;"></span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div id="hall-manager-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; margin-top: 20px;">
|
|
@include(user-hall-cards)
|
|
</div>
|
|
|
|
@if(!hallsList || !hallsList.length)
|
|
<p style="color:#888;text-align:center;padding:40px 0;">
|
|
@if(isOwner)
|
|
{!! t('hall.no_halls_owner') !!}
|
|
@else
|
|
{{ t('hall.no_halls_guest') }}
|
|
@endif
|
|
</p>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="pagination-container-fluid" @if(typeof hidePagination !=='undefined' && hidePagination) style="display: none;" @endif>
|
|
<div class="pagination-wrapper bottom-pagination fixed-pagination">
|
|
@include(snippets/pagination)
|
|
</div>
|
|
</div>
|
|
|
|
@if(isOwner || (session && session.admin))
|
|
<script>
|
|
(function() {
|
|
var csrf = (window.f0ckSession && window.f0ckSession.csrf_token) || '';
|
|
var i18n = window.f0ckI18n || {};
|
|
var grid = document.getElementById('hall-manager-grid');
|
|
|
|
function toSlug(s) {
|
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
}
|
|
|
|
// ── Create Hall ─────────────────────────────────────────────────────────────
|
|
var newNameInput = document.getElementById('uh-new-name');
|
|
var createStatus = document.getElementById('uh-create-status');
|
|
|
|
var isAdmin = @if(session && session.admin) true @else false @endif;
|
|
var ownerUserId = '{{ ownerUser.id }}';
|
|
var isOwner = @if(isOwner) true @else false @endif;
|
|
|
|
// If admin is managing another user, append user_id query param
|
|
var qs = (isAdmin && !isOwner) ? '?user_id=' + ownerUserId : '';
|
|
|
|
async function createHall() {
|
|
var name = newNameInput.value.trim();
|
|
if (!name) { createStatus.textContent = "{{ t('hall.enter_name_error') }}"; createStatus.style.color = '#f55'; return; }
|
|
var slug = toSlug(name);
|
|
createStatus.textContent = i18n.hall_creating || "{{ t('hall.creating') }}"; createStatus.style.color = '#888';
|
|
try {
|
|
var r = await fetch('/api/v2/me/halls' + qs, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
|
|
body: JSON.stringify({ name: name, slug: slug })
|
|
});
|
|
var d = await r.json();
|
|
if (d.success) {
|
|
createStatus.textContent = i18n.hall_created || '✓ Created!'; createStatus.style.color = 'var(--accent)';
|
|
newNameInput.value = '';
|
|
var card = buildCard(name, slug, d.hall);
|
|
grid.appendChild(card);
|
|
wireCard(card);
|
|
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
setTimeout(function() { createStatus.textContent = ''; }, 2000);
|
|
} else {
|
|
createStatus.textContent = '✗ ' + (d.msg || d.message || 'Error'); createStatus.style.color = '#f55';
|
|
}
|
|
} catch(e) { createStatus.textContent = '✗ Network error'; createStatus.style.color = '#f55'; }
|
|
}
|
|
|
|
document.getElementById('btn-create-uh').addEventListener('click', createHall);
|
|
newNameInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') createHall(); });
|
|
|
|
// ── Card builder (for newly created halls) ──────────────────────────────────
|
|
function buildCard(name, slug, hall) {
|
|
var div = document.createElement('div');
|
|
div.className = 'hall-manager-card';
|
|
div.id = 'uh-card-' + slug;
|
|
div.dataset.slug = slug;
|
|
var ownerUser = '{{ ownerUser.user }}';
|
|
var ownerUserId = '{{ ownerUser.id }}';
|
|
div.dataset.ownerId = ownerUserId;
|
|
div.innerHTML =
|
|
'<div class="hall-manager-image" style="position:relative;height:140px;overflow:hidden;background:#111;cursor:pointer;" title="{{ t('hall.click_upload_hint') }}">' +
|
|
'<img src="/user_hall_image/' + ownerUserId + '/' + slug + '" alt="' + name + '" style="width:100%;height:100%;object-fit:cover;opacity:0.8;">' +
|
|
'<span style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:1.1em;font-weight:700;color:#fff;text-transform:uppercase;letter-spacing:0.05em;text-shadow:0 0 10px rgba(0,0,0,0.9),0 1px 3px rgba(0,0,0,0.8);pointer-events:none;">' + name + '</span>' +
|
|
'<input type="file" class="uh-img-upload" accept="image/*" style="display:none;">' +
|
|
'</div>' +
|
|
'<div style="padding:12px;">' +
|
|
'<div style="margin-bottom:8px;"><label style="font-size:0.8em;color:#888;display:block;">{{ t('common.name') }}</label>' +
|
|
'<input type="text" class="uh-name-input" value="' + name + '" style="width:100%;box-sizing:border-box;background:var(--badge-bg);border:1px solid rgba(255,255,255,0.15);color:var(--white);padding:5px 8px;border-radius:3px;font-family:var(--font);"></div>' +
|
|
'<div style="margin-bottom:8px;"><label style="font-size:0.8em;color:#888;display:block;">{{ t('common.description') }}</label>' +
|
|
'<textarea class="uh-desc-input" style="width:100%;box-sizing:border-box;background:var(--badge-bg);border:1px solid rgba(255,255,255,0.15);color:var(--white);padding:5px 8px;border-radius:3px;font-family:var(--font);resize:vertical;min-height:50px;"></textarea></div>' +
|
|
'<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:4px;">' +
|
|
'<label style="display:flex;align-items:center;gap:5px;cursor:pointer;color:#aaa;font-size:0.85em;"><input type="checkbox" class="uh-private-toggle"> {{ t('common.private') }}</label>' +
|
|
'</div>' +
|
|
'<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">' +
|
|
'<button class="uh-btn-save hm-btn" style="background:var(--accent);color:#000;border:none;font-weight:bold;">{{ t('common.save') }}</button>' +
|
|
'<a href="/user/' + ownerUser + '/hall/' + slug + '" class="hm-btn" style="background:rgba(255,255,255,0.05);color:#aaa;border:1px solid rgba(255,255,255,0.1);">{{ t('common.view') }} →</a>' +
|
|
'<span style="margin-left:4px;font-size:0.8em;color:#666;">' +
|
|
"{{ t('hall.posts') }}".replace('{count}', (hall && hall.total_items || 0)) +
|
|
'</span>' +
|
|
'<button class="uh-btn-delete hm-btn" style="margin-left:auto;background:rgba(200,0,0,0.25);color:#f55;border:1px solid rgba(200,0,0,0.4);">🗑 {{ t('common.delete') }}</button>' +
|
|
'</div>' +
|
|
'<div class="uh-card-status" style="margin-top:6px;font-size:0.8em;color:#888;min-height:1.2em;"></div>' +
|
|
'</div>';
|
|
return div;
|
|
}
|
|
|
|
// ── Wire a card ─────────────────────────────────────────────────────────────
|
|
function wireCard(card) {
|
|
var slug = card.dataset.slug;
|
|
var status = card.querySelector('.uh-card-status');
|
|
var nameInput = card.querySelector('.uh-name-input');
|
|
var descInput = card.querySelector('.uh-desc-input');
|
|
var imgEl = card.querySelector('.hall-manager-image img');
|
|
var imgContainer = card.querySelector('.hall-manager-image');
|
|
var fileInput = card.querySelector('.uh-img-upload');
|
|
var nameOverlay = imgContainer && imgContainer.querySelector('span');
|
|
var privToggle = card.querySelector('.uh-private-toggle');
|
|
|
|
function setStatus(msg, color) {
|
|
if (!status) return;
|
|
status.textContent = msg;
|
|
status.style.color = color || '#aaa';
|
|
}
|
|
|
|
// Live name overlay
|
|
if (nameInput && nameOverlay) {
|
|
nameInput.addEventListener('input', function() { nameOverlay.textContent = nameInput.value; });
|
|
}
|
|
|
|
// Click image → open file picker
|
|
if (imgContainer && fileInput) {
|
|
imgContainer.addEventListener('click', function(e) {
|
|
if (e.target.classList.contains('uh-btn-del-img')) return;
|
|
fileInput.click();
|
|
});
|
|
}
|
|
|
|
// Upload image
|
|
if (fileInput) {
|
|
fileInput.addEventListener('change', async function(e) {
|
|
var file = e.target.files[0];
|
|
if (!file) return;
|
|
setStatus(i18n.uploading || 'Uploading…');
|
|
var fd = new FormData();
|
|
fd.append('file', file);
|
|
try {
|
|
var r = await fetch('/api/v2/me/halls/' + encodeURIComponent(slug) + '/image' + qs, {
|
|
method: 'POST',
|
|
headers: { 'x-csrf-token': csrf },
|
|
body: fd
|
|
});
|
|
var d = await r.json();
|
|
if (d.success) {
|
|
if (imgEl) imgEl.src = '/user_hall_image/' + (card.dataset.ownerId || '') + '/' + slug + '?v=' + Date.now();
|
|
setStatus('✓ ' + (i18n.hall_image_uploaded || 'Image uploaded'), 'var(--accent)');
|
|
// Add remove button if not present
|
|
if (!imgContainer.querySelector('.uh-btn-del-img')) {
|
|
var xBtn = document.createElement('button');
|
|
xBtn.className = 'uh-btn-del-img';
|
|
xBtn.title = 'Remove custom image';
|
|
xBtn.textContent = '✕';
|
|
xBtn.style.cssText = 'position:absolute;top:6px;right:6px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.7);color:#fff;border:1px solid rgba(255,255,255,0.3);border-radius:50%;cursor:pointer;font-size:0.75em;line-height:1;z-index:2;padding:0;';
|
|
imgContainer.appendChild(xBtn);
|
|
wireDelBtn(xBtn);
|
|
}
|
|
} else {
|
|
setStatus('✗ ' + (d.msg || 'Upload failed'), '#f55');
|
|
}
|
|
} catch(e2) { setStatus('✗ Network error', '#f55'); }
|
|
e.target.value = '';
|
|
});
|
|
}
|
|
|
|
// Remove custom image
|
|
function wireDelBtn(btn) {
|
|
btn.addEventListener('click', async function(e) {
|
|
e.stopPropagation();
|
|
setStatus(i18n.hall_removing || 'Removing…');
|
|
try {
|
|
var r = await fetch('/api/v2/me/halls/' + encodeURIComponent(slug) + '/image' + qs, {
|
|
method: 'DELETE',
|
|
headers: { 'x-csrf-token': csrf }
|
|
});
|
|
var d = await r.json();
|
|
if (d.success) {
|
|
if (imgEl) imgEl.src = '/user_hall_image/' + (card.dataset.ownerId || '') + '/' + slug + '?v=' + Date.now();
|
|
btn.remove();
|
|
setStatus('✓ ' + (i18n.hall_image_removed || 'Image removed'), 'var(--accent)');
|
|
} else { setStatus('✗ ' + (d.msg || 'Error'), '#f55'); }
|
|
} catch(e2) { setStatus('✗ Network error', '#f55'); }
|
|
});
|
|
}
|
|
var existingDelBtn = card.querySelector('.uh-btn-del-img');
|
|
if (existingDelBtn) wireDelBtn(existingDelBtn);
|
|
|
|
// Private toggle
|
|
if (privToggle) {
|
|
privToggle.addEventListener('change', function() {
|
|
fetch('/api/v2/me/halls/' + encodeURIComponent(slug) + qs, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
|
|
body: JSON.stringify({ is_private: privToggle.checked })
|
|
}).then(function(r) { return r.json(); }).then(function(d) {
|
|
if (d.success) setStatus(privToggle.checked ? '🔒 Private' : '🌐 Public', 'var(--accent)');
|
|
else { privToggle.checked = !privToggle.checked; setStatus('✗ ' + (d.msg || 'Error'), '#f55'); }
|
|
}).catch(function() { privToggle.checked = !privToggle.checked; setStatus('✗ Network error', '#f55'); });
|
|
});
|
|
}
|
|
|
|
// Save
|
|
var saveBtn = card.querySelector('.uh-btn-save');
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', async function() {
|
|
setStatus(i18n.hall_saving || 'Saving…');
|
|
var newName = nameInput ? nameInput.value.trim() : null;
|
|
var newDesc = descInput ? descInput.value.trim() : null;
|
|
var isPrivate = privToggle ? privToggle.checked : undefined;
|
|
if (!newName) { setStatus('✗ ' + (i18n.hall_name_empty || 'Name cannot be empty'), '#f55'); return; }
|
|
try {
|
|
var r = await fetch('/api/v2/me/halls/' + encodeURIComponent(slug) + qs, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
|
|
body: JSON.stringify({ name: newName, description: newDesc || null, is_private: isPrivate })
|
|
});
|
|
var d = await r.json();
|
|
if (d.success) {
|
|
if (nameOverlay && newName) nameOverlay.textContent = newName;
|
|
setStatus('✓ ' + (i18n.saved || 'Saved'), 'var(--accent)');
|
|
} else { setStatus('✗ ' + (d.msg || 'Error'), '#f55'); }
|
|
} catch(e2) { setStatus('✗ Network error', '#f55'); }
|
|
});
|
|
}
|
|
|
|
// Delete
|
|
var delBtn = card.querySelector('.uh-btn-delete');
|
|
if (delBtn) {
|
|
delBtn.addEventListener('click', async function() {
|
|
if (!confirm("{{ t('hall.delete_confirm') }}")) return;
|
|
setStatus("{{ t('hall.deleting') }}");
|
|
try {
|
|
var r = await fetch('/api/v2/me/halls/' + encodeURIComponent(slug) + qs, {
|
|
method: 'DELETE',
|
|
headers: { 'x-csrf-token': csrf }
|
|
});
|
|
var d = await r.json();
|
|
if (d.success) {
|
|
card.style.transition = 'opacity 0.3s';
|
|
card.style.opacity = '0';
|
|
setTimeout(function() { card.remove(); }, 300);
|
|
} else { setStatus('✗ ' + (d.msg || 'Error'), '#f55'); }
|
|
} catch(e2) { setStatus('✗ Network error', '#f55'); }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Wire all existing cards
|
|
document.querySelectorAll('.hall-manager-card').forEach(wireCard);
|
|
})();
|
|
</script>
|
|
@endif
|