aboutsummaryrefslogtreecommitdiff
path: root/templates/index.html
diff options
context:
space:
mode:
authorSam Scholten2026-06-14 20:00:15 +1000
committerSam Scholten2026-06-14 20:00:15 +1000
commitdecc46c876e7b5552f5f5ecac4ee4f1a64ad1d62 (patch)
tree46875e236a062189115c0cd8ed8f1d82980c16b7 /templates/index.html
downloadabvjt-main.tar.gz
abvjt-main.zip
Initial implementation: scrape, serve, UI, container, deploymentHEADmain
Diffstat (limited to 'templates/index.html')
-rw-r--r--templates/index.html211
1 files changed, 211 insertions, 0 deletions
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..cccd378
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Journal Abbreviations Tool</title>
+ <style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ body {
+ font-family: monospace;
+ background: #fff;
+ color: #000;
+ padding: 20px;
+ line-height: 1.6;
+ }
+ h1 {
+ font-size: 1.2em;
+ font-weight: bold;
+ margin-bottom: 20px;
+ border-bottom: 1px solid #000;
+ padding-bottom: 10px;
+ }
+ .search-form {
+ border: 1px solid #000;
+ padding: 20px;
+ margin-bottom: 20px;
+ }
+ input[type="text"] {
+ font-family: monospace;
+ font-size: 1em;
+ border: 1px solid #000;
+ padding: 5px;
+ width: 100%;
+ }
+ button {
+ font-family: monospace;
+ font-size: 1em;
+ background: #fff;
+ color: #000;
+ border: 1px solid #000;
+ padding: 5px 15px;
+ cursor: pointer;
+ margin-top: 15px;
+ }
+ button:hover {
+ background: #000;
+ color: #fff;
+ }
+ .results {
+ border: 1px solid #000;
+ padding: 0;
+ }
+ .result {
+ border-bottom: 1px solid #ccc;
+ padding: 10px;
+ }
+ .result:last-child {
+ border-bottom: none;
+ }
+ .full-name {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+ .abbrev {
+ color: #666;
+ font-size: 0.9em;
+ }
+ .copy-buttons {
+ margin-top: 8px;
+ }
+ .copy-buttons button {
+ font-size: 0.85em;
+ padding: 3px 10px;
+ margin-right: 10px;
+ }
+ .no-results, .error {
+ padding: 10px;
+ border: 1px solid #000;
+ }
+ .error {
+ border-color: #f00;
+ color: #f00;
+ }
+ footer {
+ margin-top: 30px;
+ border-top: 1px solid #000;
+ padding-top: 10px;
+ font-size: 0.9em;
+ }
+ </style>
+</head>
+<body>
+ <h1>Journal Abbreviations Tool</h1>
+ <form class="search-form" id="searchForm">
+ <input type="text" id="query" name="q" placeholder="search journal name..." autofocus autocomplete="off">
+ <br>
+ <button type="submit">search</button>
+ </form>
+ <div id="results"></div>
+ <footer>
+ <a href="/api/health">health</a> &mdash;
+ data from <a href="https://wos-help.webofscience.com/WOKRS535R111/help/WOS/A_abrvjt.html">Web of Science</a>
+ </footer>
+ <script>
+ const form = document.getElementById('searchForm');
+ const queryInput = document.getElementById('query');
+ const resultsDiv = document.getElementById('results');
+
+ function toSentenceCase(str) {
+ return str.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
+ }
+
+ function makeResultElement(journal) {
+ const sent = toSentenceCase(journal.abbreviation);
+
+ const container = document.createElement('div');
+ container.className = 'result';
+
+ const nameDiv = document.createElement('div');
+ nameDiv.className = 'full-name';
+ nameDiv.textContent = journal.full_name;
+ container.appendChild(nameDiv);
+
+ const abbrevDiv = document.createElement('div');
+ abbrevDiv.className = 'abbrev';
+ abbrevDiv.textContent = journal.abbreviation;
+ container.appendChild(abbrevDiv);
+
+ const buttonsDiv = document.createElement('div');
+ buttonsDiv.className = 'copy-buttons';
+
+ const btnSent = document.createElement('button');
+ btnSent.textContent = 'copy sentence case';
+ btnSent.addEventListener('click', () => copyText(sent));
+ buttonsDiv.appendChild(btnSent);
+
+ const btnCaps = document.createElement('button');
+ btnCaps.textContent = 'copy ALL CAPS';
+ btnCaps.addEventListener('click', () => copyText(journal.abbreviation));
+ buttonsDiv.appendChild(btnCaps);
+
+ container.appendChild(buttonsDiv);
+ return container;
+ }
+
+ function renderResults(journals) {
+ if (journals.length === 0) {
+ resultsDiv.innerHTML = '<div class="no-results">no results</div>';
+ return;
+ }
+
+ const wrapper = document.createElement('div');
+ wrapper.className = 'results';
+ for (const j of journals) {
+ wrapper.appendChild(makeResultElement(j));
+ }
+ resultsDiv.innerHTML = '';
+ resultsDiv.appendChild(wrapper);
+ }
+
+ async function copyText(text) {
+ try {
+ await navigator.clipboard.writeText(text);
+ } catch (e) {
+ const ta = document.createElement('textarea');
+ ta.value = text;
+ document.body.appendChild(ta);
+ ta.select();
+ document.execCommand('copy');
+ document.body.removeChild(ta);
+ }
+ }
+
+ async function doSearch() {
+ const q = queryInput.value.trim();
+ if (!q) {
+ resultsDiv.innerHTML = '';
+ return;
+ }
+ if (q.length < 2) {
+ return;
+ }
+ resultsDiv.innerHTML = '<div class="no-results">loading...</div>';
+ try {
+ const resp = await fetch('/api/search?q=' + encodeURIComponent(q));
+ if (!resp.ok) throw new Error(resp.status + ' ' + resp.statusText);
+ const data = await resp.json();
+ renderResults(data);
+ } catch (err) {
+ const errorDiv = document.createElement('div');
+ errorDiv.className = 'error';
+ errorDiv.textContent = err.message;
+ resultsDiv.innerHTML = '';
+ resultsDiv.appendChild(errorDiv);
+ }
+ }
+
+ let debounceTimer;
+ queryInput.addEventListener('input', () => {
+ clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(doSearch, 200);
+ });
+
+ form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ clearTimeout(debounceTimer);
+ doSearch();
+ });
+ </script>
+</body>
+</html>