diff --git a/app.py b/app.py index 83a2a0e..111b837 100644 --- a/app.py +++ b/app.py @@ -133,8 +133,13 @@ cached: dict = { "css_navbar": open("styles/header.css", "r").read(), "css_response": open("styles/human_readable.css", "r").read(), "css_collapsible": open("styles/collapsible.css", "r").read(), + "css_page": open("styles/page.css", "r").read(), "js_collapsible": open("js/collapsible.js", "r").read(), "js_shortcode": open("js/shortcode.js", "r").read(), + "favicon_svg": open("static/favicon.svg", "r").read(), + "book_info": kjv_cur.execute( + 'SELECT "order", title_short, category, otnt, chapters FROM book_info ORDER BY "order"' + ).fetchall(), } # Fill the chapter list with unique chapters for seeking back and forward by index. @@ -149,17 +154,52 @@ for book in range(1, 67): "SELECT c FROM fts_kjv where b = ? ORDER BY id DESC LIMIT 1;", (book,) ).fetchone()[0] +# Cache book_id to bookname mapping for fast lookups +cached["book_id_to_name"] = {} +for row in kjv_cur.execute("SELECT b, n FROM key_english").fetchall(): + cached["book_id_to_name"][row[0]] = row[1] + +# Organize books by testament and category for index page +from collections import defaultdict +testament_data = {"OT": defaultdict(list), "NT": defaultdict(list)} +for book in cached["book_info"]: + book_order, title, category, testament, num_chapters = book + testament_data[testament][category].append({ + "title": title, + "chapters": num_chapters + }) +cached["book_info_for_index"] = dict(testament_data) + +# Render the index page once and cache it +with app.app_context(): + from flask import render_template_string + template_content = open("html/index.html", "r").read() + cached["html_index"] = render_template_string(template_content, testament_data=cached["book_info_for_index"]) + @app.get("/js/shortcode.js") def serve_shortcode_js(): - return Response(cached.get("js_shortcode"), mimetype='application/javascript') + response = Response(cached.get("js_shortcode"), mimetype='application/javascript') + response.headers['Cache-Control'] = 'public, max-age=31536000' + return response @app.get("/js/collapsible.js") def serve_collapsible_js(): - return Response(cached.get("js_collapsible"), mimetype='application/javascript') + response = Response(cached.get("js_collapsible"), mimetype='application/javascript') + response.headers['Cache-Control'] = 'public, max-age=31536000' + return response @app.get("/styles/page.css") def serve_page_css(): - return send_file("styles/page.css", mimetype='text/css') + response = Response(cached.get("css_page"), mimetype='text/css') + response.headers['Cache-Control'] = 'public, max-age=31536000' + return response + +@app.get("/favicon.ico") +@app.get("/favicon.svg") +def serve_favicon(): + response = Response(cached.get("favicon_svg"), mimetype='image/svg+xml') + response.headers['Cache-Control'] = 'public, max-age=31536000' + return response def generate_short_id(): remain = random.randrange(614_656, 17_210_367) # resolves to length of 5 @@ -174,11 +214,10 @@ def generate_short_id(): def lookup_bookname(book_id): - result = kjv_cur.execute( - "SELECT n FROM key_english WHERE b = ?;",(book_id,)).fetchone() - if result: - result = result[0] - return result + # Ensure book_id is an integer for cache lookup + if isinstance(book_id, str): + book_id = int(book_id) + return cached["book_id_to_name"].get(book_id) def lookup_book_id(bookname) -> int: @@ -744,7 +783,7 @@ def idx(shortcode=""): logging.info(f"/{shortcode}") return resolve_shortcode(shortcode) logging.info(f"/{shortcode}") - return send_file("html/index.html") + return cached["html_index"] @app.get("/fe") def frontend(): diff --git a/html/index.html b/html/index.html index 3ff263c..ef78ff6 100644 --- a/html/index.html +++ b/html/index.html @@ -1,77 +1,105 @@ - KJV API - Legacy Edition - + + + + -

KJV API (Legacy edition)

+
+

KJV API (Legacy edition)

-
-
-

- - - +
+ +
+

+ + + + +
-
-
-API ENDPOINT SPECIFICATION v 0.3
+    
+    

Old Testament

+ {% for category, books in testament_data.OT.items() %} +

{{ category }}

+
+ {% for book in books %} +
+ +
+ {% for chapter in range(1, book.chapters + 1) %} + {{ chapter }} + {% endfor %} +
+
+ {% endfor %} +
+ {% endfor %} + + +

New Testament

+ {% for category, books in testament_data.NT.items() %} +

{{ category }}

+
+ {% for book in books %} +
+ +
+ {% for chapter in range(1, book.chapters + 1) %} + {{ chapter }} + {% endfor %} +
+
+ {% endfor %} +
+ {% endfor %} + + + +
+
API ENDPOINT SPECIFICATION v 0.3
 ================================
 
-
-/find - Tries to show a verse, if it's not a verse
+/find - Tries to show a verse, if it's not a verse
        it will try to search your query. If it's a
        single query it will try to find a definition
        for the word as well. Only avaliable in "Rich"
@@ -213,9 +222,9 @@ URL PARAMETERS
 ==============
 view=
 - json - json-encoded response
-- plain - plaintext response
-- rich - pretty response with css
-default: json
+- plain - very simple html response
+- rich - pretty response with css and js
+Required parameter
 
 kw=
 - Used with /search /show/ /define /find
@@ -244,7 +253,7 @@ num=
 FEATURES
 ========
 - King James Only. (No heretical commentaries or false Bibles)
-- High quality source documents 
+- High quality source documents
 - Free
 - Developer hates the sodomites
 
@@ -257,7 +266,6 @@ radio selector for choosing your output format.
 - /define
 - /show
 
-
 EXAMPLES
 ========
 api.tld/random
@@ -266,7 +274,10 @@ api.tld/show?kw=john3:16,1john5:7&view=plain
 
 BUGS
 ====
-Suggestions, bug reports or improvements can be reported to @tyler@1611.social
-
-
+Suggestions, bug reports or improvements can be reported to @tyler@nicecrew.digital
+
+ + + + diff --git a/js/collapsible.js b/js/collapsible.js index 4621978..f17bb8e 100644 --- a/js/collapsible.js +++ b/js/collapsible.js @@ -3,12 +3,34 @@ var i; for (i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function() { - this.classList.toggle("active"); var content = this.nextElementSibling; - if (content.style.display === "block") { + var isCurrentlyOpen = content.style.display === "block" || content.style.display === "grid"; + + // If this collapsible has a parent with book-grid class, close all siblings in that grid + var parentGrid = this.closest('.book-grid'); + if (parentGrid) { + var siblings = parentGrid.querySelectorAll('.collapsible'); + for (var j = 0; j < siblings.length; j++) { + if (siblings[j] !== this) { + siblings[j].classList.remove("active"); + var siblingContent = siblings[j].nextElementSibling; + if (siblingContent) { + siblingContent.style.display = "none"; + } + } + } + } + + // Toggle current element + this.classList.toggle("active"); + if (isCurrentlyOpen) { content.style.display = "none"; } else { - content.style.display = "block"; + if (content.classList.contains('chapter-grid')) { + content.style.display = "grid"; + } else { + content.style.display = "block"; + } } }); } diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..aed73bb --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,5 @@ + + + + +