319 lines
11 KiB
HTML
319 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Scrapy by PARVEJ - Live Data</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
<style>
|
|
.page-data { max-width: 100%; padding: 24px; height: 100vh; display: flex; flex-direction: column; }
|
|
body { justify-content: flex-start; margin: 0; height: 100vh; overflow: hidden; }
|
|
|
|
/* Make top buttons and text white */
|
|
.top-text-white { color: #ffffff !important; }
|
|
.btn-ghost-white { background: transparent; color: #ffffff !important; border-color: rgba(255,255,255,0.4); }
|
|
.btn-ghost-white:hover { background: rgba(255,255,255,0.1); border-color: #ffffff; }
|
|
|
|
/* Increase table container height */
|
|
.win-panel-flex { flex: 1; display: flex; flex-direction: column; min-height: 0; margin-top: 0; }
|
|
.tbl-container { flex: 1; overflow-y: auto; max-height: none; }
|
|
|
|
/* Sortable headers */
|
|
th { cursor: pointer; user-select: none; position: relative; }
|
|
th:hover { background: rgba(0,0,0,0.1); }
|
|
th::after { content: '\2195'; margin-left: 5px; opacity: 0.3; font-size: 0.8em; }
|
|
th.sort-asc::after { content: '\2191'; opacity: 1; }
|
|
th.sort-desc::after { content: '\2193'; opacity: 1; }
|
|
|
|
/* Tooltip */
|
|
#tooltip {
|
|
position: fixed;
|
|
background: #E7E2D9;
|
|
color: #000000;
|
|
padding: 16px;
|
|
border-radius: 6px;
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
font-family: 'Space Mono', monospace;
|
|
font-size: 0.75rem;
|
|
width: max-content;
|
|
max-width: 800px;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
z-index: 9999;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
line-height: 1.5;
|
|
}
|
|
#tooltip strong { color: #000000; font-weight: bold; font-size: 0.8rem; }
|
|
#tooltip.visible { opacity: 1; }
|
|
|
|
/* Smart Action Badges */
|
|
.action-links {
|
|
display: flex;
|
|
gap: 6px;
|
|
align-items: center;
|
|
}
|
|
.action-badge {
|
|
display: inline-block;
|
|
font-family: 'Space Mono', monospace;
|
|
font-size: 0.65rem;
|
|
padding: 3px 6px;
|
|
border-radius: 4px;
|
|
text-decoration: none;
|
|
font-weight: bold;
|
|
transition: all 0.15s;
|
|
}
|
|
/* Publisher Link */
|
|
.badge-pub {
|
|
color: var(--accent);
|
|
border: 1px solid var(--accent);
|
|
background: transparent;
|
|
}
|
|
.badge-pub:hover {
|
|
background: var(--accent);
|
|
color: #ffffff;
|
|
}
|
|
/* Direct PDF Link */
|
|
.badge-pdf {
|
|
color: #ffffff;
|
|
background: var(--green);
|
|
border: 1px solid var(--green);
|
|
}
|
|
.badge-pdf:hover {
|
|
background: var(--green-hov);
|
|
border-color: var(--green-hov);
|
|
}
|
|
/* Sci-Hub Link */
|
|
.badge-scihub {
|
|
color: #ffffff;
|
|
background: var(--accent2);
|
|
border: 1px solid var(--accent2);
|
|
}
|
|
.badge-scihub:hover {
|
|
opacity: 0.9;
|
|
}
|
|
/* ResearchGate Link */
|
|
.badge-rg {
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--input-bdr);
|
|
background: transparent;
|
|
}
|
|
.badge-rg:hover {
|
|
background: rgba(42,45,74,0.06);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-shell page-data">
|
|
|
|
<div class="app-topbar">
|
|
<div class="logo">
|
|
<div class="dot-row">
|
|
<span class="dot dot-red"></span>
|
|
<span class="dot dot-amber"></span>
|
|
<span class="dot dot-green"></span>
|
|
</div>
|
|
<h1>SCHOLAR SCRAPER // LIVE DATA VIEW</h1>
|
|
</div>
|
|
<div style="display:flex;gap:10px;align-items:center;">
|
|
<span class="badge top-text-white" id="rowCount" style="background:transparent; border:none; font-size:0.8rem;">0 rows</span>
|
|
<a href="/" class="btn btn-ghost-white" style="width:auto;margin:0;padding:6px 14px;">← BACK</a>
|
|
<a href="/download" class="btn btn-ghost-white" style="width:auto;margin:0;padding:6px 14px;">⬇ CSV</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="win-panel win-panel-flex" style="padding-top: 0; overflow: hidden;">
|
|
<div class="tbl-container">
|
|
<table id="dataTable">
|
|
<thead>
|
|
<tr>
|
|
<th onclick="sortTable(0, 'num')">#</th>
|
|
<th onclick="sortTable(1, 'str')">TITLE</th>
|
|
<th onclick="sortTable(2, 'num')">YEAR</th>
|
|
<th onclick="sortTable(3, 'str')">JOURNAL</th>
|
|
<th onclick="sortTable(4, 'str')">QUARTILE</th>
|
|
<th onclick="sortTable(5, 'num')">H-INDEX</th>
|
|
<th onclick="sortTable(6, 'num')">SJR</th>
|
|
<th onclick="sortTable(7, 'num')">CITATIONS</th>
|
|
<th onclick="sortTable(8, 'str')">PUBLISHER</th>
|
|
<th onclick="sortTable(9, 'str')">OPEN ACCESS</th>
|
|
<th onclick="sortTable(10, 'str')">COUNTRY</th>
|
|
<th>LINKS & DOWNLOADS</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Tooltip Element -->
|
|
<div id="tooltip"></div>
|
|
|
|
<script>
|
|
let currentData = [];
|
|
let sortCol = -1;
|
|
let sortAsc = true;
|
|
|
|
function getBadge(q) {
|
|
if (!q || q === 'N/A') return '<span class="badge-na">N/A</span>';
|
|
if (q === 'Q1') return '<span class="badge-q1">Q1</span>';
|
|
if (q === 'Q2') return '<span class="badge-q2">Q2</span>';
|
|
if (q === 'Q3') return '<span class="badge-q3">Q3</span>';
|
|
if (q === 'Q4') return '<span class="badge-q4">Q4</span>';
|
|
return '<span class="badge-na">' + q + '</span>';
|
|
}
|
|
|
|
function renderTable() {
|
|
const tbody = document.getElementById('tbody');
|
|
tbody.innerHTML = '';
|
|
document.getElementById('rowCount').textContent = currentData.length + ' rows';
|
|
|
|
currentData.forEach((row, idx) => {
|
|
// Escape quotes for HTML attributes safely
|
|
const titleEsc = (row.Title||'N/A').replace(/"/g, '"');
|
|
const abstractEsc = (row['Abstract/Snippet']||'No abstract').replace(/"/g, '"');
|
|
|
|
let actionsHtml = '<div class="action-links">';
|
|
|
|
// 1. Publisher Link
|
|
if (row['Paper Link'] && row['Paper Link'] !== 'N/A') {
|
|
actionsHtml += '<a href="' + row['Paper Link'] + '" target="_blank" class="action-badge badge-pub" title="Publisher Website">PAGE</a>';
|
|
}
|
|
|
|
// 2. Either Local PDF or ResearchGate Search
|
|
if (row['pdf_exists']) {
|
|
actionsHtml += '<a href="' + row['pdf_url'] + '" target="_blank" class="action-badge badge-pdf" title="Open Downloaded PDF in Browser">PDF</a>';
|
|
} else {
|
|
let searchVal = row['DOI'] && row['DOI'] !== 'N/A' ? row['DOI'] : row.Title;
|
|
let searchUrl = 'https://www.google.com/search?q=' + encodeURIComponent(searchVal + ' site:researchgate.net');
|
|
actionsHtml += '<a href="' + searchUrl + '" target="_blank" class="action-badge badge-rg" title="Search ResearchGate on Google">RG SEARCH</a>';
|
|
}
|
|
|
|
actionsHtml += '</div>';
|
|
|
|
tbody.innerHTML += '<tr>'
|
|
+ '<td>' + (row.originalIndex) + '</td>'
|
|
+ '<td class="title-cell" data-fulltitle="' + titleEsc + '" data-abstract="' + abstractEsc + '">'
|
|
+ (row.Title||'N/A').substring(0,60) + '…</td>'
|
|
+ '<td>' + (row.Year||'N/A') + '</td>'
|
|
+ '<td title="' + (row['Journal/Venue (Scholar)']||'').replace(/"/g, '"') + '">' + (row['Journal/Venue (Scholar)']||'N/A').substring(0,28) + '</td>'
|
|
+ '<td>' + getBadge(row['Quartile (SCImago)']) + '</td>'
|
|
+ '<td>' + (row['H-index (SCImago)']||'N/A') + '</td>'
|
|
+ '<td>' + (row['SJR Score (SCImago)']||'N/A') + '</td>'
|
|
+ '<td>' + (row['Citations Count']||'0') + '</td>'
|
|
+ '<td title="' + (row.Publisher||'').replace(/"/g, '"') + '">' + (row.Publisher||'N/A').substring(0,22) + '</td>'
|
|
+ '<td>' + (row['Open Access']||'N/A') + '</td>'
|
|
+ '<td>' + (row['Country (SCImago)']||'N/A') + '</td>'
|
|
+ '<td>' + actionsHtml + '</td>'
|
|
+ '</tr>';
|
|
});
|
|
|
|
attachTooltipEvents();
|
|
}
|
|
|
|
async function fetchData() {
|
|
const res = await fetch('/data');
|
|
let data = await res.json();
|
|
|
|
// Assign original indices so sorting doesn't lose the '#' number
|
|
data.forEach((row, i) => { row.originalIndex = i + 1; });
|
|
|
|
currentData = data;
|
|
if (sortCol !== -1) {
|
|
applySort();
|
|
} else {
|
|
renderTable();
|
|
}
|
|
}
|
|
|
|
// ── Sorting ────────────────────────────────────────────────────────────────
|
|
const sortKeys = [
|
|
'originalIndex', 'Title', 'Year', 'Journal/Venue (Scholar)', 'Quartile (SCImago)',
|
|
'H-index (SCImago)', 'SJR Score (SCImago)', 'Citations Count', 'Publisher', 'Open Access', 'Country (SCImago)'
|
|
];
|
|
|
|
function sortTable(colIndex, type) {
|
|
if (sortCol === colIndex) {
|
|
sortAsc = !sortAsc;
|
|
} else {
|
|
sortCol = colIndex;
|
|
sortAsc = true;
|
|
}
|
|
|
|
// Update header arrows
|
|
document.querySelectorAll('th').forEach(th => th.classList.remove('sort-asc', 'sort-desc'));
|
|
const activeTh = document.querySelectorAll('th')[colIndex];
|
|
activeTh.classList.add(sortAsc ? 'sort-asc' : 'sort-desc');
|
|
|
|
applySort();
|
|
}
|
|
|
|
function applySort() {
|
|
const key = sortKeys[sortCol];
|
|
currentData.sort((a, b) => {
|
|
let valA = a[key] || '';
|
|
let valB = b[key] || '';
|
|
|
|
// Convert to numbers if possible for numeric columns
|
|
if (sortCol === 0 || sortCol === 2 || sortCol === 5 || sortCol === 6 || sortCol === 7) {
|
|
valA = parseFloat(String(valA).replace(',', '.')) || 0;
|
|
valB = parseFloat(String(valB).replace(',', '.')) || 0;
|
|
return sortAsc ? valA - valB : valB - valA;
|
|
}
|
|
|
|
// String sort
|
|
valA = String(valA).toLowerCase();
|
|
valB = String(valB).toLowerCase();
|
|
if (valA < valB) return sortAsc ? -1 : 1;
|
|
if (valA > valB) return sortAsc ? 1 : -1;
|
|
return 0;
|
|
});
|
|
renderTable();
|
|
}
|
|
|
|
// ── Tooltip ────────────────────────────────────────────────────────────────
|
|
function attachTooltipEvents() {
|
|
const tooltip = document.getElementById('tooltip');
|
|
|
|
document.querySelectorAll('.title-cell').forEach(cell => {
|
|
cell.addEventListener('mouseenter', (e) => {
|
|
const t = e.target.dataset.fulltitle;
|
|
const a = e.target.dataset.abstract;
|
|
tooltip.innerHTML = '<strong>' + t + '</strong><br><br>' + a;
|
|
tooltip.classList.add('visible');
|
|
});
|
|
|
|
cell.addEventListener('mousemove', (e) => {
|
|
const box = tooltip.getBoundingClientRect();
|
|
let left = e.clientX + 15;
|
|
let top = e.clientY + 15;
|
|
|
|
// Prevent tooltip from overflowing the right of the screen
|
|
if (left + box.width > window.innerWidth) {
|
|
left = e.clientX - box.width - 15;
|
|
if (left < 15) left = 15;
|
|
}
|
|
|
|
// Prevent tooltip from overflowing the bottom of the screen
|
|
if (top + box.height > window.innerHeight) {
|
|
top = e.clientY - box.height - 15;
|
|
if (top < 15) top = 15;
|
|
}
|
|
|
|
tooltip.style.left = left + 'px';
|
|
tooltip.style.top = top + 'px';
|
|
});
|
|
|
|
cell.addEventListener('mouseleave', () => {
|
|
tooltip.classList.remove('visible');
|
|
});
|
|
});
|
|
}
|
|
|
|
fetchData();
|
|
setInterval(fetchData, 5000);
|
|
</script>
|
|
</body>
|
|
</html>
|