v0.2
This commit is contained in:
+222
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
})();
|
||||
Reference in New Issue
Block a user