@rmdes/indiekit-endpoint-homepage 1.0.18 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locales/en.json +3 -3
- package/package.json +1 -1
- package/views/homepage-identity.njk +290 -40
package/locales/en.json
CHANGED
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"email": { "label": "Email" },
|
|
92
92
|
"keyUrl": { "label": "PGP Key URL", "hint": "URL to your public PGP key" }
|
|
93
93
|
},
|
|
94
|
-
"
|
|
95
|
-
"legend": "
|
|
96
|
-
"
|
|
94
|
+
"categories": {
|
|
95
|
+
"legend": "Site Categories",
|
|
96
|
+
"tags": { "label": "Categories", "hint": "Comma-separated tags for your site (rendered as p-category in your h-card)" }
|
|
97
97
|
},
|
|
98
98
|
"social": {
|
|
99
99
|
"legend": "Social Links",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
4
|
<style>
|
|
5
|
-
.hp-
|
|
5
|
+
.hp-dashboard {
|
|
6
6
|
display: flex;
|
|
7
7
|
flex-direction: column;
|
|
8
8
|
gap: var(--space-xl, 2rem);
|
|
@@ -35,41 +35,125 @@
|
|
|
35
35
|
margin-block-end: var(--space-m, 1rem);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/* Field grid for two-column layouts */
|
|
38
39
|
.hp-field-grid {
|
|
39
40
|
display: grid;
|
|
40
41
|
grid-template-columns: 1fr 1fr;
|
|
41
|
-
gap: var(--space-s, 0.75rem);
|
|
42
|
+
gap: var(--space-m, 1rem) var(--space-s, 0.75rem);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
.hp-field-grid .field--full {
|
|
45
46
|
grid-column: 1 / -1;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
@media (max-width: 640px) {
|
|
50
|
+
.hp-field-grid {
|
|
51
|
+
grid-template-columns: 1fr;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Field styling consistent with edit panels */
|
|
56
|
+
.hp-dashboard .field {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: 0.25rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.hp-dashboard .field__label {
|
|
63
|
+
display: block;
|
|
64
|
+
font: var(--font-caption, 0.75rem/1.4 sans-serif);
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
color: var(--color-on-surface, inherit);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.hp-dashboard .field__input {
|
|
70
|
+
width: 100%;
|
|
71
|
+
padding: var(--space-2xs, 0.375rem) var(--space-xs, 0.5rem);
|
|
72
|
+
border: 1px solid var(--color-outline-variant, #ccc);
|
|
73
|
+
border-radius: var(--border-radius-small, 0.25rem);
|
|
74
|
+
font: var(--font-body, 0.875rem/1.5 sans-serif);
|
|
75
|
+
background: var(--color-background, #fff);
|
|
76
|
+
color: var(--color-on-surface, inherit);
|
|
77
|
+
transition: border-color 0.15s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.hp-dashboard .field__input:focus {
|
|
81
|
+
outline: none;
|
|
82
|
+
border-color: var(--color-primary, #0066cc);
|
|
83
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary, #0066cc) 20%, transparent);
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
.hp-
|
|
58
|
-
|
|
86
|
+
.hp-dashboard textarea.field__input {
|
|
87
|
+
resize: vertical;
|
|
88
|
+
min-height: 4.5rem;
|
|
59
89
|
}
|
|
60
90
|
|
|
91
|
+
.hp-dashboard .field__hint {
|
|
92
|
+
color: var(--color-on-offset, #888);
|
|
93
|
+
font: var(--font-caption, 0.75rem/1.4 sans-serif);
|
|
94
|
+
margin: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Social links */
|
|
61
98
|
.hp-social-header {
|
|
62
99
|
display: grid;
|
|
63
|
-
grid-template-columns: 1fr 2fr 0.7fr 0.8fr auto;
|
|
100
|
+
grid-template-columns: 1.5rem 1fr 2fr 0.7fr 0.8fr auto;
|
|
64
101
|
gap: var(--space-xs, 0.5rem);
|
|
65
|
-
padding: var(--space-xs, 0.5rem) 0;
|
|
66
|
-
font-weight: 600;
|
|
102
|
+
padding: var(--space-xs, 0.5rem) var(--space-xs, 0.5rem);
|
|
67
103
|
font: var(--font-caption, 0.75rem/1.4 sans-serif);
|
|
68
104
|
font-weight: 600;
|
|
69
105
|
color: var(--color-on-offset, #666);
|
|
70
106
|
border-bottom: 2px solid var(--color-outline-variant, #ddd);
|
|
71
107
|
}
|
|
72
108
|
|
|
109
|
+
.hp-social-row {
|
|
110
|
+
display: grid;
|
|
111
|
+
grid-template-columns: 1.5rem 1fr 2fr 0.7fr 0.8fr auto;
|
|
112
|
+
gap: var(--space-xs, 0.5rem);
|
|
113
|
+
align-items: center;
|
|
114
|
+
padding: var(--space-xs, 0.5rem);
|
|
115
|
+
background: var(--color-background, #fff);
|
|
116
|
+
border: 1px solid var(--color-outline-variant, #ddd);
|
|
117
|
+
border-top: none;
|
|
118
|
+
transition: background-color 0.15s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.hp-social-row:first-child {
|
|
122
|
+
border-top: 1px solid var(--color-outline-variant, #ddd);
|
|
123
|
+
border-radius: var(--border-radius-small, 0.25rem) var(--border-radius-small, 0.25rem) 0 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.hp-social-row:last-child {
|
|
127
|
+
border-radius: 0 0 var(--border-radius-small, 0.25rem) var(--border-radius-small, 0.25rem);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.hp-social-row:only-child {
|
|
131
|
+
border-radius: var(--border-radius-small, 0.25rem);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.hp-social-row:hover {
|
|
135
|
+
background: color-mix(in srgb, var(--color-offset, #f5f5f5) 50%, transparent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.hp-social-row.sortable-ghost {
|
|
139
|
+
opacity: 0.4;
|
|
140
|
+
background: var(--color-primary-container, #e6f0ff);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.hp-social-row.sortable-chosen {
|
|
144
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.hp-social-drag {
|
|
148
|
+
cursor: grab;
|
|
149
|
+
color: var(--color-on-offset, #999);
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.hp-social-drag:active { cursor: grabbing; }
|
|
156
|
+
|
|
73
157
|
.hp-social-row input,
|
|
74
158
|
.hp-social-row select {
|
|
75
159
|
width: 100%;
|
|
@@ -79,6 +163,42 @@
|
|
|
79
163
|
font: var(--font-body, 0.875rem/1.5 sans-serif);
|
|
80
164
|
background: var(--color-background, #fff);
|
|
81
165
|
color: var(--color-on-surface, inherit);
|
|
166
|
+
transition: border-color 0.15s;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.hp-social-row input:focus,
|
|
170
|
+
.hp-social-row select:focus {
|
|
171
|
+
outline: none;
|
|
172
|
+
border-color: var(--color-primary, #0066cc);
|
|
173
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary, #0066cc) 20%, transparent);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.hp-social-empty {
|
|
177
|
+
text-align: center;
|
|
178
|
+
padding: var(--space-m, 1rem);
|
|
179
|
+
color: var(--color-on-offset, #888);
|
|
180
|
+
font: var(--font-caption, 0.875rem/1.4 sans-serif);
|
|
181
|
+
background: var(--color-background, #fff);
|
|
182
|
+
border: 1px dashed var(--color-outline-variant, #ddd);
|
|
183
|
+
border-radius: var(--border-radius-small, 0.25rem);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.hp-social-actions {
|
|
187
|
+
display: flex;
|
|
188
|
+
gap: var(--space-xs, 0.5rem);
|
|
189
|
+
align-items: center;
|
|
190
|
+
margin-block-start: var(--space-s, 0.75rem);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@media (max-width: 768px) {
|
|
194
|
+
.hp-social-header { display: none; }
|
|
195
|
+
.hp-social-row {
|
|
196
|
+
grid-template-columns: 1.5rem 1fr;
|
|
197
|
+
gap: var(--space-2xs, 0.25rem);
|
|
198
|
+
padding: var(--space-s, 0.75rem);
|
|
199
|
+
}
|
|
200
|
+
.hp-social-row input::placeholder,
|
|
201
|
+
.hp-social-row select { font-size: 0.8125rem; }
|
|
82
202
|
}
|
|
83
203
|
</style>
|
|
84
204
|
|
|
@@ -95,7 +215,7 @@
|
|
|
95
215
|
</div>
|
|
96
216
|
{% endif %}
|
|
97
217
|
|
|
98
|
-
<form method="post" action="{{ homepageEndpoint }}/save-identity" class="hp-
|
|
218
|
+
<form method="post" action="{{ homepageEndpoint }}/save-identity" class="hp-dashboard" id="identity-form">
|
|
99
219
|
|
|
100
220
|
{# Profile Section #}
|
|
101
221
|
<section class="hp-section">
|
|
@@ -121,7 +241,7 @@
|
|
|
121
241
|
<input class="field__input" type="text" id="identity-pronoun" name="identity-pronoun" value="{{ identity.pronoun or '' }}">
|
|
122
242
|
<p class="field__hint">{{ __("homepageBuilder.identity.profile.pronoun.hint") }}</p>
|
|
123
243
|
</div>
|
|
124
|
-
<div class="field"
|
|
244
|
+
<div class="field"></div>
|
|
125
245
|
<div class="field field--full">
|
|
126
246
|
<label class="field__label" for="identity-bio">{{ __("homepageBuilder.identity.profile.bio.label") }}</label>
|
|
127
247
|
<textarea class="field__input" id="identity-bio" name="identity-bio" rows="3">{{ identity.bio or '' }}</textarea>
|
|
@@ -177,13 +297,13 @@
|
|
|
177
297
|
</div>
|
|
178
298
|
</section>
|
|
179
299
|
|
|
180
|
-
{#
|
|
300
|
+
{# Categories Section #}
|
|
181
301
|
<section class="hp-section">
|
|
182
|
-
<h2>{{ __("homepageBuilder.identity.
|
|
302
|
+
<h2>{{ __("homepageBuilder.identity.categories.legend") }}</h2>
|
|
183
303
|
<div class="field">
|
|
184
|
-
<label class="field__label" for="identity-categories">{{ __("homepageBuilder.identity.
|
|
304
|
+
<label class="field__label" for="identity-categories">{{ __("homepageBuilder.identity.categories.tags.label") }}</label>
|
|
185
305
|
<input class="field__input" type="text" id="identity-categories" name="identity-categories" value="{{ identity.categories | join(', ') if identity.categories else '' }}">
|
|
186
|
-
<p class="field__hint">{{ __("homepageBuilder.identity.
|
|
306
|
+
<p class="field__hint">{{ __("homepageBuilder.identity.categories.tags.hint") }}</p>
|
|
187
307
|
</div>
|
|
188
308
|
</section>
|
|
189
309
|
|
|
@@ -193,6 +313,7 @@
|
|
|
193
313
|
<p class="hp-section__desc">{{ __("homepageBuilder.identity.social.description") }}</p>
|
|
194
314
|
|
|
195
315
|
<div class="hp-social-header">
|
|
316
|
+
<span></span>
|
|
196
317
|
<span>{{ __("homepageBuilder.identity.social.name.label") }}</span>
|
|
197
318
|
<span>{{ __("homepageBuilder.identity.social.url.label") }}</span>
|
|
198
319
|
<span>{{ __("homepageBuilder.identity.social.rel.label") }}</span>
|
|
@@ -200,11 +321,9 @@
|
|
|
200
321
|
<span></span>
|
|
201
322
|
</div>
|
|
202
323
|
|
|
203
|
-
<div id="social-links-container">
|
|
204
|
-
{# Populated by JS from identity.social data #}
|
|
205
|
-
</div>
|
|
324
|
+
<div id="social-links-container"></div>
|
|
206
325
|
|
|
207
|
-
<div
|
|
326
|
+
<div class="hp-social-actions">
|
|
208
327
|
<button type="button" class="button button--small" id="add-social-link">Add Social Link</button>
|
|
209
328
|
</div>
|
|
210
329
|
</section>
|
|
@@ -219,16 +338,128 @@
|
|
|
219
338
|
var socialData = {{ identity.social | dump | default('[]') | safe }};
|
|
220
339
|
var socialContainer = document.getElementById('social-links-container');
|
|
221
340
|
|
|
341
|
+
// Icon options grouped for the select dropdown
|
|
342
|
+
var iconOptions = [
|
|
343
|
+
{ value: '', label: 'None', group: '' },
|
|
344
|
+
{ value: 'github', label: 'GitHub', group: 'Code' },
|
|
345
|
+
{ value: 'gitlab', label: 'GitLab', group: 'Code' },
|
|
346
|
+
{ value: 'forgejo', label: 'Forgejo', group: 'Code' },
|
|
347
|
+
{ value: 'codeberg', label: 'Codeberg', group: 'Code' },
|
|
348
|
+
{ value: 'sourcehut', label: 'SourceHut', group: 'Code' },
|
|
349
|
+
{ value: 'linkedin', label: 'LinkedIn', group: 'Social' },
|
|
350
|
+
{ value: 'bluesky', label: 'Bluesky', group: 'Social' },
|
|
351
|
+
{ value: 'mastodon', label: 'Mastodon', group: 'Social' },
|
|
352
|
+
{ value: 'activitypub', label: 'ActivityPub', group: 'Social' },
|
|
353
|
+
{ value: 'pixelfed', label: 'PixelFed', group: 'Social' },
|
|
354
|
+
{ value: 'twitter', label: 'X / Twitter', group: 'Social' },
|
|
355
|
+
{ value: 'facebook', label: 'Facebook', group: 'Social' },
|
|
356
|
+
{ value: 'instagram', label: 'Instagram', group: 'Social' },
|
|
357
|
+
{ value: 'threads', label: 'Threads', group: 'Social' },
|
|
358
|
+
{ value: 'reddit', label: 'Reddit', group: 'Social' },
|
|
359
|
+
{ value: 'hackernews', label: 'Hacker News', group: 'Social' },
|
|
360
|
+
{ value: 'indieweb', label: 'IndieWeb', group: 'Social' },
|
|
361
|
+
{ value: 'youtube', label: 'YouTube', group: 'Content' },
|
|
362
|
+
{ value: 'twitch', label: 'Twitch', group: 'Content' },
|
|
363
|
+
{ value: 'flickr', label: 'Flickr', group: 'Content' },
|
|
364
|
+
{ value: 'spotify', label: 'Spotify', group: 'Content' },
|
|
365
|
+
{ value: 'bandcamp', label: 'Bandcamp', group: 'Content' },
|
|
366
|
+
{ value: 'soundcloud', label: 'SoundCloud', group: 'Content' },
|
|
367
|
+
{ value: 'funkwhale', label: 'Funkwhale', group: 'Content' },
|
|
368
|
+
{ value: 'lastfm', label: 'Last.fm', group: 'Content' },
|
|
369
|
+
{ value: 'peertube', label: 'PeerTube', group: 'Content' },
|
|
370
|
+
{ value: 'bookwyrm', label: 'BookWyrm', group: 'Content' },
|
|
371
|
+
{ value: 'matrix', label: 'Matrix', group: 'Messaging' },
|
|
372
|
+
{ value: 'discord', label: 'Discord', group: 'Messaging' },
|
|
373
|
+
{ value: 'signal', label: 'Signal', group: 'Messaging' },
|
|
374
|
+
{ value: 'telegram', label: 'Telegram', group: 'Messaging' },
|
|
375
|
+
{ value: 'xmpp', label: 'XMPP', group: 'Messaging' },
|
|
376
|
+
{ value: 'rss', label: 'RSS', group: 'Other' },
|
|
377
|
+
{ value: 'email', label: 'Email', group: 'Other' },
|
|
378
|
+
{ value: 'keybase', label: 'Keybase', group: 'Other' },
|
|
379
|
+
{ value: 'orcid', label: 'ORCID', group: 'Other' },
|
|
380
|
+
{ value: 'website', label: 'Website', group: 'Other' }
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Sync DOM input values back into socialData.
|
|
385
|
+
* Must be called before any operation that triggers renderSocialLinks().
|
|
386
|
+
*/
|
|
387
|
+
function syncSocialData() {
|
|
388
|
+
var rows = socialContainer.querySelectorAll('.hp-social-row');
|
|
389
|
+
rows.forEach(function(row, index) {
|
|
390
|
+
if (index >= socialData.length) return;
|
|
391
|
+
var inputs = row.querySelectorAll('input');
|
|
392
|
+
var select = row.querySelector('select');
|
|
393
|
+
if (inputs[0]) socialData[index].name = inputs[0].value;
|
|
394
|
+
if (inputs[1]) socialData[index].url = inputs[1].value;
|
|
395
|
+
if (inputs[2]) socialData[index].rel = inputs[2].value;
|
|
396
|
+
if (select) socialData[index].icon = select.value;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function createDragHandle() {
|
|
401
|
+
var drag = document.createElement('span');
|
|
402
|
+
drag.className = 'hp-social-drag drag-handle';
|
|
403
|
+
drag.title = 'Drag to reorder';
|
|
404
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
405
|
+
svg.setAttribute('width', '14');
|
|
406
|
+
svg.setAttribute('height', '14');
|
|
407
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
408
|
+
svg.setAttribute('fill', 'currentColor');
|
|
409
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
410
|
+
path.setAttribute('d', 'M8 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm8-16a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z');
|
|
411
|
+
svg.appendChild(path);
|
|
412
|
+
drag.appendChild(svg);
|
|
413
|
+
return drag;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function buildIconSelect(selectedValue) {
|
|
417
|
+
var select = document.createElement('select');
|
|
418
|
+
var currentGroup = '';
|
|
419
|
+
var optgroup = null;
|
|
420
|
+
|
|
421
|
+
iconOptions.forEach(function(icon) {
|
|
422
|
+
if (icon.group && icon.group !== currentGroup) {
|
|
423
|
+
currentGroup = icon.group;
|
|
424
|
+
optgroup = document.createElement('optgroup');
|
|
425
|
+
optgroup.label = currentGroup;
|
|
426
|
+
select.appendChild(optgroup);
|
|
427
|
+
}
|
|
428
|
+
var opt = document.createElement('option');
|
|
429
|
+
opt.value = icon.value;
|
|
430
|
+
opt.textContent = icon.label;
|
|
431
|
+
if (icon.value === (selectedValue || '')) opt.selected = true;
|
|
432
|
+
if (optgroup && icon.group) {
|
|
433
|
+
optgroup.appendChild(opt);
|
|
434
|
+
} else {
|
|
435
|
+
select.appendChild(opt);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
return select;
|
|
440
|
+
}
|
|
441
|
+
|
|
222
442
|
function renderSocialLinks() {
|
|
223
443
|
socialContainer.textContent = '';
|
|
444
|
+
if (socialData.length === 0) {
|
|
445
|
+
var empty = document.createElement('div');
|
|
446
|
+
empty.className = 'hp-social-empty';
|
|
447
|
+
empty.textContent = 'No social links configured. Click "Add Social Link" to add one.';
|
|
448
|
+
socialContainer.appendChild(empty);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
224
451
|
socialData.forEach(function(link, index) {
|
|
225
452
|
socialContainer.appendChild(createSocialRow(link, index));
|
|
226
453
|
});
|
|
454
|
+
initSortable();
|
|
227
455
|
}
|
|
228
456
|
|
|
229
457
|
function createSocialRow(link, index) {
|
|
230
458
|
var row = document.createElement('div');
|
|
231
459
|
row.className = 'hp-social-row';
|
|
460
|
+
row.dataset.index = index;
|
|
461
|
+
|
|
462
|
+
row.appendChild(createDragHandle());
|
|
232
463
|
|
|
233
464
|
var nameInput = document.createElement('input');
|
|
234
465
|
nameInput.type = 'text';
|
|
@@ -251,23 +482,8 @@
|
|
|
251
482
|
relInput.placeholder = 'me';
|
|
252
483
|
row.appendChild(relInput);
|
|
253
484
|
|
|
254
|
-
var iconSelect =
|
|
485
|
+
var iconSelect = buildIconSelect(link.icon);
|
|
255
486
|
iconSelect.name = 'social[' + index + '][icon]';
|
|
256
|
-
var icons = [
|
|
257
|
-
{ value: '', label: 'None' },
|
|
258
|
-
{ value: 'github', label: 'GitHub' },
|
|
259
|
-
{ value: 'linkedin', label: 'LinkedIn' },
|
|
260
|
-
{ value: 'bluesky', label: 'Bluesky' },
|
|
261
|
-
{ value: 'mastodon', label: 'Mastodon' },
|
|
262
|
-
{ value: 'activitypub', label: 'ActivityPub' }
|
|
263
|
-
];
|
|
264
|
-
icons.forEach(function(icon) {
|
|
265
|
-
var opt = document.createElement('option');
|
|
266
|
-
opt.value = icon.value;
|
|
267
|
-
opt.textContent = icon.label;
|
|
268
|
-
if (icon.value === (link.icon || '')) opt.selected = true;
|
|
269
|
-
iconSelect.appendChild(opt);
|
|
270
|
-
});
|
|
271
487
|
row.appendChild(iconSelect);
|
|
272
488
|
|
|
273
489
|
var removeBtn = document.createElement('button');
|
|
@@ -275,6 +491,7 @@
|
|
|
275
491
|
removeBtn.className = 'button button--small button--secondary';
|
|
276
492
|
removeBtn.textContent = 'Remove';
|
|
277
493
|
removeBtn.addEventListener('click', function() {
|
|
494
|
+
syncSocialData();
|
|
278
495
|
socialData.splice(index, 1);
|
|
279
496
|
renderSocialLinks();
|
|
280
497
|
});
|
|
@@ -284,10 +501,43 @@
|
|
|
284
501
|
}
|
|
285
502
|
|
|
286
503
|
document.getElementById('add-social-link').addEventListener('click', function() {
|
|
504
|
+
syncSocialData();
|
|
287
505
|
socialData.push({ name: '', url: '', rel: 'me', icon: '' });
|
|
288
506
|
renderSocialLinks();
|
|
289
507
|
});
|
|
290
508
|
|
|
509
|
+
// --- SortableJS for drag-and-drop reordering ---
|
|
510
|
+
function syncOrderFromDom() {
|
|
511
|
+
syncSocialData();
|
|
512
|
+
var rows = socialContainer.querySelectorAll('.hp-social-row');
|
|
513
|
+
var reordered = [];
|
|
514
|
+
rows.forEach(function(row) {
|
|
515
|
+
var idx = parseInt(row.dataset.index, 10);
|
|
516
|
+
if (socialData[idx]) reordered.push(socialData[idx]);
|
|
517
|
+
});
|
|
518
|
+
socialData = reordered;
|
|
519
|
+
// Re-render to update form field name indices
|
|
520
|
+
renderSocialLinks();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function initSortable() {
|
|
524
|
+
if (typeof Sortable === 'undefined') return;
|
|
525
|
+
if (socialContainer._sortable) socialContainer._sortable.destroy();
|
|
526
|
+
socialContainer._sortable = new Sortable(socialContainer, {
|
|
527
|
+
handle: '.drag-handle',
|
|
528
|
+
animation: 150,
|
|
529
|
+
ghostClass: 'sortable-ghost',
|
|
530
|
+
chosenClass: 'sortable-chosen',
|
|
531
|
+
draggable: '.hp-social-row',
|
|
532
|
+
onEnd: syncOrderFromDom
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
var sortableScript = document.createElement('script');
|
|
537
|
+
sortableScript.src = 'https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js';
|
|
538
|
+
sortableScript.onload = function() { initSortable(); };
|
|
539
|
+
document.head.appendChild(sortableScript);
|
|
540
|
+
|
|
291
541
|
renderSocialLinks();
|
|
292
542
|
</script>
|
|
293
543
|
{% endblock %}
|