first version
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../app/app.css">
|
||||
<style>
|
||||
body { width: 340px; padding: 12px; }
|
||||
button { width: 100%; margin-bottom: 8px; }
|
||||
.row { margin-bottom: 8px; }
|
||||
.row label { flex: 1; }
|
||||
#status { font-size: 12px; white-space: pre-wrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3 style="margin-top:0">Abacus → Kimai</h3>
|
||||
<div class="row">
|
||||
<label>From <input type="date" id="from"></label>
|
||||
<label>To <input type="date" id="to"></label>
|
||||
</div>
|
||||
<button id="crawl" class="primary">Read days from MyAbacus</button>
|
||||
<button id="stop" class="hidden">Stop</button>
|
||||
<button id="open">Open dashboard</button>
|
||||
<details>
|
||||
<summary class="muted">Other pages</summary>
|
||||
<button id="extract" style="margin-top:6px">Generic extraction (tables / text)</button>
|
||||
</details>
|
||||
<div id="status" class="muted"></div>
|
||||
<script src="../lib/util.js"></script>
|
||||
<script src="../lib/parse.js"></script>
|
||||
<script src="../lib/store.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
const $status = document.getElementById('status');
|
||||
|
||||
// Runs inside the MyAbacus page (every frame). Returns raw row texts; parsing happens here.
|
||||
function collectRows(rowSelector) {
|
||||
const DATE = /\d{1,2}[./]\d{1,2}[./]\d{2,4}|\d{4}-\d{2}-\d{2}/;
|
||||
const clean = (t) => (t || '').replace(/\s*\n\s*/g, ' \t ').trim();
|
||||
let rows = [];
|
||||
try {
|
||||
rows = [...document.querySelectorAll(rowSelector || 'tr')].map((el) => clean(el.innerText)).filter(Boolean);
|
||||
} catch (e) { /* bad selector */ }
|
||||
if (rows.some((r) => DATE.test(r))) return { mode: 'rows', url: location.href, rows };
|
||||
// Fallback: plain text lines (grids rendered with divs)
|
||||
const lines = (document.body?.innerText || '').split('\n').map((l) => l.trim()).filter(Boolean);
|
||||
return { mode: 'lines', url: location.href, rows: lines };
|
||||
}
|
||||
|
||||
async function extract() {
|
||||
$status.textContent = 'Extracting…';
|
||||
const { settings } = await Store.get();
|
||||
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
const results = await browser.scripting.executeScript({
|
||||
target: { tabId: tab.id, allFrames: true },
|
||||
func: collectRows,
|
||||
args: [settings.parse.rowSelector],
|
||||
});
|
||||
|
||||
const p = settings.parse;
|
||||
const opts = { timeMode: p.timeMode, durationIndex: +p.durationIndex, sameDateRows: p.sameDateRows, year: +p.year || new Date().getFullYear() };
|
||||
let days = [];
|
||||
const raw = [];
|
||||
for (const r of results) {
|
||||
if (!r.result) continue;
|
||||
raw.push(...r.result.rows);
|
||||
days = days.concat(AbacusParse.parseRows(r.result.rows, { ...opts, carryDate: r.result.mode === 'lines' }));
|
||||
}
|
||||
const { days: merged } = AbacusParse.fromText(AbacusParse.toText(days));
|
||||
|
||||
await Store.set({
|
||||
daysText: AbacusParse.toText(merged),
|
||||
extractInfo: { at: new Date().toISOString(), url: tab.url, count: merged.length, raw: raw.slice(0, 2000) },
|
||||
});
|
||||
|
||||
if (!merged.length) {
|
||||
$status.textContent = 'No dated hours found on this page.\nOpen the dashboard → "Raw text" to see what was read, or paste the table manually.';
|
||||
return;
|
||||
}
|
||||
const total = merged.reduce((a, d) => a + d.minutes, 0);
|
||||
$status.textContent = `Found ${merged.length} day(s), ${U.fmtDur(total)} total\n${merged[0].date} → ${merged[merged.length - 1].date}`;
|
||||
browser.runtime.openOptionsPage();
|
||||
}
|
||||
|
||||
/* ---------- MyAbacus day-by-day crawl ---------- */
|
||||
const $from = document.getElementById('from');
|
||||
const $to = document.getElementById('to');
|
||||
|
||||
function showCrawl(c) {
|
||||
if (!c) return;
|
||||
$status.textContent = c.message;
|
||||
$status.className = c.status === 'error' ? 'err' : c.status === 'done' ? 'ok' : 'muted';
|
||||
const running = c.status === 'running';
|
||||
document.getElementById('crawl').disabled = running;
|
||||
document.getElementById('stop').classList.toggle('hidden', !running);
|
||||
}
|
||||
|
||||
async function crawl() {
|
||||
const from = $from.value, to = $to.value;
|
||||
if (!from || !to || from > to) throw new Error('Pick a valid date range');
|
||||
await browser.storage.local.set({ crawlRange: { from, to } });
|
||||
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
const target = { tabId: tab.id, allFrames: true };
|
||||
await browser.scripting.executeScript({ target, files: ['lib/util.js', 'lib/parse.js', 'lib/abacus-crawl.js'] });
|
||||
const results = await browser.scripting.executeScript({
|
||||
target,
|
||||
func: (f, t) => AbacusCrawl.run(f, t),
|
||||
args: [from, to],
|
||||
});
|
||||
const res = results.map((r) => r.result).filter(Boolean);
|
||||
if (res.every((r) => r.skipped)) throw new Error('No "Gestion des temps" card found on this tab');
|
||||
const err = res.find((r) => r.error);
|
||||
if (!err) browser.runtime.openOptionsPage();
|
||||
}
|
||||
|
||||
document.getElementById('crawl').addEventListener('click', () => crawl().catch((e) => {
|
||||
$status.textContent = 'Error: ' + e.message;
|
||||
$status.className = 'err';
|
||||
}));
|
||||
document.getElementById('stop').addEventListener('click', () => browser.storage.local.set({ crawlStop: true }));
|
||||
|
||||
browser.storage.onChanged.addListener((ch) => { if (ch.crawl) showCrawl(ch.crawl.newValue); });
|
||||
|
||||
(async () => {
|
||||
const { crawlRange, crawl: last } = await browser.storage.local.get(['crawlRange', 'crawl']);
|
||||
const today = U.isoDate(new Date());
|
||||
$from.value = crawlRange?.from || U.monday(today);
|
||||
$to.value = crawlRange?.to || today;
|
||||
// Only show a stale "running" if it's recent (page may have been reloaded mid-crawl)
|
||||
if (last && (last.status !== 'running' || Date.now() - new Date(last.at) < 30000)) showCrawl(last);
|
||||
})();
|
||||
|
||||
document.getElementById('extract').addEventListener('click', () => extract().catch((e) => { $status.textContent = 'Error: ' + e.message; }));
|
||||
document.getElementById('open').addEventListener('click', () => browser.runtime.openOptionsPage());
|
||||
Reference in New Issue
Block a user