This commit is contained in:
Arthur Ria
2026-05-20 09:47:25 +02:00
commit 6484dd09c5
12 changed files with 1340 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
:root {
--bg: #ffffff;
--fg: #1f2937;
--muted: #6b7280;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--success: #16a34a;
--warning: #d97706;
--error: #dc2626;
--border: #e5e7eb;
--surface: #f9fafb;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
body {
width: 360px;
min-height: 260px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 13px;
color: var(--fg);
background: var(--bg);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 12px 14px 10px 14px;
border-bottom: 1px solid var(--border);
}
.header-text {
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.title {
margin: 0;
font-size: 15px;
font-weight: 600;
letter-spacing: 0.2px;
}
.view-name {
display: block;
font-size: 11.5px;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 250px;
}
.view-name::before {
content: "Vue : ";
color: #9ca3af;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 22px;
padding: 0 8px;
font-size: 12px;
font-weight: 600;
color: #fff;
background: var(--muted);
border-radius: 999px;
}
.badge.badge--active {
background: var(--primary);
}
.status {
margin: 10px 14px 6px 14px;
padding: 8px 10px;
font-size: 12px;
border-radius: 6px;
border: 1px solid transparent;
line-height: 1.35;
}
.status--info {
color: #1e3a8a;
background: #eff6ff;
border-color: #bfdbfe;
}
.status--ok {
color: #166534;
background: #f0fdf4;
border-color: #bbf7d0;
}
.status--warn {
color: #92400e;
background: #fffbeb;
border-color: #fde68a;
}
.status--error {
color: #991b1b;
background: #fef2f2;
border-color: #fecaca;
}
.preview-wrapper {
padding: 4px 14px 0 14px;
}
.preview-label {
display: block;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--muted);
margin: 6px 0 4px 0;
}
.preview {
list-style: none;
margin: 0;
padding: 4px;
height: 140px;
overflow: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 11.5px;
color: #374151;
}
.preview-item {
display: flex;
flex-direction: column;
padding: 5px 8px;
border-radius: 4px;
gap: 1px;
}
.preview-item + .preview-item {
margin-top: 2px;
border-top: 1px solid var(--border);
}
.preview-code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 11.5px;
color: #111827;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.preview-id {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 10.5px;
color: #6b7280;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.preview-more {
padding: 6px 8px;
font-size: 11px;
font-style: italic;
color: var(--muted);
text-align: center;
}
.footer {
padding: 10px 14px 14px 14px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 9px 12px;
font-size: 13px;
font-weight: 600;
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.55;
}
.btn--primary {
color: #fff;
background: var(--primary);
}
.btn--primary:hover:not(:disabled) {
background: var(--primary-hover);
}
.btn--ok {
color: #fff;
background: var(--success);
}
.btn--error {
color: #fff;
background: var(--error);
}
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>TakeID</title>
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<header class="header">
<div class="header-text">
<h1 class="title">TakeID - SmartUI</h1>
<span id="view-name" class="view-name" hidden></span>
</div>
<span id="count-badge" class="badge">0</span>
</header>
<section id="status" class="status status--info">Lecture de la page...</section>
<section class="preview-wrapper">
<label class="preview-label">Sélection</label>
<ul id="preview" class="preview" aria-live="polite"></ul>
</section>
<footer class="footer">
<button id="copy-btn" class="btn btn--primary" type="button" disabled>
<span class="btn-label">Copier les IDs</span>
</button>
</footer>
<script src="popup.js"></script>
</body>
</html>
+277
View File
@@ -0,0 +1,277 @@
(function () {
'use strict';
const browser = globalThis.browser || globalThis.chrome;
const statusEl = document.getElementById('status');
const countBadge = document.getElementById('count-badge');
const previewEl = document.getElementById('preview');
const copyBtn = document.getElementById('copy-btn');
const copyLabel = copyBtn.querySelector('.btn-label');
const viewNameEl = document.getElementById('view-name');
let currentIds = [];
function parseViewName(url) {
if (!url) return null;
try {
const u = new URL(url);
const raw = u.searchParams.get('viewname');
if (!raw) return null;
// Format attendu : AppName|ViewName
const parts = raw.split('|');
const name = parts.length > 1 ? parts[1] : parts[0];
return name ? name.trim() : null;
} catch (e) {
return null;
}
}
function setViewName(name) {
if (!name) {
viewNameEl.hidden = true;
viewNameEl.textContent = '';
viewNameEl.removeAttribute('title');
return;
}
viewNameEl.textContent = name;
viewNameEl.title = name;
viewNameEl.hidden = false;
}
function setStatus(text, level) {
statusEl.textContent = text;
statusEl.className = 'status status--' + (level || 'info');
}
function setBadge(n) {
countBadge.textContent = String(n);
countBadge.classList.toggle('badge--active', n > 0);
}
function setPreview(pairs) {
// Vide le contenu existant
while (previewEl.firstChild) previewEl.removeChild(previewEl.firstChild);
if (!pairs || pairs.length === 0) return;
const MAX_PREVIEW = 50;
const visible = pairs.slice(0, MAX_PREVIEW);
for (const pair of visible) {
const item = document.createElement('li');
item.className = 'preview-item';
const codeEl = document.createElement('span');
codeEl.className = 'preview-code';
codeEl.textContent = pair.code || '(code inconnu)';
codeEl.title = codeEl.textContent;
const idEl = document.createElement('span');
idEl.className = 'preview-id';
idEl.textContent = pair.id;
idEl.title = pair.id;
item.appendChild(codeEl);
item.appendChild(idEl);
previewEl.appendChild(item);
}
if (pairs.length > MAX_PREVIEW) {
const more = document.createElement('li');
more.className = 'preview-more';
more.textContent = '... et ' + (pairs.length - MAX_PREVIEW) + ' de plus';
previewEl.appendChild(more);
}
}
function setButtonState(state) {
copyBtn.classList.remove('btn--primary', 'btn--ok', 'btn--error');
switch (state) {
case 'ok':
copyBtn.classList.add('btn--ok');
copyLabel.textContent = 'Copié';
break;
case 'error':
copyBtn.classList.add('btn--error');
copyLabel.textContent = 'Échec de la copie';
break;
case 'idle':
default:
copyBtn.classList.add('btn--primary');
copyLabel.textContent = 'Copier les IDs';
break;
}
}
function isSmartUiUrl(url) {
if (!url) return false;
try {
const u = new URL(url);
return /\/SmartUI\//i.test(u.pathname);
} catch (e) {
return /\/SmartUI\//i.test(url);
}
}
function queryActiveTab() {
return new Promise((resolve) => {
try {
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
resolve(tabs && tabs[0] ? tabs[0] : null);
});
} catch (e) {
resolve(null);
}
});
}
function sendMessage(tabId, msg) {
return new Promise((resolve) => {
let settled = false;
const done = (value) => {
if (settled) return;
settled = true;
resolve(value);
};
try {
browser.tabs.sendMessage(tabId, msg, (response) => {
const err = browser.runtime && browser.runtime.lastError;
if (err) {
done({ ok: false, error: err.message || String(err) });
} else {
done(response || { ok: false, error: 'empty_response' });
}
});
} catch (e) {
done({ ok: false, error: String(e && e.message ? e.message : e) });
}
setTimeout(() => done({ ok: false, error: 'timeout' }), 1500);
});
}
async function refresh() {
const tab = await queryActiveTab();
if (!tab) {
setStatus("Impossible de lire l'onglet actif.", 'error');
setBadge(0);
setPreview([]);
setViewName(null);
copyBtn.disabled = true;
return;
}
if (!isSmartUiUrl(tab.url)) {
setStatus("Cette page n'est pas une vue SmartUI.", 'warn');
setBadge(0);
setPreview([]);
setViewName(null);
copyBtn.disabled = true;
return;
}
setViewName(parseViewName(tab.url));
let resp = await sendMessage(tab.id, { type: 'GET_SELECTED_IDS' });
// Si le content script ne repond pas, l'injecter programmatiquement et reessayer
if (!resp || resp.ok === false) {
setStatus("Injection du content script...", 'info');
try {
await browser.scripting.executeScript({
target: { tabId: tab.id, allFrames: true },
files: ['content/content-script.js']
});
// Attendre que le script s'initialise
await new Promise((r) => setTimeout(r, 800));
resp = await sendMessage(tab.id, { type: 'GET_SELECTED_IDS' });
} catch (e) {
console.warn('[TakeID] Injection failed:', e);
}
}
if (!resp || resp.ok === false) {
setStatus(
"Le content script ne répond pas. Rechargez la page SmartUI puis réouvrez le popup.",
'error'
);
setBadge(0);
setPreview([]);
copyBtn.disabled = true;
return;
}
const pairs = Array.isArray(resp.pairs)
? resp.pairs
: (Array.isArray(resp.ids) ? resp.ids.map((id) => ({ id, code: null })) : []);
currentIds = pairs.map((p) => p.id);
setBadge(currentIds.length);
setPreview(pairs);
if (currentIds.length === 0) {
if (!resp.cacheSize) {
setStatus(
"Aucune donnée en cache. Naviguez sur la grille (ou rafraîchissez la vue) pour que l'extension capture les IDs.",
'warn'
);
} else if (!resp.selectedCount) {
setStatus(
"Aucune ligne sélectionnée. Cochez les lignes dans la grille puis réouvrez le popup.",
'warn'
);
} else {
setStatus(
"Lignes sélectionnées détectées mais aucun ID associé trouvé dans le cache.",
'warn'
);
}
copyBtn.disabled = true;
} else {
setStatus(
currentIds.length + ' ID' + (currentIds.length > 1 ? 's' : '') + ' prêt' + (currentIds.length > 1 ? 's' : '') + ' à copier.',
'ok'
);
copyBtn.disabled = false;
}
}
async function copyIds() {
if (!currentIds || currentIds.length === 0) return;
const text = currentIds.join('\n');
try {
await navigator.clipboard.writeText(text);
setButtonState('ok');
setStatus(currentIds.length + ' ID(s) copié(s) dans le presse-papier.', 'ok');
setTimeout(() => setButtonState('idle'), 1400);
} catch (e) {
// Fallback : execCommand
try {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
const ok = document.execCommand('copy');
document.body.removeChild(ta);
if (ok) {
setButtonState('ok');
setStatus(currentIds.length + ' ID(s) copié(s).', 'ok');
setTimeout(() => setButtonState('idle'), 1400);
} else {
throw new Error('execCommand failed');
}
} catch (e2) {
setButtonState('error');
setStatus('Échec de la copie : ' + (e2.message || e2), 'error');
setTimeout(() => setButtonState('idle'), 1800);
}
}
}
copyBtn.addEventListener('click', copyIds);
document.addEventListener('DOMContentLoaded', refresh);
if (document.readyState !== 'loading') refresh();
})();