@@ -16,6 +16,10 @@ document.addEventListener("DOMContentLoaded", () => {
16
16
const customPlatformNameInput = document . getElementById (
17
17
"custom-platform-name" ,
18
18
) ;
19
+ const iconChoicePlatform = document . getElementById ( "iconChoicePlatform" ) ;
20
+ const iconChoiceEmoji = document . getElementById ( "iconChoiceEmoji" ) ;
21
+ const emojiInputGroup = document . getElementById ( "emoji-input-group" ) ;
22
+ const emojiInput = document . getElementById ( "emoji-input" ) ;
19
23
const usernameInput = document . getElementById ( "username" ) ;
20
24
const urlInput = document . getElementById ( "url" ) ;
21
25
const saveProfileBtn = document . getElementById ( "save-profile-btn" ) ;
@@ -49,7 +53,6 @@ document.addEventListener("DOMContentLoaded", () => {
49
53
urlPrefix : "https://youtube.com/@" ,
50
54
} ,
51
55
custom : { name : "Custom" , icon : "icons/custom.png" , urlPrefix : "" } ,
52
- // TODO: let user choose the icon or emoji for custom fields
53
56
} ;
54
57
55
58
function showStatus ( message , isError = false , duration = 3000 ) {
@@ -77,6 +80,9 @@ document.addEventListener("DOMContentLoaded", () => {
77
80
78
81
function openModal ( profile = null ) {
79
82
profileForm . reset ( ) ;
83
+ iconChoicePlatform . checked = true ; // default to platform icon
84
+ emojiInputGroup . style . display = "none" ; // hide emoji input initially
85
+ emojiInput . required = false ;
80
86
customPlatformGroup . style . display = "none" ;
81
87
profileIdInput . value = "" ;
82
88
@@ -91,6 +97,23 @@ document.addEventListener("DOMContentLoaded", () => {
91
97
if ( profile . platform === "custom" ) {
92
98
customPlatformGroup . style . display = "block" ;
93
99
customPlatformNameInput . value = profile . customPlatformName || "" ;
100
+ customPlatformNameInput . required = true ;
101
+ } else {
102
+ customPlatformGroup . style . display = "none" ; // ensure hidden if not custom
103
+ customPlatformNameInput . required = false ;
104
+ }
105
+ const iconValue = profile . displayIcon || profile . platform ;
106
+ if ( platformDetails [ iconValue ] ) {
107
+ // it's a platform key, check platform radio
108
+ iconChoicePlatform . checked = true ;
109
+ emojiInputGroup . style . display = "none" ;
110
+ emojiInput . required = false ;
111
+ } else {
112
+ // assume it's an emoji
113
+ iconChoiceEmoji . checked = true ;
114
+ emojiInput . value = iconValue ;
115
+ emojiInputGroup . style . display = "block" ;
116
+ emojiInput . required = true ;
94
117
}
95
118
} else {
96
119
modalTitle . textContent = "Add Social Profile" ;
@@ -119,43 +142,61 @@ document.addEventListener("DOMContentLoaded", () => {
119
142
}
120
143
}
121
144
145
+ function createIconElement ( profile ) {
146
+ const iconValue = profile . displayIcon || profile . platform ;
147
+
148
+ if ( platformDetails [ iconValue ] ) {
149
+ const iconPath = getIconPath ( iconValue , profile . customPlatformName ) ;
150
+ const platformName = getPlatformDisplayName (
151
+ iconValue ,
152
+ profile . customPlatformName ,
153
+ ) ;
154
+ const img = document . createElement ( "img" ) ;
155
+ img . src = iconPath ;
156
+ img . alt = platformName ;
157
+ // note: icon size is set by CSS .profile-icon
158
+ return img ;
159
+ } else {
160
+ const span = document . createElement ( "span" ) ;
161
+ span . classList . add ( "profile-emoji-icon" ) ;
162
+ span . textContent = iconValue ;
163
+ span . setAttribute ( "aria-label" , "Emoji icon" ) ;
164
+ return span ;
165
+ }
166
+ }
167
+
122
168
// profile section
123
169
function renderProfileItem ( profile ) {
124
170
const div = document . createElement ( "div" ) ;
125
171
div . classList . add ( "profile-item" ) ;
126
172
div . dataset . id = profile . id ;
127
173
128
- const iconPath = getIconPath ( profile . platform , profile . customPlatformName ) ;
174
+ const iconContainer = document . createElement ( "div" ) ;
175
+ iconContainer . classList . add ( "profile-icon" ) ;
176
+ const iconElement = createIconElement ( profile ) ;
177
+ iconContainer . appendChild ( iconElement ) ;
178
+
129
179
const platformDisplayName = getPlatformDisplayName (
130
180
profile . platform ,
131
181
profile . customPlatformName ,
132
182
) ;
133
183
134
184
div . innerHTML = `
135
- <div class="profile-icon">
136
- <img src="${ iconPath } " alt="${ platformDisplayName } ">
137
- <!-- Or use: <span class="icon-placeholder">L</span> -->
138
- </div>
139
- <div class="profile-info">
140
- <span class="platform-name">${ platformDisplayName } </span>
141
- <span class="username">${ profile . username } </span>
142
- </div>
143
- <div class="profile-actions">
144
- <button class="action-icon copy-btn" title="Copy URL">
145
- <img src="icons/copy.png" alt="Delete" width="16" height="16">
146
- </button>
147
- <button class="action-icon edit-btn" title="Edit Profile">
148
- <img src="icons/edit.png" alt="Delete" width="16" height="16">
149
- </button>
150
- <button class="action-icon delete-btn" title="Delete Profile">
151
- <img src="icons/delete.png" alt="Delete" width="16" height="16">
152
- </button>
153
- </div>
154
- ` ;
155
-
156
- // event listener for actions
185
+ <div class="profile-info">
186
+ <span class="platform-name">${ platformDisplayName } </span>
187
+ <span class="username">${ profile . username } </span>
188
+ </div>
189
+ <div class="profile-actions">
190
+ <button class="action-icon copy-btn" title="Copy URL"><img src="icons/copy.png" alt="Copy" width="16" height="16"></button>
191
+ <button class="action-icon edit-btn" title="Edit Profile"><img src="icons/edit.png" alt="Edit" width="16" height="16"></button>
192
+ <button class="action-icon delete-btn" title="Delete Profile"><img src="icons/delete.png" alt="Delete" width="16" height="16"></button>
193
+ </div>
194
+ ` ;
195
+
196
+ div . prepend ( iconContainer ) ;
197
+
157
198
div . querySelector ( ".copy-btn" ) . addEventListener ( "click" , ( e ) => {
158
- e . stopPropagation ( ) ; // prevent triggering other listeners if nested
199
+ e . stopPropagation ( ) ;
159
200
navigator . clipboard
160
201
. writeText ( profile . url )
161
202
. then ( ( ) => {
@@ -297,10 +338,51 @@ document.addEventListener("DOMContentLoaded", () => {
297
338
// update URL prefix when platform changes
298
339
platformSelect . addEventListener ( "change" , handlePlatformChange ) ;
299
340
300
- // handle form (add or edit)
341
+ iconChoicePlatform . addEventListener ( "change" , ( ) => {
342
+ if ( iconChoicePlatform . checked ) {
343
+ emojiInputGroup . style . display = "none" ;
344
+ emojiInput . required = false ;
345
+ }
346
+ } ) ;
347
+ iconChoiceEmoji . addEventListener ( "change" , ( ) => {
348
+ if ( iconChoiceEmoji . checked ) {
349
+ emojiInputGroup . style . display = "block" ;
350
+ emojiInput . required = true ;
351
+ emojiInput . focus ( ) ;
352
+ }
353
+ } ) ;
354
+
301
355
profileForm . addEventListener ( "submit" , async ( e ) => {
302
356
e . preventDefault ( ) ;
303
357
358
+ let chosenDisplayIcon ;
359
+ if ( iconChoicePlatform . checked ) {
360
+ chosenDisplayIcon = platformSelect . value ;
361
+ } else {
362
+ // emoji radio is checked
363
+ const emojiValue = emojiInput . value . trim ( ) ;
364
+ if ( ! emojiValue ) {
365
+ showStatus (
366
+ 'Please enter an emoji or select "Use Platform Icon".' ,
367
+ true ,
368
+ 4000 ,
369
+ ) ;
370
+ emojiInput . focus ( ) ;
371
+ return ;
372
+ }
373
+
374
+ if ( emojiValue . length > 2 ) {
375
+ showStatus (
376
+ "Please enter only one or two characters for the emoji." ,
377
+ true ,
378
+ 4000 ,
379
+ ) ;
380
+ emojiInput . focus ( ) ;
381
+ return ;
382
+ }
383
+ chosenDisplayIcon = emojiValue ; // use the entered emoji
384
+ }
385
+
304
386
const profileData = {
305
387
id : profileIdInput . value || null ,
306
388
platform : platformSelect . value ,
@@ -310,6 +392,7 @@ document.addEventListener("DOMContentLoaded", () => {
310
392
platformSelect . value === "custom"
311
393
? customPlatformNameInput . value . trim ( )
312
394
: null ,
395
+ displayIcon : chosenDisplayIcon ,
313
396
} ;
314
397
315
398
if ( profileData . platform === "custom" && ! profileData . customPlatformName ) {
0 commit comments