|
40 | 40 | brandName: config.brandName || '', |
41 | 41 | accentColor: config.accentColor || '', |
42 | 42 | fontFamily: config.fontFamily || '', |
43 | | - topbarStyle: { mode: '', color: '', gradient_from: '', gradient_to: '', gradient_dir: 'to-r' }, |
| 43 | + topbarStyle: { mode: '', color: '', gradient_from: '', gradient_to: '', gradient_dir: 'to-r', image_path: '' }, |
44 | 44 | topbarContent: { mode: 'none', links: [], quote: '' } |
45 | 45 | }; |
46 | 46 |
|
|
91 | 91 | var modeContainer = el.querySelector('#appearance-topbar-mode'); |
92 | 92 | var solidPanel = el.querySelector('#appearance-topbar-solid'); |
93 | 93 | var gradientPanel = el.querySelector('#appearance-topbar-gradient'); |
| 94 | + var imagePanel = el.querySelector('#appearance-topbar-image'); |
| 95 | + |
| 96 | + // Dynamically add "Image" button and upload panel if not in template. |
| 97 | + if (modeContainer && !modeContainer.querySelector('[data-mode="image"]')) { |
| 98 | + var imgBtn = document.createElement('button'); |
| 99 | + imgBtn.type = 'button'; |
| 100 | + imgBtn.className = 'btn-secondary text-xs px-3 py-1.5'; |
| 101 | + imgBtn.setAttribute('data-mode', 'image'); |
| 102 | + imgBtn.textContent = 'Image'; |
| 103 | + modeContainer.appendChild(imgBtn); |
| 104 | + } |
| 105 | + if (!imagePanel && modeContainer) { |
| 106 | + imagePanel = document.createElement('div'); |
| 107 | + imagePanel.id = 'appearance-topbar-image'; |
| 108 | + imagePanel.className = 'hidden space-y-2'; |
| 109 | + imagePanel.innerHTML = |
| 110 | + '<p class="text-xs text-fg-secondary">Upload a background image for the topbar.</p>' + |
| 111 | + '<div class="flex items-center gap-3">' + |
| 112 | + ' <label class="btn-secondary text-xs cursor-pointer">' + |
| 113 | + ' <i class="fa-solid fa-upload mr-1.5"></i>Choose Image' + |
| 114 | + ' <input type="file" accept="image/*" class="hidden" id="topbar-image-input"/>' + |
| 115 | + ' </label>' + |
| 116 | + ' <button type="button" id="topbar-image-remove" class="text-xs text-fg-muted hover:text-rose-400 transition-colors">' + |
| 117 | + ' <i class="fa-solid fa-trash mr-1"></i>Remove' + |
| 118 | + ' </button>' + |
| 119 | + '</div>'; |
| 120 | + // Insert after gradient panel. |
| 121 | + if (gradientPanel) { |
| 122 | + gradientPanel.parentNode.insertBefore(imagePanel, gradientPanel.nextSibling); |
| 123 | + } |
| 124 | + |
| 125 | + // Wire upload handler. |
| 126 | + var imgInput = imagePanel.querySelector('#topbar-image-input'); |
| 127 | + if (imgInput) { |
| 128 | + imgInput.addEventListener('change', function () { |
| 129 | + var file = imgInput.files[0]; |
| 130 | + if (!file) return; |
| 131 | + var form = new FormData(); |
| 132 | + form.append('file', file); |
| 133 | + fetch('/campaigns/' + config.campaignId + '/topbar-image', { |
| 134 | + method: 'POST', body: form, credentials: 'same-origin', |
| 135 | + headers: { 'X-CSRF-Token': Chronicle.getCsrf() } |
| 136 | + }).then(function (res) { |
| 137 | + if (res.ok) { |
| 138 | + Chronicle.notify('Topbar image uploaded — reloading...', 'success'); |
| 139 | + setTimeout(function () { window.location.reload(); }, 600); |
| 140 | + } else { |
| 141 | + Chronicle.notify('Failed to upload image', 'error'); |
| 142 | + } |
| 143 | + }); |
| 144 | + }); |
| 145 | + } |
| 146 | + var imgRemove = imagePanel.querySelector('#topbar-image-remove'); |
| 147 | + if (imgRemove) { |
| 148 | + imgRemove.addEventListener('click', function () { |
| 149 | + Chronicle.apiFetch('/campaigns/' + config.campaignId + '/topbar-image', { method: 'DELETE' }) |
| 150 | + .then(function (res) { |
| 151 | + if (res.ok) { |
| 152 | + draft.topbarStyle.mode = ''; |
| 153 | + draft.topbarStyle.image_path = ''; |
| 154 | + setActiveMode(''); |
| 155 | + updateTopbarPreview(); |
| 156 | + Chronicle.notify('Topbar image removed', 'success'); |
| 157 | + setTimeout(function () { window.location.reload(); }, 600); |
| 158 | + } |
| 159 | + }); |
| 160 | + }); |
| 161 | + } |
| 162 | + } |
94 | 163 | var solidColorInput = el.querySelector('#appearance-topbar-color'); |
95 | 164 | var gradFromInput = el.querySelector('#appearance-topbar-gradient-from'); |
96 | 165 | var gradToInput = el.querySelector('#appearance-topbar-gradient-to'); |
|
609 | 678 | if (gradientPanel) { |
610 | 679 | gradientPanel.classList.toggle('hidden', mode !== 'gradient'); |
611 | 680 | } |
| 681 | + if (imagePanel) { |
| 682 | + imagePanel.classList.toggle('hidden', mode !== 'image'); |
| 683 | + } |
612 | 684 | } |
613 | 685 |
|
614 | 686 | /** |
|
626 | 698 | var dir = GRADIENT_DIR_CSS[draft.topbarStyle.gradient_dir] || 'to right'; |
627 | 699 | previewTopbar.style.background = 'linear-gradient(' + dir + ', ' + draft.topbarStyle.gradient_from + ', ' + draft.topbarStyle.gradient_to + ')'; |
628 | 700 | previewTopbar.style.color = isLightColor(draft.topbarStyle.gradient_from) ? '' : '#f9fafb'; |
| 701 | + } else if (mode === 'image' && draft.topbarStyle.image_path) { |
| 702 | + previewTopbar.style.background = 'url(/media/' + draft.topbarStyle.image_path + ') center/cover no-repeat'; |
| 703 | + previewTopbar.style.color = '#f9fafb'; |
629 | 704 | } else { |
630 | 705 | previewTopbar.style.background = ''; |
631 | 706 | previewTopbar.style.color = ''; |
|
0 commit comments