Rename & fix of all html templates to imrove linting. Added .db and .R files to gitignore.
This commit is contained in:
parent
a2c7176385
commit
60cc378f05
4
.gitignore
vendored
4
.gitignore
vendored
@ -9,3 +9,7 @@ dist/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
|
||||||
|
*.db
|
||||||
|
|
||||||
|
*.R
|
@ -6,14 +6,14 @@ bp = Blueprint("main", __name__)
|
|||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html.jina")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/logs")
|
@bp.route("/logs")
|
||||||
def logs():
|
def logs():
|
||||||
return render_template("logs.html", app_title="PaperScraper")
|
return render_template("logs.html.jina", app_title="PaperScraper")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/about")
|
@bp.route("/about")
|
||||||
def about():
|
def about():
|
||||||
return render_template("about.html", app_title="PaperScraper")
|
return render_template("about.html.jina", app_title="PaperScraper")
|
@ -72,7 +72,7 @@ def list_papers():
|
|||||||
status_counts = {status: count for status, count in status_counts}
|
status_counts = {status: count for status, count in status_counts}
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"papers.html",
|
"papers.html.jina",
|
||||||
papers=pagination.items,
|
papers=pagination.items,
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
total_papers=total_papers,
|
total_papers=total_papers,
|
||||||
@ -137,4 +137,4 @@ def export_papers():
|
|||||||
@bp.route("/<int:paper_id>/detail")
|
@bp.route("/<int:paper_id>/detail")
|
||||||
def paper_detail(paper_id):
|
def paper_detail(paper_id):
|
||||||
paper = PaperMetadata.query.get_or_404(paper_id)
|
paper = PaperMetadata.query.get_or_404(paper_id)
|
||||||
return render_template("partials/paper_detail_modal.html", paper=paper)
|
return render_template("partials/paper_detail_modal.html.jina", paper=paper)
|
@ -72,7 +72,7 @@ def schedule():
|
|||||||
}
|
}
|
||||||
volume = VolumeConfig.query.first()
|
volume = VolumeConfig.query.first()
|
||||||
return render_template(
|
return render_template(
|
||||||
"schedule.html",
|
"schedule.html.jina",
|
||||||
schedule=schedule,
|
schedule=schedule,
|
||||||
volume=volume.volume if volume else 0,
|
volume=volume.volume if volume else 0,
|
||||||
app_title="PaperScraper",
|
app_title="PaperScraper",
|
||||||
|
@ -32,19 +32,19 @@ def upload():
|
|||||||
duplicate_strategy = request.form.get("duplicate_strategy", "skip")
|
duplicate_strategy = request.form.get("duplicate_strategy", "skip")
|
||||||
|
|
||||||
if not file:
|
if not file:
|
||||||
return render_template("upload.html", error="No file selected.")
|
return render_template("upload.html.jina", error="No file selected.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream = codecs.iterdecode(file.stream, "utf-8")
|
stream = codecs.iterdecode(file.stream, "utf-8")
|
||||||
content = "".join(stream)
|
content = "".join(stream)
|
||||||
df = pd.read_csv(StringIO(content), delimiter=delimiter)
|
df = pd.read_csv(StringIO(content), delimiter=delimiter)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return render_template("upload.html", error=f"Failed to read CSV file: {e}")
|
return render_template("upload.html.jina", error=f"Failed to read CSV file: {e}")
|
||||||
|
|
||||||
missing = REQUIRED_COLUMNS - set(df.columns)
|
missing = REQUIRED_COLUMNS - set(df.columns)
|
||||||
if missing:
|
if missing:
|
||||||
return render_template(
|
return render_template(
|
||||||
"upload.html", error=f"Missing required columns: {', '.join(missing)}"
|
"upload.html.jina", error=f"Missing required columns: {', '.join(missing)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Optional: parse 'published_online' to date
|
# Optional: parse 'published_online' to date
|
||||||
@ -126,7 +126,7 @@ def upload():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return render_template(
|
return render_template(
|
||||||
"upload.html", error=f"Failed to save data to database: {e}"
|
"upload.html.jina", error=f"Failed to save data to database: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prepare error samples for display
|
# Prepare error samples for display
|
||||||
@ -145,13 +145,13 @@ def upload():
|
|||||||
session["error_data"] = error_csv.getvalue()
|
session["error_data"] = error_csv.getvalue()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"upload.html",
|
"upload.html.jina",
|
||||||
success=f"File processed! Added: {added_count}, Updated: {updated_count}, Skipped: {skipped_count}, Errors: {error_count}",
|
success=f"File processed! Added: {added_count}, Updated: {updated_count}, Skipped: {skipped_count}, Errors: {error_count}",
|
||||||
error_message=error_message,
|
error_message=error_message,
|
||||||
error_samples=error_samples
|
error_samples=error_samples
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template("upload.html")
|
return render_template("upload.html.jina")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/download_error_log")
|
@bp.route("/download_error_log")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends 'base.html' %} {% block content %}
|
{% extends "base.html" %} {% block content %}
|
||||||
<h1 class="mb-4">📘 About This App</h1>
|
<h1 class="mb-4">📘 About This App</h1>
|
||||||
|
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
@ -107,4 +107,4 @@
|
|||||||
<li>Anyone needing a structured way to fetch and track papers in bulk</li>
|
<li>Anyone needing a structured way to fetch and track papers in bulk</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock content %}
|
@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>{{ app_title }}</title>
|
|
||||||
<link
|
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<!-- Optional Alpine.js -->
|
|
||||||
<script
|
|
||||||
defer
|
|
||||||
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
|
||||||
></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% include 'nav.html' %}
|
|
||||||
<main class="container my-5">{% block content %}{% endblock %}</main>
|
|
||||||
{% include 'footer.html' %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
21
scipaperloader/templates/base.html.jinja
Normal file
21
scipaperloader/templates/base.html.jinja
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="description" content="A platform to load scientific papers and manage metadata." />
|
||||||
|
<meta name="keywords" content="science, papers, research, management" />
|
||||||
|
<title>{{ app_title }}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Optional Alpine.js -->
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include "nav.html" %}
|
||||||
|
<main class="container my-5">{% block content %}{% endblock content %}</main>
|
||||||
|
{% include "footer.html" %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,4 +1,5 @@
|
|||||||
{% extends 'base.html' %} {% block content %}
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<h1 class="display-4">Welcome to SciPaperLoader</h1>
|
<h1 class="display-4">Welcome to SciPaperLoader</h1>
|
||||||
@ -29,7 +30,7 @@
|
|||||||
A daemon process runs hourly to fetch papers using Zotero API.
|
A daemon process runs hourly to fetch papers using Zotero API.
|
||||||
Downloads are randomized to mimic human behavior and avoid detection.
|
Downloads are randomized to mimic human behavior and avoid detection.
|
||||||
</p>
|
</p>
|
||||||
<a href="/logs" class="btn btn-sm btn-outline-secondary">View Logs</a>
|
<a href="{{ url_for('logger.list_logs') }}" class="btn btn-sm btn-outline-secondary">View Logs</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,9 +44,7 @@
|
|||||||
inspect errors. Files are stored on disk in structured folders per
|
inspect errors. Files are stored on disk in structured folders per
|
||||||
DOI.
|
DOI.
|
||||||
</p>
|
</p>
|
||||||
<a href="{{ url_for('papers.list_papers') }}" class="btn btn-sm btn-outline-success"
|
<a href="{{ url_for('papers.list_papers') }}" class="btn btn-sm btn-outline-success">Browse Papers</a>
|
||||||
>Browse Papers</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,11 +58,9 @@
|
|||||||
volume (e.g. 2/hour at daytime, 0 at night) to match your bandwidth or
|
volume (e.g. 2/hour at daytime, 0 at night) to match your bandwidth or
|
||||||
usage pattern.
|
usage pattern.
|
||||||
</p>
|
</p>
|
||||||
<a href="{{ url_for('schedule.schedule') }}" class="btn btn-sm btn-outline-warning"
|
<a href="{{ url_for('schedule.schedule') }}" class="btn btn-sm btn-outline-warning">Adjust Schedule</a>
|
||||||
>Adjust Schedule</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content %}
|
@ -1,17 +1,8 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for('main.index') }}"
|
<a class="navbar-brand" href="{{ url_for('main.index') }}">{{ app_title }}</a>
|
||||||
>{{ app_title }}</a
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
>
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<button
|
|
||||||
class="navbar-toggler"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-label="Toggle navigation"
|
|
||||||
>
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
@ -20,28 +11,22 @@
|
|||||||
<a class="nav-link" href="{{ url_for('upload.upload') }}">Import CSV</a>
|
<a class="nav-link" href="{{ url_for('upload.upload') }}">Import CSV</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('papers.list_papers') }}">Papers</a>
|
<a class="nav-link" href="{{ url_for('papers.list_papers') }}">Papers</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('schedule.schedule') }}">Schedule</a>
|
<a class="nav-link" href="{{ url_for('schedule.schedule') }}">Schedule</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown"
|
||||||
class="nav-link dropdown-toggle"
|
aria-expanded="false">
|
||||||
href="#"
|
|
||||||
id="navbarDropdown"
|
|
||||||
role="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
More
|
More
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ url_for('logger.list_logs') }}">Logs</a>
|
<a class="dropdown-item" href="{{ url_for('logger.list_logs') }}">Logs</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="/about">About</a>
|
<a class="dropdown-item" href="{{ url_for('main.about') }}">About</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="https://git.mbeck.cologne">Help</a>
|
<a class="dropdown-item" href="https://git.mbeck.cologne">Help</a>
|
||||||
@ -50,14 +35,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<form class="d-flex">
|
<form class="d-flex">
|
||||||
<input
|
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
|
||||||
class="form-control me-2"
|
|
||||||
type="search"
|
|
||||||
placeholder="Search"
|
|
||||||
aria-label="Search"
|
|
||||||
/>
|
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
@ -1,273 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Papers{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
{# --- Sort direction logic for each column --- #}
|
|
||||||
{% set title_sort = 'asc' if sort_by != 'title' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set journal_sort = 'asc' if sort_by != 'journal' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set doi_sort = 'asc' if sort_by != 'doi' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set issn_sort = 'asc' if sort_by != 'issn' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set status_sort = 'asc' if sort_by != 'status' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set created_sort = 'asc' if sort_by != 'created_at' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
{% set updated_sort = 'asc' if sort_by != 'updated_at' or sort_dir == 'desc' else 'desc' %}
|
|
||||||
|
|
||||||
<form method="get" class="mb-4 row g-3">
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label>Status</label>
|
|
||||||
<select name="status" class="form-select">
|
|
||||||
<option value="">All</option>
|
|
||||||
{% if request.args.get('status') == 'Pending' %}
|
|
||||||
<option value="Pending" selected>Pending</option>
|
|
||||||
{% else %}
|
|
||||||
<option value="Pending">Pending</option>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if request.args.get('status') == 'Done' %}
|
|
||||||
<option value="Done" selected>Done</option>
|
|
||||||
{% else %}
|
|
||||||
<option value="Done">Done</option>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if request.args.get('status') == 'Failed' %}
|
|
||||||
<option value="Failed" selected>Failed</option>
|
|
||||||
{% else %}
|
|
||||||
<option value="Failed">Failed</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label>Created from</label>
|
|
||||||
<input type="date" name="created_from" class="form-control" value="{{ request.args.get('created_from', '') }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label>Created to</label>
|
|
||||||
<input type="date" name="created_to" class="form-control" value="{{ request.args.get('created_to', '') }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label>Updated from</label>
|
|
||||||
<input type="date" name="updated_from" class="form-control" value="{{ request.args.get('updated_from', '') }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label>Updated to</label>
|
|
||||||
<input type="date" name="updated_to" class="form-control" value="{{ request.args.get('updated_to', '') }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2 d-flex align-items-end">
|
|
||||||
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="modal fade" id="paperDetailModal" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
||||||
<div class="modal-content" id="paper-detail-content">
|
|
||||||
<!-- AJAX-loaded content will go here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-4">
|
|
||||||
<!-- Statistics Section -->
|
|
||||||
<div class="me-auto">
|
|
||||||
<div class="list-group list-group-horizontal">
|
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
<strong>Total Papers</strong>
|
|
||||||
<span class="badge bg-primary rounded-pill">{{ total_papers }}</span>
|
|
||||||
</div>
|
|
||||||
{% for status, count in status_counts.items() %}
|
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
<strong>{{ status }}:</strong>
|
|
||||||
<span class="badge bg-primary rounded-pill">{{ count }}</span>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pagination Section -->
|
|
||||||
<nav aria-label="Page navigation" class="mx-auto">
|
|
||||||
<ul class="pagination justify-content-center mb-0">
|
|
||||||
{% if pagination.has_prev %}
|
|
||||||
<li class="page-item">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.prev_num, **params) }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link" aria-hidden="true">«</span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for page_num in pagination.iter_pages(left_edge=2, right_edge=2, left_current=2, right_current=2) %}
|
|
||||||
{% if page_num %}
|
|
||||||
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=page_num, **params) }}">{{ page_num }}</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if pagination.has_next %}
|
|
||||||
<li class="page-item">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.next_num, **params) }}" aria-label="Next">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link" aria-hidden="true">»</span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Buttons Section -->
|
|
||||||
<div class="ms-auto">
|
|
||||||
<a href="{{ url_for('papers.export_papers') }}" class="btn btn-outline-secondary">Export CSV</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table table-striped table-bordered table-smaller">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'title', 'sort_dir': title_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">Title</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'journal', 'sort_dir': journal_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">Journal</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'doi', 'sort_dir': doi_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">DOI</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'issn', 'sort_dir': issn_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">ISSN</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'status', 'sort_dir': status_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">Status</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'created_at', 'sort_dir': created_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">Created</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set params = params.update({'sort_by': 'updated_at', 'sort_dir': updated_sort}) or params %}
|
|
||||||
<a href="{{ url_for('papers.list_papers', **params) }}">Updated</a>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for paper in papers %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#" class="icon-link icon-link-hover paper-link" data-url="{{ url_for('papers.paper_detail', paper_id=paper.id) }}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="bi" viewBox="0 0 16 16" aria-hidden="true">
|
|
||||||
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
|
|
||||||
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
|
|
||||||
</svg>
|
|
||||||
{{ paper.title }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>{{ paper.journal }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="http://doi.org/{{ paper.doi }}" target="_blank" class="icon-link icon-link-hover">
|
|
||||||
{{ paper.doi }}
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="bi" viewBox="0 0 16 16" aria-hidden="true">
|
|
||||||
<path d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>{{ paper.issn }}</td>
|
|
||||||
<td>{{ paper.status }}</td>
|
|
||||||
<td>{{ paper.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
|
||||||
<td>{{ paper.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav aria-label="Page navigation">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
{% if pagination.has_prev %}
|
|
||||||
<li class="page-item">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.prev_num, **params) }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link" aria-hidden="true">«</span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for page_num in pagination.iter_pages(left_edge=2, right_edge=2, left_current=2, right_current=2) %}
|
|
||||||
{% if page_num %}
|
|
||||||
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=page_num, **params) }}">{{ page_num }}</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if pagination.has_next %}
|
|
||||||
<li class="page-item">
|
|
||||||
{% set params = request.args.to_dict() %}
|
|
||||||
{% set _ = params.pop('page', None) %}
|
|
||||||
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.next_num, **params) }}" aria-label="Next">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link" aria-hidden="true">»</span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('paperDetailModal'));
|
|
||||||
const content = document.getElementById('paper-detail-content');
|
|
||||||
|
|
||||||
document.querySelectorAll('.paper-link').forEach(link => {
|
|
||||||
link.addEventListener('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const url = this.getAttribute('data-url');
|
|
||||||
|
|
||||||
fetch(url)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(html => {
|
|
||||||
content.innerHTML = html;
|
|
||||||
modal.show();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
content.innerHTML = '<div class="modal-body text-danger">Error loading details.</div>';
|
|
||||||
modal.show();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
281
scipaperloader/templates/papers.html.jinja
Normal file
281
scipaperloader/templates/papers.html.jinja
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Papers{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{# --- Sort direction logic for each column --- #}
|
||||||
|
{% set title_sort = 'asc' if sort_by != 'title' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set journal_sort = 'asc' if sort_by != 'journal' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set doi_sort = 'asc' if sort_by != 'doi' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set issn_sort = 'asc' if sort_by != 'issn' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set status_sort = 'asc' if sort_by != 'status' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set created_sort = 'asc' if sort_by != 'created_at' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
{% set updated_sort = 'asc' if sort_by != 'updated_at' or sort_dir == 'desc' else 'desc' %}
|
||||||
|
|
||||||
|
<form method="get" class="mb-4 row g-3">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Status</label>
|
||||||
|
<select name="status" class="form-select">
|
||||||
|
<option value="">All</option>
|
||||||
|
{% if request.args.get('status') == 'Pending' %}
|
||||||
|
<option value="Pending" selected>Pending</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="Pending">Pending</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if request.args.get('status') == 'Done' %}
|
||||||
|
<option value="Done" selected>Done</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="Done">Done</option>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if request.args.get('status') == 'Failed' %}
|
||||||
|
<option value="Failed" selected>Failed</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="Failed">Failed</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Created from</label>
|
||||||
|
<input type="date" name="created_from" class="form-control" value="{{ request.args.get('created_from', '') }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Created to</label>
|
||||||
|
<input type="date" name="created_to" class="form-control" value="{{ request.args.get('created_to', '') }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Updated from</label>
|
||||||
|
<input type="date" name="updated_from" class="form-control" value="{{ request.args.get('updated_from', '') }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Updated to</label>
|
||||||
|
<input type="date" name="updated_to" class="form-control" value="{{ request.args.get('updated_to', '') }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="modal fade" id="paperDetailModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
|
<div class="modal-content" id="paper-detail-content">
|
||||||
|
<!-- AJAX-loaded content will go here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<!-- Statistics Section -->
|
||||||
|
<div class="me-auto">
|
||||||
|
<div class="list-group list-group-horizontal">
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<strong>Total Papers</strong>
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ total_papers }}</span>
|
||||||
|
</div>
|
||||||
|
{% for status, count in status_counts.items() %}
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<strong>{{ status }}:</strong>
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Section -->
|
||||||
|
<nav aria-label="Page navigation" class="mx-auto">
|
||||||
|
<ul class="pagination justify-content-center mb-0">
|
||||||
|
{% if pagination.has_prev %}
|
||||||
|
<li class="page-item">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.prev_num, **params) }}"
|
||||||
|
aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">«</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for page_num in pagination.iter_pages(left_edge=2, right_edge=2, left_current=2, right_current=2) %}
|
||||||
|
{% if page_num %}
|
||||||
|
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=page_num, **params) }}">{{ page_num
|
||||||
|
}}</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if pagination.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.next_num, **params) }}"
|
||||||
|
aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Buttons Section -->
|
||||||
|
<div class="ms-auto">
|
||||||
|
<a href="{{ url_for('papers.export_papers') }}" class="btn btn-outline-secondary">Export CSV</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-bordered table-smaller">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'title', 'sort_dir': title_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">Title</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'journal', 'sort_dir': journal_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">Journal</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'doi', 'sort_dir': doi_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">DOI</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'issn', 'sort_dir': issn_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">ISSN</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'status', 'sort_dir': status_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">Status</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'created_at', 'sort_dir': created_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">Created</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set params = params.update({'sort_by': 'updated_at', 'sort_dir': updated_sort}) or params %}
|
||||||
|
<a href="{{ url_for('papers.list_papers', **params) }}">Updated</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for paper in papers %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="icon-link icon-link-hover paper-link"
|
||||||
|
data-url="{{ url_for('papers.paper_detail', paper_id=paper.id) }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="bi" viewBox="0 0 16 16" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z" />
|
||||||
|
<path
|
||||||
|
d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z" />
|
||||||
|
</svg>
|
||||||
|
{{ paper.title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ paper.journal }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://doi.org/{{ paper.doi }}" target="_blank" class="icon-link icon-link-hover">
|
||||||
|
{{ paper.doi }}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="bi" viewBox="0 0 16 16" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ paper.issn }}</td>
|
||||||
|
<td>{{ paper.status }}</td>
|
||||||
|
<td>{{ paper.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||||
|
<td>{{ paper.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
{% if pagination.has_prev %}
|
||||||
|
<li class="page-item">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.prev_num, **params) }}"
|
||||||
|
aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">«</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for page_num in pagination.iter_pages(left_edge=2, right_edge=2, left_current=2, right_current=2) %}
|
||||||
|
{% if page_num %}
|
||||||
|
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=page_num, **params) }}">{{ page_num }}</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if pagination.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
{% set params = request.args.to_dict() %}
|
||||||
|
{% set _ = params.pop('page', None) %}
|
||||||
|
<a class="page-link" href="{{ url_for('papers.list_papers', page=pagination.next_num, **params) }}"
|
||||||
|
aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('paperDetailModal'));
|
||||||
|
const content = document.getElementById('paper-detail-content');
|
||||||
|
|
||||||
|
document.querySelectorAll('.paper-link').forEach(link => {
|
||||||
|
link.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const url = this.getAttribute('data-url');
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(html => {
|
||||||
|
content.innerHTML = html;
|
||||||
|
modal.show();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
content.innerHTML = '<div class="modal-body text-danger">Error loading details.</div>';
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
@ -12,18 +12,13 @@
|
|||||||
<p>
|
<p>
|
||||||
<strong>ISSN:</strong>
|
<strong>ISSN:</strong>
|
||||||
{% for issn in value.split(',') %}
|
{% for issn in value.split(',') %}
|
||||||
<a
|
<a href="https://www.worldcat.org/search?q=issn:{{ issn.strip() }}" target="_blank">{{ issn.strip() }}</a>{% if not
|
||||||
href="https://www.worldcat.org/search?q=issn:{{ issn.strip() }}"
|
loop.last %}, {% endif %} {% endfor %}
|
||||||
target="_blank"
|
|
||||||
>{{ issn.strip() }}</a
|
|
||||||
>{% if not loop.last %}, {% endif %} {% endfor %}
|
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
<strong>ISSN:</strong>
|
<strong>ISSN:</strong>
|
||||||
<a href="https://www.worldcat.org/search?q=issn:{{ value }}" target="_blank"
|
<a href="https://www.worldcat.org/search?q=issn:{{ value }}" target="_blank">{{ value }}</a>
|
||||||
>{{ value }}</a
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %} {% endif %} {% if not key.startswith('_') and key != 'metadata'
|
{% endif %} {% endif %} {% if not key.startswith('_') and key != 'metadata'
|
||||||
and key != 'doi' and key != 'issn' %}
|
and key != 'doi' and key != 'issn' %}
|
||||||
@ -34,4 +29,4 @@
|
|||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -1,15 +1,17 @@
|
|||||||
{% extends 'base.html' %} {% block content %}
|
{% extends "base.html" %} {% block content %}
|
||||||
<style>
|
<style>
|
||||||
.timeline {
|
.timeline {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
user-select: none; /* Prevent text selection during drag */
|
user-select: none;
|
||||||
|
/* Prevent text selection during drag */
|
||||||
}
|
}
|
||||||
|
|
||||||
.hour-block {
|
.hour-block {
|
||||||
width: 49px;
|
width: 49px;
|
||||||
height: 70px; /* Increased height to fit additional text */
|
height: 70px;
|
||||||
|
/* Increased height to fit additional text */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
@ -76,11 +78,8 @@
|
|||||||
messages %}
|
messages %}
|
||||||
<div id="flash-messages">
|
<div id="flash-messages">
|
||||||
{% for category, message in messages %}
|
{% for category, message in messages %}
|
||||||
<div
|
<div class="flash-message {{ category }}" x-data="{}"
|
||||||
class="flash-message {{ category }}"
|
x-init="setTimeout(() => $el.classList.add('fade'), 100); setTimeout(() => $el.remove(), 5000)">
|
||||||
x-data="{}"
|
|
||||||
x-init="setTimeout(() => $el.classList.add('fade'), 100); setTimeout(() => $el.remove(), 5000)"
|
|
||||||
>
|
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -118,39 +117,21 @@
|
|||||||
<strong x-text="volume"></strong> papers.
|
<strong x-text="volume"></strong> papers.
|
||||||
</p>
|
</p>
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<form
|
<form method="post" action="{{ url_for('schedule.schedule') }}" class="input-group w-50">
|
||||||
method="POST"
|
|
||||||
action="{{ url_for('schedule.schedule') }}"
|
|
||||||
class="input-group w-50"
|
|
||||||
>
|
|
||||||
<label class="input-group-text">Papers per day:</label>
|
<label class="input-group-text">Papers per day:</label>
|
||||||
<input
|
<input type="number" class="form-control" name="total_volume" value="{{ volume }}" min="1" max="1000"
|
||||||
type="number"
|
required />
|
||||||
class="form-control"
|
|
||||||
name="total_volume"
|
|
||||||
value="{{ volume }}"
|
|
||||||
min="1"
|
|
||||||
max="1000"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button type="submit" class="btn btn-primary">Update Volume</button>
|
<button type="submit" class="btn btn-primary">Update Volume</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mt-4">Current Schedule</h2>
|
<h2 class="mt-4">Current Schedule</h2>
|
||||||
<form method="POST" action="{{ url_for('schedule.schedule') }}">
|
<form method="post" action="{{ url_for('schedule.schedule') }}">
|
||||||
<div class="timeline mb-3" @mouseup="endDrag()" @mouseleave="endDrag()">
|
<div class="timeline mb-3" @mouseup="endDrag()" @mouseleave="endDrag()">
|
||||||
<template x-for="hour in Object.keys(schedule)" :key="hour">
|
<template x-for="hour in Object.keys(schedule)" :key="hour">
|
||||||
<div
|
<div class="hour-block" :id="'hour-' + hour" :data-hour="hour" :style="getBackgroundStyle(hour)"
|
||||||
class="hour-block"
|
:class="{'selected': isSelected(hour)}" @mousedown="startDrag($event, hour)" @mouseover="dragSelect(hour)">
|
||||||
:id="'hour-' + hour"
|
|
||||||
:data-hour="hour"
|
|
||||||
:style="getBackgroundStyle(hour)"
|
|
||||||
:class="{'selected': isSelected(hour)}"
|
|
||||||
@mousedown="startDrag($event, hour)"
|
|
||||||
@mouseover="dragSelect(hour)"
|
|
||||||
>
|
|
||||||
<div><strong x-text="formatHour(hour)"></strong></div>
|
<div><strong x-text="formatHour(hour)"></strong></div>
|
||||||
<div class="weight"><span x-text="schedule[hour]"></span></div>
|
<div class="weight"><span x-text="schedule[hour]"></span></div>
|
||||||
<div class="papers">
|
<div class="papers">
|
||||||
@ -163,25 +144,14 @@
|
|||||||
|
|
||||||
<div class="input-group mb-4 w-50">
|
<div class="input-group mb-4 w-50">
|
||||||
<label class="input-group-text">Set Weight:</label>
|
<label class="input-group-text">Set Weight:</label>
|
||||||
<input
|
<input type="number" step="0.1" min="0" max="5" x-model="newWeight" class="form-control" />
|
||||||
type="number"
|
<button type="button" class="btn btn-outline-primary" @click="applyWeight()">
|
||||||
step="0.1"
|
|
||||||
min="0"
|
|
||||||
max="5"
|
|
||||||
x-model="newWeight"
|
|
||||||
class="form-control"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-primary"
|
|
||||||
@click="applyWeight()"
|
|
||||||
>
|
|
||||||
Apply to Selected
|
Apply to Selected
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="/" class="btn btn-outline-secondary">⬅ Back</a>
|
<a href="{{ url_for(main.index) }}" class="btn btn-outline-secondary">⬅ Back</a>
|
||||||
<button type="submit" class="btn btn-success">💾 Save Schedule</button>
|
<button type="submit" class="btn btn-success">💾 Save Schedule</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -297,4 +267,4 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock content %}
|
@ -1,73 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% block content %}
|
|
||||||
<h1>Welcome to SciPaperLoader</h1>
|
|
||||||
|
|
||||||
{% if success %}
|
|
||||||
<div class="alert alert-success mt-3">{{ success }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if error_message %}
|
|
||||||
<div class="alert alert-warning mt-3">
|
|
||||||
<h4>{{ error_message }}</h4>
|
|
||||||
<table class="table table-sm table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Row</th>
|
|
||||||
<th>DOI</th>
|
|
||||||
<th>Error</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for error in error_samples %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ error.row }}</td>
|
|
||||||
<td>{{ error.doi }}</td>
|
|
||||||
<td>{{ error.error }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<a href="{{ url_for('upload.download_error_log') }}" class="btn btn-outline-secondary">Download Full Error Log</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<p><strong>Instructions:</strong> Please upload a CSV file containing academic paper metadata. The file must include the following columns:</p>
|
|
||||||
<ul>
|
|
||||||
<li><code>alternative_id</code> – an alternative title or abbreviation</li>
|
|
||||||
<li><code>journal</code> – the journal name</li>
|
|
||||||
<li><code>doi</code> – the digital object identifier</li>
|
|
||||||
<li><code>issn</code> – the ISSN of the journal</li>
|
|
||||||
<li><code>title</code> – the title of the paper</li>
|
|
||||||
</ul>
|
|
||||||
<p>The format of your CSV should resemble the response structure of the Crossref API's <code>/journals/{issn}/works</code> endpoint.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('upload.upload') }}" enctype="multipart/form-data">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">How to handle duplicate DOIs:</label>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="duplicate_strategy" value="skip" id="skip" checked>
|
|
||||||
<label class="form-check-label" for="skip">Skip duplicate entries</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="duplicate_strategy" value="update" id="update">
|
|
||||||
<label class="form-check-label" for="update">Update existing entries</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="file">Upload CSV File</label>
|
|
||||||
<input type="file" name="file" id="file" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group mt-3">
|
|
||||||
<label for="delimiter">Choose CSV Delimiter</label>
|
|
||||||
<select name="delimiter" id="delimiter" class="form-control">
|
|
||||||
<option value=",">Comma (,)</option>
|
|
||||||
<option value=";">Semicolon (;)</option>
|
|
||||||
<option value="\t">Tab (\\t)</option>
|
|
||||||
<option value="|">Pipe (|)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary mt-3">Upload</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
76
scipaperloader/templates/upload.html.jinja
Normal file
76
scipaperloader/templates/upload.html.jinja
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{% extends "base.html" %} {% block content %}
|
||||||
|
<h1>Welcome to SciPaperLoader</h1>
|
||||||
|
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success mt-3">{{ success }}</div>
|
||||||
|
{% endif %} {% if error_message %}
|
||||||
|
<div class="alert alert-warning mt-3">
|
||||||
|
<h4>{{ error_message }}</h4>
|
||||||
|
<table class="table table-sm table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Row</th>
|
||||||
|
<th>DOI</th>
|
||||||
|
<th>Error</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for error in error_samples %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ error.row }}</td>
|
||||||
|
<td>{{ error.doi }}</td>
|
||||||
|
<td>{{ error.error }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a href="{{ url_for('upload.download_error_log') }}" class="btn btn-outline-secondary">Download Full Error Log</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p>
|
||||||
|
<strong>Instructions:</strong> Please upload a CSV file containing academic
|
||||||
|
paper metadata. The file must include the following columns:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>alternative_id</code> – an alternative title or abbreviation</li>
|
||||||
|
<li><code>journal</code> – the journal name</li>
|
||||||
|
<li><code>doi</code> – the digital object identifier</li>
|
||||||
|
<li><code>issn</code> – the ISSN of the journal</li>
|
||||||
|
<li><code>title</code> – the title of the paper</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
The format of your CSV should resemble the response structure of the
|
||||||
|
Crossref API's <code>/journals/{issn}/works</code> endpoint.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="{{ url_for('upload.upload') }}" enctype="multipart/form-data">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">How to handle duplicate DOIs:</label>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="duplicate_strategy" value="skip" id="skip" checked />
|
||||||
|
<label class="form-check-label" for="skip">Skip duplicate entries</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="duplicate_strategy" value="update" id="update" />
|
||||||
|
<label class="form-check-label" for="update">Update existing entries</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="file">Upload CSV File</label>
|
||||||
|
<input type="file" name="file" id="file" class="form-control" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label for="delimiter">Choose CSV Delimiter</label>
|
||||||
|
<select name="delimiter" id="delimiter" class="form-control">
|
||||||
|
<option value=",">Comma (,)</option>
|
||||||
|
<option value=";">Semicolon (;)</option>
|
||||||
|
<option value="\t">Tab (\\t)</option>
|
||||||
|
<option value="|">Pipe (|)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Upload</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
Loading…
x
Reference in New Issue
Block a user