Fancier homepage and a bit of performance improvement through aggressive caching.
This commit is contained in:
@@ -133,8 +133,13 @@ cached: dict = {
|
|||||||
"css_navbar": open("styles/header.css", "r").read(),
|
"css_navbar": open("styles/header.css", "r").read(),
|
||||||
"css_response": open("styles/human_readable.css", "r").read(),
|
"css_response": open("styles/human_readable.css", "r").read(),
|
||||||
"css_collapsible": open("styles/collapsible.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_collapsible": open("js/collapsible.js", "r").read(),
|
||||||
"js_shortcode": open("js/shortcode.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.
|
# 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,)
|
"SELECT c FROM fts_kjv where b = ? ORDER BY id DESC LIMIT 1;", (book,)
|
||||||
).fetchone()[0]
|
).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")
|
@app.get("/js/shortcode.js")
|
||||||
def serve_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")
|
@app.get("/js/collapsible.js")
|
||||||
def serve_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")
|
@app.get("/styles/page.css")
|
||||||
def serve_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():
|
def generate_short_id():
|
||||||
remain = random.randrange(614_656, 17_210_367) # resolves to length of 5
|
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):
|
def lookup_bookname(book_id):
|
||||||
result = kjv_cur.execute(
|
# Ensure book_id is an integer for cache lookup
|
||||||
"SELECT n FROM key_english WHERE b = ?;",(book_id,)).fetchone()
|
if isinstance(book_id, str):
|
||||||
if result:
|
book_id = int(book_id)
|
||||||
result = result[0]
|
return cached["book_id_to_name"].get(book_id)
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_book_id(bookname) -> int:
|
def lookup_book_id(bookname) -> int:
|
||||||
@@ -744,7 +783,7 @@ def idx(shortcode=""):
|
|||||||
logging.info(f"/{shortcode}")
|
logging.info(f"/{shortcode}")
|
||||||
return resolve_shortcode(shortcode)
|
return resolve_shortcode(shortcode)
|
||||||
logging.info(f"/{shortcode}")
|
logging.info(f"/{shortcode}")
|
||||||
return send_file("html/index.html")
|
return cached["html_index"]
|
||||||
|
|
||||||
@app.get("/fe")
|
@app.get("/fe")
|
||||||
def frontend():
|
def frontend():
|
||||||
|
|||||||
+172
-161
@@ -1,77 +1,105 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>KJV API - Legacy Edition</title>
|
<title>KJV API - Legacy Edition</title>
|
||||||
<style>html,body{background-color:#1a1a1a}</style>
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<script src="/js/collapsible.js" defer></script>
|
||||||
<style>
|
<style>
|
||||||
/* Modern KJV API Styling - Index Page */
|
/* Bible Book Styles */
|
||||||
|
.book-grid {
|
||||||
:root {
|
display: grid;
|
||||||
--color-bg-primary: #1a1a1a;
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
--color-bg-secondary: #252525;
|
gap: 0.5rem;
|
||||||
--color-bg-tertiary: #2f2f2f;
|
padding: 0.5rem 0;
|
||||||
--color-text-primary: #e8e8e8;
|
|
||||||
--color-text-secondary: #b8b8b8;
|
|
||||||
--color-text-muted: #888888;
|
|
||||||
--color-accent-primary: #6b9bd1;
|
|
||||||
--color-accent-hover: #85aede;
|
|
||||||
--color-link: #6b9bd1;
|
|
||||||
--color-link-hover: #85aede;
|
|
||||||
--color-border: #3a3a3a;
|
|
||||||
--color-shadow: rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
--font-scripture: Georgia, "Crimson Text", "Times New Roman", serif;
|
|
||||||
--font-mono: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
|
|
||||||
|
|
||||||
--space-xs: 0.25rem;
|
|
||||||
--space-sm: 0.5rem;
|
|
||||||
--space-md: 1rem;
|
|
||||||
--space-lg: 1.5rem;
|
|
||||||
--space-xl: 2rem;
|
|
||||||
|
|
||||||
--content-max-width: 820px;
|
|
||||||
--border-radius: 8px;
|
|
||||||
--border-radius-sm: 4px;
|
|
||||||
--transition-fast: 150ms ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
.book-button {
|
||||||
box-sizing: border-box;
|
background-color: var(--color-bg-tertiary);
|
||||||
}
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
body {
|
padding: 0.75rem 1rem;
|
||||||
background-color: var(--color-bg-primary);
|
text-align: left;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
margin: 0;
|
transition: all var(--transition-fast);
|
||||||
padding: var(--space-xl);
|
cursor: pointer;
|
||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
line-height: 1.6;
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
.book-button:hover {
|
||||||
font-family: var(--font-ui);
|
background-color: var(--color-accent-primary);
|
||||||
font-size: 16px;
|
color: var(--color-bg-primary);
|
||||||
|
border-color: var(--color-accent-primary);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.book-button.active {
|
||||||
max-width: var(--content-max-width);
|
background-color: var(--color-accent-primary);
|
||||||
margin: 0 auto;
|
color: var(--color-bg-primary);
|
||||||
padding: var(--space-lg);
|
border-color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
.chapter-grid {
|
||||||
color: var(--color-text-primary);
|
display: none;
|
||||||
font-size: 2.5rem;
|
grid-template-columns: repeat(auto-fill, minmax(45px, 1fr));
|
||||||
margin-bottom: var(--space-lg);
|
gap: 0.4rem;
|
||||||
font-weight: 700;
|
padding: 1rem;
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chapter-link {
|
||||||
|
background-color: var(--color-bg-tertiary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
padding: 0.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
.chapter-link:hover {
|
||||||
|
background-color: var(--color-accent-primary);
|
||||||
|
color: var(--color-bg-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn {
|
||||||
|
background-color: var(--color-accent-primary);
|
||||||
|
color: var(--color-bg-primary);
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn:hover {
|
||||||
|
background-color: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
padding: var(--space-xl);
|
padding: var(--space-xl);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
@@ -80,124 +108,105 @@ form {
|
|||||||
box-shadow: 0 2px 8px var(--color-shadow);
|
box-shadow: 0 2px 8px var(--color-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
.testament-header {
|
||||||
display: block;
|
color: var(--color-accent-primary);
|
||||||
margin-bottom: var(--space-sm);
|
font-size: 1.8rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 2px solid var(--color-accent-primary);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-link);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-link-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text] {
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--space-sm) var(--space-md);
|
|
||||||
margin: var(--space-sm) 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-family: var(--font-ui);
|
|
||||||
background-color: var(--color-bg-tertiary);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
border: 2px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text]:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-accent-primary);
|
|
||||||
box-shadow: 0 0 0 3px rgba(107, 155, 209, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=button],
|
|
||||||
input[type=submit],
|
|
||||||
input[type=reset] {
|
|
||||||
font-family: var(--font-ui);
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: var(--color-accent-primary);
|
|
||||||
border: none;
|
|
||||||
color: var(--color-bg-primary);
|
|
||||||
padding: var(--space-sm) var(--space-lg);
|
|
||||||
text-decoration: none;
|
|
||||||
margin: var(--space-sm) 0;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
transition: background-color var(--transition-fast), transform var(--transition-fast);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=button]:hover,
|
|
||||||
input[type=submit]:hover,
|
|
||||||
input[type=reset]:hover {
|
|
||||||
background-color: var(--color-accent-hover);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: var(--color-bg-tertiary);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: var(--space-lg);
|
|
||||||
overflow-x: auto;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
body {
|
.book-grid {
|
||||||
padding: var(--space-md);
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
.quick-actions {
|
||||||
font-size: 2rem;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
.quick-btn {
|
||||||
padding: var(--space-lg);
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
<h1>KJV API (Legacy edition)</h1>
|
<div class="container">
|
||||||
|
<h1>KJV API (Legacy edition)</h1>
|
||||||
|
|
||||||
<form action="/find">
|
<div class="search-section">
|
||||||
<label for='kw'>Keyword Search</label><br>
|
<form action="/find">
|
||||||
<input type="text" name="kw"><br><br>
|
<label for='kw'>Search Scripture</label><br>
|
||||||
<input type="hidden" name="view" value="rich"">
|
<input type="text" name="kw" id="kw" placeholder="Enter verse reference or search keywords..."><br><br>
|
||||||
<input type="submit">
|
<input type="hidden" name="view" value="rich">
|
||||||
<form>
|
<input type="submit" value="Search">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="quick-actions">
|
||||||
|
<a href="/random?view=rich" class="quick-btn">Random Verse</a>
|
||||||
|
<a href="/votd?view=rich" class="quick-btn">Verse of the Day</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<pre>
|
<!-- OLD TESTAMENT -->
|
||||||
<code>
|
<h2 class="testament-header">Old Testament</h2>
|
||||||
API ENDPOINT SPECIFICATION v 0.3
|
{% for category, books in testament_data.OT.items() %}
|
||||||
|
<h3 class="category-header">{{ category }}</h3>
|
||||||
|
<div class="book-grid">
|
||||||
|
{% for book in books %}
|
||||||
|
<div>
|
||||||
|
<button class="book-button collapsible" data-book="{{ book.title }}">
|
||||||
|
{{ book.title }}
|
||||||
|
</button>
|
||||||
|
<div class="chapter-grid content">
|
||||||
|
{% for chapter in range(1, book.chapters + 1) %}
|
||||||
|
<a href="/show?kw={{ book.title }} {{ chapter }}&view=rich" class="chapter-link">{{ chapter }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- NEW TESTAMENT -->
|
||||||
|
<h2 class="testament-header">New Testament</h2>
|
||||||
|
{% for category, books in testament_data.NT.items() %}
|
||||||
|
<h3 class="category-header">{{ category }}</h3>
|
||||||
|
<div class="book-grid">
|
||||||
|
{% for book in books %}
|
||||||
|
<div>
|
||||||
|
<button class="book-button collapsible" data-book="{{ book.title }}">
|
||||||
|
{{ book.title }}
|
||||||
|
</button>
|
||||||
|
<div class="chapter-grid content">
|
||||||
|
{% for chapter in range(1, book.chapters + 1) %}
|
||||||
|
<a href="/show?kw={{ book.title }} {{ chapter }}&view=rich" class="chapter-link">{{ chapter }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- API DOCUMENTATION -->
|
||||||
|
<button class="collapsible" style="margin-top: 2rem;">API Documentation</button>
|
||||||
|
<div class="content">
|
||||||
|
<pre><code>API ENDPOINT SPECIFICATION v 0.3
|
||||||
================================
|
================================
|
||||||
|
|
||||||
<a href="/find">
|
<a href="/find">/find</a> - Tries to show a verse, if it's not a verse
|
||||||
/find</a> - Tries to show a verse, if it's not a verse
|
|
||||||
it will try to search your query. If it's a
|
it will try to search your query. If it's a
|
||||||
single query it will try to find a definition
|
single query it will try to find a definition
|
||||||
for the word as well. Only avaliable in "Rich"
|
for the word as well. Only avaliable in "Rich"
|
||||||
@@ -213,9 +222,9 @@ URL PARAMETERS
|
|||||||
==============
|
==============
|
||||||
view=
|
view=
|
||||||
- json - json-encoded response
|
- json - json-encoded response
|
||||||
- plain - plaintext response
|
- plain - very simple html response
|
||||||
- rich - pretty response with css
|
- rich - pretty response with css and js
|
||||||
default: json
|
Required parameter
|
||||||
|
|
||||||
kw=
|
kw=
|
||||||
- Used with /search /show/ /define /find
|
- Used with /search /show/ /define /find
|
||||||
@@ -257,7 +266,6 @@ radio selector for choosing your output format.
|
|||||||
- /define
|
- /define
|
||||||
- /show
|
- /show
|
||||||
|
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
========
|
========
|
||||||
api.tld/random
|
api.tld/random
|
||||||
@@ -266,7 +274,10 @@ api.tld/show?kw=john3:16,1john5:7&view=plain
|
|||||||
|
|
||||||
BUGS
|
BUGS
|
||||||
====
|
====
|
||||||
Suggestions, bug reports or improvements can be reported to <a href="https://1611.social/@tyler">@tyler@1611.social</a>
|
Suggestions, bug reports or improvements can be reported to <a href="https://nicecrew.digital/@tyler">@tyler@nicecrew.digital</a></code></pre>
|
||||||
</code>
|
</div>
|
||||||
</pre>
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+24
-2
@@ -3,12 +3,34 @@ var i;
|
|||||||
|
|
||||||
for (i = 0; i < coll.length; i++) {
|
for (i = 0; i < coll.length; i++) {
|
||||||
coll[i].addEventListener("click", function() {
|
coll[i].addEventListener("click", function() {
|
||||||
this.classList.toggle("active");
|
|
||||||
var content = this.nextElementSibling;
|
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";
|
content.style.display = "none";
|
||||||
|
} else {
|
||||||
|
if (content.classList.contains('chapter-grid')) {
|
||||||
|
content.style.display = "grid";
|
||||||
} else {
|
} else {
|
||||||
content.style.display = "block";
|
content.style.display = "block";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<path fill="#6b9bd1" d="M16 4 C10 4 6 6 4 8 L4 26 C6 24 10 22 16 22 C22 22 26 24 28 26 L28 8 C26 6 22 4 16 4 Z"/>
|
||||||
|
<path fill="#85aede" d="M16 4 L16 22 C22 22 26 24 28 26 L28 8 C26 6 22 4 16 4 Z"/>
|
||||||
|
<line x1="16" y1="4" x2="16" y2="22" stroke="#3a3a3a" stroke-width="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
Reference in New Issue
Block a user