Skip to content

Commit 0ba9883

Browse files
committed
Avoid setting innerHTML
1 parent a59d15a commit 0ba9883

File tree

4 files changed

+197
-93
lines changed

4 files changed

+197
-93
lines changed

src/core/popup.ts

+56-19
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { formatVariables, unescapeValue } from '../utils/string-utils';
1111
import { loadTemplates, createDefaultTemplate } from '../managers/template-manager';
1212
import browser from '../utils/browser-polyfill';
1313
import { detectBrowser } from '../utils/browser-detection';
14+
import { createElementWithClass, createElementWithHTML } from '../utils/dom-utils';
1415

1516
let currentTemplate: Template | null = null;
1617
let templates: Template[] = [];
@@ -384,8 +385,7 @@ document.addEventListener('DOMContentLoaded', async function() {
384385
}
385386

386387
for (const property of template.properties) {
387-
const propertyDiv = document.createElement('div');
388-
propertyDiv.className = 'metadata-property';
388+
const propertyDiv = createElementWithClass('div', 'metadata-property');
389389
let value = await replaceVariables(tabId, unescapeValue(property.value), variables, currentUrl);
390390

391391
// Apply type-specific parsing
@@ -405,11 +405,23 @@ document.addEventListener('DOMContentLoaded', async function() {
405405
break;
406406
}
407407

408-
propertyDiv.innerHTML = `
409-
<span class="metadata-property-icon"><i data-lucide="${getPropertyTypeIcon(property.type)}"></i></span>
410-
<label for="${property.name}">${property.name}</label>
411-
<input id="${property.name}" type="text" value="${escapeHtml(value)}" data-type="${property.type}" data-template-value="${escapeHtml(property.value)}" />
412-
`;
408+
const iconSpan = createElementWithClass('span', 'metadata-property-icon');
409+
iconSpan.appendChild(createElementWithHTML('i', '', { 'data-lucide': getPropertyTypeIcon(property.type) }));
410+
propertyDiv.appendChild(iconSpan);
411+
412+
const label = document.createElement('label');
413+
label.setAttribute('for', property.name);
414+
label.textContent = property.name;
415+
propertyDiv.appendChild(label);
416+
417+
const input = document.createElement('input');
418+
input.id = property.name;
419+
input.type = 'text';
420+
input.value = value;
421+
input.setAttribute('data-type', property.type);
422+
input.setAttribute('data-template-value', property.value);
423+
propertyDiv.appendChild(input);
424+
413425
templateProperties.appendChild(propertyDiv);
414426
}
415427

@@ -497,16 +509,42 @@ document.addEventListener('DOMContentLoaded', async function() {
497509

498510
showMoreActionsButton.addEventListener('click', function() {
499511
if (currentTemplate && Object.keys(currentVariables).length > 0) {
500-
const formattedVariables = formatVariables(currentVariables);
501-
variablesPanel.innerHTML = `
502-
<div class="variables-header">
503-
<h3>Page variables</h3>
504-
<span class="close-panel clickable-icon" aria-label="Close">
505-
<i data-lucide="x"></i>
506-
</span>
507-
</div>
508-
<div class="variable-list">${formattedVariables}</div>
509-
`;
512+
const variablesPanel = document.querySelector('.variables-panel') as HTMLElement;
513+
variablesPanel.innerHTML = '';
514+
515+
const headerDiv = createElementWithClass('div', 'variables-header');
516+
const headerTitle = document.createElement('h3');
517+
headerTitle.textContent = 'Page variables';
518+
headerDiv.appendChild(headerTitle);
519+
520+
const closeButton = createElementWithClass('span', 'close-panel clickable-icon');
521+
closeButton.setAttribute('aria-label', 'Close');
522+
closeButton.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'x' }));
523+
headerDiv.appendChild(closeButton);
524+
525+
variablesPanel.appendChild(headerDiv);
526+
527+
const variableListDiv = createElementWithClass('div', 'variable-list');
528+
Object.entries(currentVariables).forEach(([key, value]) => {
529+
const variableItem = createElementWithClass('div', 'variable-item');
530+
531+
const keySpan = createElementWithClass('span', 'variable-key');
532+
keySpan.textContent = key;
533+
keySpan.setAttribute('data-variable', `{{${key}}}`);
534+
variableItem.appendChild(keySpan);
535+
536+
const chevronSpan = createElementWithClass('span', 'chevron-icon');
537+
chevronSpan.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'chevron-down' }));
538+
variableItem.appendChild(chevronSpan);
539+
540+
const valueSpan = createElementWithClass('span', 'variable-value');
541+
valueSpan.textContent = value;
542+
variableItem.appendChild(valueSpan);
543+
544+
variableListDiv.appendChild(variableItem);
545+
});
546+
variablesPanel.appendChild(variableListDiv);
547+
510548
variablesPanel.classList.add('show');
511549
initializeIcons();
512550

@@ -546,8 +584,7 @@ document.addEventListener('DOMContentLoaded', async function() {
546584
});
547585
});
548586

549-
const closePanel = variablesPanel.querySelector('.close-panel') as HTMLElement;
550-
closePanel.addEventListener('click', function() {
587+
closeButton.addEventListener('click', function() {
551588
variablesPanel.classList.remove('show');
552589
});
553590
} else {

src/managers/general-settings.ts

+30-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getCommands } from '../utils/hotkeys';
44
import { initializeToggles } from '../utils/ui-utils';
55
import { generalSettings, loadGeneralSettings, saveGeneralSettings } from '../utils/storage-utils';
66
import { detectBrowser } from '../utils/browser-detection';
7+
import { createElementWithClass, createElementWithHTML } from '../utils/dom-utils';
78

89
export function updateVaultList(): void {
910
const vaultList = document.getElementById('vault-list') as HTMLUListElement;
@@ -12,22 +13,27 @@ export function updateVaultList(): void {
1213
vaultList.innerHTML = '';
1314
generalSettings.vaults.forEach((vault, index) => {
1415
const li = document.createElement('li');
15-
li.innerHTML = `
16-
<div class="drag-handle">
17-
<i data-lucide="grip-vertical"></i>
18-
</div>
19-
<span>${vault}</span>
20-
<button type="button" class="remove-vault-btn clickable-icon" aria-label="Remove vault">
21-
<i data-lucide="trash-2"></i>
22-
</button>
23-
`;
2416
li.dataset.index = index.toString();
2517
li.draggable = true;
18+
19+
const dragHandle = createElementWithClass('div', 'drag-handle');
20+
dragHandle.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'grip-vertical' }));
21+
li.appendChild(dragHandle);
22+
23+
const span = document.createElement('span');
24+
span.textContent = vault;
25+
li.appendChild(span);
26+
27+
const removeBtn = createElementWithClass('button', 'remove-vault-btn clickable-icon');
28+
removeBtn.setAttribute('type', 'button');
29+
removeBtn.setAttribute('aria-label', 'Remove vault');
30+
removeBtn.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'trash-2' }));
31+
li.appendChild(removeBtn);
32+
2633
li.addEventListener('dragstart', handleDragStart);
2734
li.addEventListener('dragover', handleDragOver);
2835
li.addEventListener('drop', handleDrop);
2936
li.addEventListener('dragend', handleDragEnd);
30-
const removeBtn = li.querySelector('.remove-vault-btn') as HTMLButtonElement;
3137
removeBtn.addEventListener('click', (e) => {
3238
e.stopPropagation();
3339
removeVault(index);
@@ -71,7 +77,10 @@ export async function setShortcutInstructions() {
7177
default:
7278
instructions = 'To change key assignments, please refer to your browser\'s extension settings.';
7379
}
74-
shortcutInstructionsElement.innerHTML = `Keyboard shortcuts give you quick access to clipper features. ${instructions}`;
80+
shortcutInstructionsElement.textContent = 'Keyboard shortcuts give you quick access to clipper features. ';
81+
const codeElement = document.createElement('code');
82+
codeElement.textContent = instructions.replace(/<\/?code>/g, '');
83+
shortcutInstructionsElement.appendChild(codeElement);
7584
}
7685
}
7786

@@ -119,12 +128,16 @@ function initializeKeyboardShortcuts(): void {
119128

120129
getCommands().then(commands => {
121130
commands.forEach(command => {
122-
const shortcutItem = document.createElement('div');
123-
shortcutItem.className = 'shortcut-item';
124-
shortcutItem.innerHTML = `
125-
<span>${command.description}</span>
126-
<span class="setting-hotkey">${command.shortcut || 'Not set'}</span>
127-
`;
131+
const shortcutItem = createElementWithClass('div', 'shortcut-item');
132+
133+
const descriptionSpan = document.createElement('span');
134+
descriptionSpan.textContent = command.description;
135+
shortcutItem.appendChild(descriptionSpan);
136+
137+
const hotkeySpan = createElementWithClass('span', 'setting-hotkey');
138+
hotkeySpan.textContent = command.shortcut || 'Not set';
139+
shortcutItem.appendChild(hotkeySpan);
140+
128141
shortcutsList.appendChild(shortcutItem);
129142
});
130143
});

src/managers/template-ui.ts

+90-57
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { generalSettings } from '../utils/storage-utils';
66
import { updateUrl } from '../utils/routing';
77
import { handleDragStart, handleDragOver, handleDrop, handleDragEnd } from '../utils/drag-and-drop';
88
import browser from '../utils/browser-polyfill';
9+
import { createElementWithClass, createElementWithHTML } from '../utils/dom-utils';
910

1011
let hasUnsavedChanges = false;
1112

@@ -26,15 +27,21 @@ export function updateTemplateList(loadedTemplates?: Template[]): void {
2627
templatesToUse.forEach((template, index) => {
2728
if (template && template.name && template.id) {
2829
const li = document.createElement('li');
29-
li.innerHTML = `
30-
<div class="drag-handle">
31-
<i data-lucide="grip-vertical"></i>
32-
</div>
33-
<span class="template-name">${template.name}</span>
34-
<button type="button" class="delete-template-btn clickable-icon" aria-label="Delete template">
35-
<i data-lucide="trash-2"></i>
36-
</button>
37-
`;
30+
31+
const dragHandle = createElementWithClass('div', 'drag-handle');
32+
dragHandle.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'grip-vertical' }));
33+
li.appendChild(dragHandle);
34+
35+
const templateName = createElementWithClass('span', 'template-name');
36+
templateName.textContent = template.name;
37+
li.appendChild(templateName);
38+
39+
const deleteBtn = createElementWithClass('button', 'delete-template-btn clickable-icon');
40+
deleteBtn.setAttribute('type', 'button');
41+
deleteBtn.setAttribute('aria-label', 'Delete template');
42+
deleteBtn.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'trash-2' }));
43+
li.appendChild(deleteBtn);
44+
3845
li.dataset.id = template.id;
3946
li.dataset.index = index.toString();
4047
li.draggable = true;
@@ -44,13 +51,12 @@ export function updateTemplateList(loadedTemplates?: Template[]): void {
4451
showTemplateEditor(template);
4552
}
4653
});
47-
const deleteBtn = li.querySelector('.delete-template-btn');
48-
if (deleteBtn) {
49-
deleteBtn.addEventListener('click', (e) => {
50-
e.stopPropagation();
51-
deleteTemplate(template.id);
52-
});
53-
}
54+
55+
deleteBtn.addEventListener('click', (e) => {
56+
e.stopPropagation();
57+
deleteTemplate(template.id);
58+
});
59+
5460
if (index === editingTemplateIndex) {
5561
li.classList.add('active');
5662
}
@@ -173,7 +179,11 @@ export function showTemplateEditor(template: Template | null): void {
173179

174180
const vaultSelect = document.getElementById('template-vault') as HTMLSelectElement;
175181
if (vaultSelect) {
176-
vaultSelect.innerHTML = '<option value="">Last used</option>';
182+
vaultSelect.innerHTML = '';
183+
const lastUsedOption = document.createElement('option');
184+
lastUsedOption.value = '';
185+
lastUsedOption.textContent = 'Last used';
186+
vaultSelect.appendChild(lastUsedOption);
177187
generalSettings.vaults.forEach(vault => {
178188
const option = document.createElement('option');
179189
option.value = vault;
@@ -238,32 +248,55 @@ export function addPropertyToEditor(name: string = '', value: string = '', type:
238248
if (!templateProperties) return;
239249

240250
const propertyId = id || Date.now().toString() + Math.random().toString(36).slice(2, 11);
241-
const propertyDiv = document.createElement('div');
242-
propertyDiv.className = 'property-editor';
243-
propertyDiv.innerHTML = `
244-
<div class="drag-handle">
245-
<i data-lucide="grip-vertical"></i>
246-
</div>
247-
<div class="property-select">
248-
<div class="property-selected" data-value="${type}">
249-
<i data-lucide="${getPropertyTypeIcon(type)}"></i>
250-
</div>
251-
<select class="property-type" id="${propertyId}-type">
252-
<option value="text">Text</option>
253-
<option value="multitext">List</option>
254-
<option value="number">Number</option>
255-
<option value="checkbox">Checkbox</option>
256-
<option value="date">Date</option>
257-
<option value="datetime">Date & time</option>
258-
</select>
259-
</div>
260-
<input type="text" class="property-name" id="${propertyId}-name" value="${name}" placeholder="Property name">
261-
<input type="text" class="property-value" id="${propertyId}-value" value="${escapeHtml(unescapeValue(value))}" placeholder="Property value">
262-
<button type="button" class="remove-property-btn clickable-icon" aria-label="Remove property">
263-
<i data-lucide="trash-2"></i>
264-
</button>
265-
`;
251+
const propertyDiv = createElementWithClass('div', 'property-editor');
266252
propertyDiv.dataset.id = propertyId;
253+
254+
const dragHandle = createElementWithClass('div', 'drag-handle');
255+
dragHandle.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'grip-vertical' }));
256+
propertyDiv.appendChild(dragHandle);
257+
258+
const propertySelectDiv = createElementWithClass('div', 'property-select');
259+
const propertySelectedDiv = createElementWithClass('div', 'property-selected');
260+
propertySelectedDiv.dataset.value = type;
261+
propertySelectedDiv.appendChild(createElementWithHTML('i', '', { 'data-lucide': getPropertyTypeIcon(type) }));
262+
propertySelectDiv.appendChild(propertySelectedDiv);
263+
264+
const select = document.createElement('select');
265+
select.className = 'property-type';
266+
select.id = `${propertyId}-type`;
267+
['text', 'multitext', 'number', 'checkbox', 'date', 'datetime'].forEach(optionValue => {
268+
const option = document.createElement('option');
269+
option.value = optionValue;
270+
option.textContent = optionValue.charAt(0).toUpperCase() + optionValue.slice(1);
271+
select.appendChild(option);
272+
});
273+
propertySelectDiv.appendChild(select);
274+
propertyDiv.appendChild(propertySelectDiv);
275+
276+
const nameInput = createElementWithHTML('input', '', {
277+
type: 'text',
278+
class: 'property-name',
279+
id: `${propertyId}-name`,
280+
value: name,
281+
placeholder: 'Property name'
282+
});
283+
propertyDiv.appendChild(nameInput);
284+
285+
const valueInput = createElementWithHTML('input', '', {
286+
type: 'text',
287+
class: 'property-value',
288+
id: `${propertyId}-value`,
289+
value: escapeHtml(unescapeValue(value)),
290+
placeholder: 'Property value'
291+
});
292+
propertyDiv.appendChild(valueInput);
293+
294+
const removeBtn = createElementWithClass('button', 'remove-property-btn clickable-icon');
295+
removeBtn.setAttribute('type', 'button');
296+
removeBtn.setAttribute('aria-label', 'Remove property');
297+
removeBtn.appendChild(createElementWithHTML('i', '', { 'data-lucide': 'trash-2' }));
298+
propertyDiv.appendChild(removeBtn);
299+
267300
templateProperties.appendChild(propertyDiv);
268301

269302
propertyDiv.addEventListener('mousedown', (event) => {
@@ -288,23 +321,16 @@ export function addPropertyToEditor(name: string = '', value: string = '', type:
288321
propertyDiv.addEventListener('dragend', resetDraggable);
289322
propertyDiv.addEventListener('mouseup', resetDraggable);
290323

291-
const propertySelect = propertyDiv.querySelector('.property-select');
292-
if (!propertySelect) return;
293-
294-
const propertySelected = propertySelect.querySelector('.property-selected');
295-
const hiddenSelect = propertySelect.querySelector('select');
324+
if (select) {
325+
select.value = type;
296326

297-
if (hiddenSelect) {
298-
hiddenSelect.value = type;
299-
300-
hiddenSelect.addEventListener('change', function() {
301-
if (propertySelected) updateSelectedOption(this.value, propertySelected as HTMLElement);
327+
select.addEventListener('change', function() {
328+
if (propertySelectedDiv) updateSelectedOption(this.value, propertySelectedDiv);
302329
});
303330
}
304331

305-
const removePropertyBtn = propertyDiv.querySelector('.remove-property-btn');
306-
if (removePropertyBtn) {
307-
removePropertyBtn.addEventListener('click', () => {
332+
if (removeBtn) {
333+
removeBtn.addEventListener('click', () => {
308334
templateProperties.removeChild(propertyDiv);
309335
});
310336
}
@@ -314,14 +340,21 @@ export function addPropertyToEditor(name: string = '', value: string = '', type:
314340
propertyDiv.addEventListener('drop', handleDrop);
315341
propertyDiv.addEventListener('dragend', handleDragEnd);
316342

317-
if (propertySelected) updateSelectedOption(type, propertySelected as HTMLElement);
343+
updateSelectedOption(type, propertySelectedDiv);
318344

319345
initializeIcons(propertyDiv);
320346
}
321347

322348
function updateSelectedOption(value: string, propertySelected: HTMLElement): void {
323349
const iconName = getPropertyTypeIcon(value);
324-
propertySelected.innerHTML = `<i data-lucide="${iconName}"></i>`;
350+
351+
// Clear existing content
352+
propertySelected.innerHTML = '';
353+
354+
// Create and append the new icon element
355+
const iconElement = createElementWithHTML('i', '', { 'data-lucide': iconName });
356+
propertySelected.appendChild(iconElement);
357+
325358
propertySelected.setAttribute('data-value', value);
326359
initializeIcons(propertySelected);
327360
}

0 commit comments

Comments
 (0)