Skip to content

Commit 35bd41e

Browse files
feat(settings): add emoji suppoprt for profile icon
1 parent a0bd589 commit 35bd41e

File tree

6 files changed

+270
-43
lines changed

6 files changed

+270
-43
lines changed

manifest.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"manifest_version": 3,
3-
"name": "Quick Social Links",
4-
"version": "1.0",
5-
"description": "Quickly copy your social media profile URLs. Set your links in the options.",
3+
"name": "ClipLinks",
4+
"version": "1.0.1",
5+
"description": "Instantly copy your social media and website links with a single click.",
66
"permissions": ["storage"],
77
"action": {
88
"default_popup": "popup.html",

options.css

+53
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,56 @@ h1 {
280280
padding: 30px 0;
281281
font-style: italic;
282282
}
283+
284+
#profile-form .radio-group {
285+
margin-top: 5px;
286+
margin-bottom: 10px;
287+
}
288+
289+
#profile-form .radio-group label {
290+
margin-right: 20px;
291+
font-weight: normal;
292+
cursor: pointer;
293+
}
294+
295+
#profile-form .radio-group input[type="radio"] {
296+
margin-right: 5px;
297+
vertical-align: middle;
298+
}
299+
300+
#emoji-input-group {
301+
margin-top: -5px;
302+
}
303+
#emoji-input {
304+
max-width: 150px;
305+
font-size: 1.2em;
306+
text-align: center;
307+
padding-top: 6px;
308+
padding-bottom: 6px;
309+
}
310+
311+
.profile-emoji-icon {
312+
font-size: 1.5em;
313+
line-height: 1;
314+
display: inline-block;
315+
text-align: center;
316+
width: 100%;
317+
}
318+
319+
.profile-icon {
320+
display: flex;
321+
align-items: center;
322+
justify-content: center;
323+
width: 24px;
324+
325+
height: 24px;
326+
flex-shrink: 0;
327+
overflow: hidden;
328+
}
329+
330+
.profile-icon img {
331+
max-width: 100%;
332+
max-height: 100%;
333+
display: block;
334+
object-fit: contain;
335+
}

options.html

+43
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,49 @@ <h2 id="modal-title">Add Social Profile</h2>
6161
</select>
6262
</div>
6363

64+
<div class="form-group">
65+
<label>Icon Choice</label>
66+
<div class="radio-group">
67+
<label>
68+
<input
69+
type="radio"
70+
name="iconChoice"
71+
value="platform"
72+
id="iconChoicePlatform"
73+
checked
74+
/>
75+
Use Platform Icon
76+
</label>
77+
<label>
78+
<input
79+
type="radio"
80+
name="iconChoice"
81+
value="emoji"
82+
id="iconChoiceEmoji"
83+
/>
84+
Use Emoji
85+
</label>
86+
</div>
87+
</div>
88+
89+
<div
90+
class="form-group"
91+
id="emoji-input-group"
92+
style="display: none"
93+
>
94+
<label for="emoji-input">Emoji</label>
95+
<input
96+
type="text"
97+
id="emoji-input"
98+
placeholder="Enter a single emoji (e.g., 🔗)"
99+
maxlength="2"
100+
/>
101+
<small
102+
>Enter one or two characters. Display may
103+
vary.</small
104+
>
105+
</div>
106+
64107
<div
65108
class="form-group"
66109
id="custom-platform-group"

options.js

+109-26
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ document.addEventListener("DOMContentLoaded", () => {
1616
const customPlatformNameInput = document.getElementById(
1717
"custom-platform-name",
1818
);
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");
1923
const usernameInput = document.getElementById("username");
2024
const urlInput = document.getElementById("url");
2125
const saveProfileBtn = document.getElementById("save-profile-btn");
@@ -49,7 +53,6 @@ document.addEventListener("DOMContentLoaded", () => {
4953
urlPrefix: "https://youtube.com/@",
5054
},
5155
custom: { name: "Custom", icon: "icons/custom.png", urlPrefix: "" },
52-
// TODO: let user choose the icon or emoji for custom fields
5356
};
5457

5558
function showStatus(message, isError = false, duration = 3000) {
@@ -77,6 +80,9 @@ document.addEventListener("DOMContentLoaded", () => {
7780

7881
function openModal(profile = null) {
7982
profileForm.reset();
83+
iconChoicePlatform.checked = true; // default to platform icon
84+
emojiInputGroup.style.display = "none"; // hide emoji input initially
85+
emojiInput.required = false;
8086
customPlatformGroup.style.display = "none";
8187
profileIdInput.value = "";
8288

@@ -91,6 +97,23 @@ document.addEventListener("DOMContentLoaded", () => {
9197
if (profile.platform === "custom") {
9298
customPlatformGroup.style.display = "block";
9399
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;
94117
}
95118
} else {
96119
modalTitle.textContent = "Add Social Profile";
@@ -119,43 +142,61 @@ document.addEventListener("DOMContentLoaded", () => {
119142
}
120143
}
121144

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+
122168
// profile section
123169
function renderProfileItem(profile) {
124170
const div = document.createElement("div");
125171
div.classList.add("profile-item");
126172
div.dataset.id = profile.id;
127173

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+
129179
const platformDisplayName = getPlatformDisplayName(
130180
profile.platform,
131181
profile.customPlatformName,
132182
);
133183

134184
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+
157198
div.querySelector(".copy-btn").addEventListener("click", (e) => {
158-
e.stopPropagation(); // prevent triggering other listeners if nested
199+
e.stopPropagation();
159200
navigator.clipboard
160201
.writeText(profile.url)
161202
.then(() => {
@@ -297,10 +338,51 @@ document.addEventListener("DOMContentLoaded", () => {
297338
// update URL prefix when platform changes
298339
platformSelect.addEventListener("change", handlePlatformChange);
299340

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+
301355
profileForm.addEventListener("submit", async (e) => {
302356
e.preventDefault();
303357

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+
304386
const profileData = {
305387
id: profileIdInput.value || null,
306388
platform: platformSelect.value,
@@ -310,6 +392,7 @@ document.addEventListener("DOMContentLoaded", () => {
310392
platformSelect.value === "custom"
311393
? customPlatformNameInput.value.trim()
312394
: null,
395+
displayIcon: chosenDisplayIcon,
313396
};
314397

315398
if (profileData.platform === "custom" && !profileData.customPlatformName) {

popup.css

+25
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,28 @@ body {
156156
font-size: 0.95em;
157157
line-height: 1.4;
158158
}
159+
160+
.profile-emoji-icon {
161+
font-size: 1.3em;
162+
line-height: 1;
163+
display: inline-block;
164+
text-align: center;
165+
width: 100%;
166+
}
167+
168+
.profile-icon {
169+
display: flex;
170+
align-items: center;
171+
justify-content: center;
172+
width: 20px;
173+
height: 20px;
174+
flex-shrink: 0;
175+
overflow: hidden;
176+
}
177+
178+
.profile-icon img {
179+
max-width: 100%;
180+
max-height: 100%;
181+
display: block;
182+
object-fit: contain;
183+
}

0 commit comments

Comments
 (0)