|
67 | 67 | // Image layers driven by a dropdown (e.g. the project logo). |
68 | 68 | const selectLayers = []; // {field, label, options, el} |
69 | 69 |
|
| 70 | + // One date picker can drive multiple text fields (see datePart in templates.js). |
| 71 | + let datePicker = null; // {label, default} |
| 72 | + |
| 73 | + // Parse a YYYY-MM-DD value as a LOCAL date (avoids UTC-shift to the previous day). |
| 74 | + function parseLocalDate(value) { |
| 75 | + const [y, m, d] = value.split('-').map(Number); |
| 76 | + return new Date(y, m - 1, d); |
| 77 | + } |
| 78 | + const DATE_PARTS = { |
| 79 | + weekday: (dt) => dt.toLocaleDateString('en-US', { weekday: 'long' }) + ',', |
| 80 | + monthday: (dt) => dt.toLocaleDateString('en-US', { month: 'long', day: 'numeric' }), |
| 81 | + }; |
| 82 | + function formatDatePart(value, part) { |
| 83 | + const fn = DATE_PARTS[part]; |
| 84 | + return fn ? fn(parseLocalDate(value)) : value; |
| 85 | + } |
| 86 | + |
70 | 87 | function makeImage(layer) { |
71 | 88 | const img = document.createElement('img'); |
72 | 89 | img.className = 'layer-image'; |
|
92 | 109 | applyFont(span, Object.assign({}, parentFont, child.font), parentColor, parentTitleCase); |
93 | 110 | if (child.editable) { |
94 | 111 | span.dataset.field = child.field; |
95 | | - fields.push({ field: child.field, label: child.label, default: child.default, el: span }); |
| 112 | + fields.push({ field: child.field, label: child.label, default: child.default, el: span, datePart: child.datePart }); |
96 | 113 | } |
97 | 114 | return span; |
98 | 115 | } |
|
149 | 166 | optionalLayers.length = 0; |
150 | 167 | shiftEls.length = 0; |
151 | 168 | selectLayers.length = 0; |
| 169 | + datePicker = null; |
152 | 170 | for (const layer of tpl.layers) { |
| 171 | + if (layer.datePicker) datePicker = layer.datePicker; |
153 | 172 | const el = buildLayer(layer); |
154 | 173 | if (layer.shiftOnCollapse) shiftEls.push(el); |
155 | 174 | if (layer.optional) { |
|
206 | 225 | return wrap; |
207 | 226 | } |
208 | 227 |
|
| 228 | + function makeDatePicker(spec, dateFields) { |
| 229 | + const wrap = document.createElement('label'); |
| 230 | + wrap.className = 'field'; |
| 231 | + const span = document.createElement('span'); |
| 232 | + span.className = 'field-label'; |
| 233 | + span.textContent = spec.label; |
| 234 | + const input = document.createElement('input'); |
| 235 | + input.type = 'date'; |
| 236 | + input.value = spec.default || ''; |
| 237 | + const apply = () => { |
| 238 | + if (!input.value) return; |
| 239 | + for (const f of dateFields) f.el.textContent = formatDatePart(input.value, f.datePart); |
| 240 | + }; |
| 241 | + input.addEventListener('input', apply); |
| 242 | + apply(); // sync spans to the initial date |
| 243 | + wrap.appendChild(span); |
| 244 | + wrap.appendChild(input); |
| 245 | + return wrap; |
| 246 | + } |
| 247 | + |
209 | 248 | function buildControls() { |
210 | 249 | controlsEl.innerHTML = ''; |
211 | 250 | // Dropdowns first (e.g. project logo). |
212 | 251 | for (const sel of selectLayers) controlsEl.appendChild(makeSelect(sel)); |
213 | 252 |
|
| 253 | + // One date picker drives all date-derived fields (Weekday + Date). |
| 254 | + const dateFields = fields.filter((f) => f.datePart); |
| 255 | + if (datePicker && dateFields.length) { |
| 256 | + controlsEl.appendChild(makeDatePicker(datePicker, dateFields)); |
| 257 | + } |
| 258 | + |
214 | 259 | const emittedToggles = new Set(); |
215 | 260 | for (const f of fields) { |
| 261 | + if (f.datePart) continue; // handled by the date picker |
216 | 262 | const opt = optionalLayerForField(f.field); |
217 | 263 | if (opt && !emittedToggles.has(opt)) { |
218 | 264 | emittedToggles.add(opt); |
|
0 commit comments