Merge pull request 'develop ready to be stable enough' (#5) from develop into stable
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
@@ -1,2 +1,5 @@
|
|||||||
logs/*.log
|
logs/*.log
|
||||||
*__pycache__*
|
*__pycache__*
|
||||||
|
data/shortcodes.db
|
||||||
|
data/seq.db
|
||||||
|
data/sermons.db
|
||||||
|
|||||||
@@ -97,6 +97,26 @@ if not kjv_cur:
|
|||||||
if not sermon_cur:
|
if not sermon_cur:
|
||||||
print("WARN: Error connecting to Sermon Database")
|
print("WARN: Error connecting to Sermon Database")
|
||||||
|
|
||||||
|
# Build grammatical variants lookup from word groups
|
||||||
|
variant_groups = json.load(open("data/grammatical_variants.json", "r"))
|
||||||
|
grammatical_variants_map = {}
|
||||||
|
for group in variant_groups:
|
||||||
|
for word in group:
|
||||||
|
# Map each word to all other words in its group
|
||||||
|
grammatical_variants_map[word] = [w for w in group if w != word]
|
||||||
|
|
||||||
|
# Build theological synonyms lookup from word groups
|
||||||
|
theological_synonym_groups = json.load(open("data/theological_synonyms.json", "r"))
|
||||||
|
theological_synonyms_map = {}
|
||||||
|
for group in theological_synonym_groups:
|
||||||
|
for word in group:
|
||||||
|
# Map each word to other words in its group
|
||||||
|
theological_synonyms_map[word] = [w for w in group if w != word]
|
||||||
|
|
||||||
|
# Load concept mappings for common theological terms not in KJV
|
||||||
|
concept_mappings = json.load(open("data/concept_mappings.json", "r"))
|
||||||
|
concept_mappings_dict = {mapping["query"].lower(): mapping for mapping in concept_mappings}
|
||||||
|
|
||||||
cached: dict = {
|
cached: dict = {
|
||||||
"verse_ids": [i[0] for i in kjv_cur.execute("SELECT id FROM fts_kjv;").fetchall()],
|
"verse_ids": [i[0] for i in kjv_cur.execute("SELECT id FROM fts_kjv;").fetchall()],
|
||||||
"book_names": [
|
"book_names": [
|
||||||
@@ -105,13 +125,21 @@ cached: dict = {
|
|||||||
"chapter_list": [],
|
"chapter_list": [],
|
||||||
"books_chapter_lengths": {},
|
"books_chapter_lengths": {},
|
||||||
"json_dictionary": json.load(open("data/1828_Webster_KJV.json", "r")),
|
"json_dictionary": json.load(open("data/1828_Webster_KJV.json", "r")),
|
||||||
|
"grammatical_variants": grammatical_variants_map,
|
||||||
|
"theological_synonyms": theological_synonyms_map,
|
||||||
|
"concept_mappings": concept_mappings_dict,
|
||||||
"html_response": open("html/response.html", "r").read(),
|
"html_response": open("html/response.html", "r").read(),
|
||||||
"html_navbar": open("html/navbar.html", "r").read(),
|
"html_navbar": open("html/navbar.html", "r").read(),
|
||||||
"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.
|
||||||
@@ -126,13 +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")
|
||||||
|
def serve_page_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
|
||||||
@@ -147,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:
|
||||||
@@ -448,6 +514,15 @@ def parse_query(query_override: str = None):
|
|||||||
return send_file("html/find.html")
|
return send_file("html/find.html")
|
||||||
if query.endswith(","):
|
if query.endswith(","):
|
||||||
query = query[:-1]
|
query = query[:-1]
|
||||||
|
|
||||||
|
# Check if query contains both letters and digits (required for reference parsing)
|
||||||
|
# If only numbers or only letters, skip reference parsing and go to keyword search
|
||||||
|
has_letters = any(char.isalpha() for char in query)
|
||||||
|
has_digits = any(char.isdigit() for char in query)
|
||||||
|
|
||||||
|
if not (has_letters and has_digits):
|
||||||
|
result = search()
|
||||||
|
else:
|
||||||
result = show(kw=query, human_readable=human_readable)
|
result = show(kw=query, human_readable=human_readable)
|
||||||
if not hasattr(result, "__len__"):
|
if not hasattr(result, "__len__"):
|
||||||
logging.debug(f"parse_query.show = '{result}'")
|
logging.debug(f"parse_query.show = '{result}'")
|
||||||
@@ -455,30 +530,40 @@ def parse_query(query_override: str = None):
|
|||||||
|
|
||||||
if len(result) > 17:
|
if len(result) > 17:
|
||||||
return result
|
return result
|
||||||
result = "<!DOCTYPE html>"
|
# Reference parsing failed, fall back to keyword search
|
||||||
result += search()
|
result = search()
|
||||||
result += "<style>" + cached["css_collapsible"] + "</style>"
|
# Remove the closing </div> from search() so we can add dictionary content inside .responses
|
||||||
result += (
|
if result.endswith("</div>"):
|
||||||
"<h3>1828 Webster Definitions</h3> Click on the word to get the definition."
|
result = result[:-6] # Remove last </div>
|
||||||
)
|
|
||||||
|
# Check if we have any dictionary definitions before showing the section
|
||||||
split_query = query.replace(",", " ").split(" ")
|
split_query = query.replace(",", " ").split(" ")
|
||||||
if len(split_query) > 20:
|
if len(split_query) > 20:
|
||||||
return "Your Query is too long. 20 Words or less."
|
return "Your Query is too long. 20 Words or less."
|
||||||
|
|
||||||
|
definitions_html = ""
|
||||||
|
has_definitions = False
|
||||||
for word in split_query:
|
for word in split_query:
|
||||||
definition = define(word)
|
definition = define(word)
|
||||||
if not definition:
|
if not definition:
|
||||||
continue
|
continue
|
||||||
result += (
|
has_definitions = True
|
||||||
|
definitions_html += (
|
||||||
"<button type='button' class='collapsible'>"
|
"<button type='button' class='collapsible'>"
|
||||||
+ word.capitalize()
|
+ word.capitalize()
|
||||||
+ "</button>"
|
+ "</button>"
|
||||||
)
|
)
|
||||||
result += "<div class='definition'>" + definition + "</div>"
|
definitions_html += "<div class='definition'>" + definition + "</div>"
|
||||||
logging.debug(f"parse_query.define = '{word}'")
|
logging.debug(f"parse_query.define = '{word}'")
|
||||||
|
|
||||||
|
# Only add the dictionary section if we found at least one definition
|
||||||
|
if has_definitions:
|
||||||
|
result += "<style>" + cached["css_collapsible"] + "</style>"
|
||||||
|
result += "<div class='dictionary-section'><h3>1828 Webster Definitions</h3><p>Click on the word to get the definition.</p></div>"
|
||||||
|
result += definitions_html
|
||||||
result += "<script src='/js/collapsible.js'></script>"
|
result += "<script src='/js/collapsible.js'></script>"
|
||||||
result += "<hr>"
|
|
||||||
# result += search()
|
result += "</div>"
|
||||||
#logging.debug(f"parse_query.search = '{result}'")
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -604,7 +689,7 @@ def show(kw=None, get_start_verse_id=False, human_readable=False):
|
|||||||
if arg_view == "plain":
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = "<br>".join(response_list)
|
||||||
elif arg_view == "rich" or human_readable:
|
elif arg_view == "rich" or human_readable:
|
||||||
response = "<br>".join(response_list)
|
response = "".join(response_list)
|
||||||
else:
|
else:
|
||||||
response = jsonify(response_list)
|
response = jsonify(response_list)
|
||||||
return response
|
return response
|
||||||
@@ -643,7 +728,7 @@ def response_rich(scripture):
|
|||||||
|
|
||||||
def scripture_response(scriptures, human_readable=False):
|
def scripture_response(scriptures, human_readable=False):
|
||||||
response = []
|
response = []
|
||||||
arg_view = request.args.get("view", "json")
|
arg_view = request.args.get("view", "rich")
|
||||||
logging.debug(f"search ->requested_format = {arg_view}")
|
logging.debug(f"search ->requested_format = {arg_view}")
|
||||||
if arg_view == "plain":
|
if arg_view == "plain":
|
||||||
for scripture in scriptures:
|
for scripture in scriptures:
|
||||||
@@ -670,8 +755,8 @@ def scripture_response(scriptures, human_readable=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
response.append(cached["css_navbar"])
|
response.append(cached["css_navbar"])
|
||||||
response.append("<div class='responses'")
|
response.append("<div class='responses'>")
|
||||||
response.append(f"{len(scriptures)} Results")
|
response.append(f"<div class='results-count'>{len(scriptures)} Results</div>")
|
||||||
for scripture in scriptures:
|
for scripture in scriptures:
|
||||||
response.append(response_rich(scripture))
|
response.append(response_rich(scripture))
|
||||||
response.append("</div>")
|
response.append("</div>")
|
||||||
@@ -698,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():
|
||||||
@@ -720,7 +805,7 @@ def random_verse():
|
|||||||
if arg_view == "plain":
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = "<br>".join(response_list)
|
||||||
elif arg_view == "rich":
|
elif arg_view == "rich":
|
||||||
response = "<br>".join(response_list)
|
response = "".join(response_list)
|
||||||
else:
|
else:
|
||||||
response = jsonify(response_list)
|
response = jsonify(response_list)
|
||||||
#logging.debug(f"Search Response: {response}")
|
#logging.debug(f"Search Response: {response}")
|
||||||
@@ -741,7 +826,7 @@ def verse_of_the_day():
|
|||||||
if arg_view == "plain":
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = "<br>".join(response_list)
|
||||||
elif arg_view == "rich":
|
elif arg_view == "rich":
|
||||||
response = "<br>".join(response_list)
|
response = "".join(response_list)
|
||||||
else:
|
else:
|
||||||
response = jsonify(response_list)
|
response = jsonify(response_list)
|
||||||
#logging.debug(f"Search Response: {response}")
|
#logging.debug(f"Search Response: {response}")
|
||||||
@@ -769,19 +854,287 @@ def search():
|
|||||||
return send_file("html/search.html")
|
return send_file("html/search.html")
|
||||||
else:
|
else:
|
||||||
return send_file("html/search.html")
|
return send_file("html/search.html")
|
||||||
|
|
||||||
|
# Check if this is a common theological concept not in KJV
|
||||||
|
concept_mapping = cached["concept_mappings"].get(cleaned_keywords.lower(), None)
|
||||||
|
concept_notice = None
|
||||||
|
if concept_mapping:
|
||||||
|
# Build hyperlinks for all search terms
|
||||||
|
original_query = cleaned_keywords
|
||||||
|
search_links = []
|
||||||
|
for term in concept_mapping["search_terms"]:
|
||||||
|
search_link = f"/search?kw={term.replace(' ', '+')}&view=rich"
|
||||||
|
search_links.append(f"<a href='{search_link}'><strong>{term}</strong></a>")
|
||||||
|
|
||||||
|
links_html = ", ".join(search_links)
|
||||||
|
concept_notice = f"<div class='correction-notice'><strong>{original_query.capitalize()}</strong>: {concept_mapping['explanation']}<br>You may find what you are looking for with these search terms: {links_html}</div>"
|
||||||
|
|
||||||
|
# Use the first search term for the actual search
|
||||||
|
cleaned_keywords = concept_mapping["search_terms"][0]
|
||||||
|
|
||||||
|
def score_result_by_proximity(verse_text, query_words):
|
||||||
|
"""
|
||||||
|
Score a verse based on how close together the query words appear.
|
||||||
|
Higher score = words appear closer together.
|
||||||
|
"""
|
||||||
|
verse_lower = verse_text.lower()
|
||||||
|
|
||||||
|
# Find all positions of each query word
|
||||||
|
word_positions = {}
|
||||||
|
for word in query_words:
|
||||||
|
word_lower = word.lower()
|
||||||
|
positions = []
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
pos = verse_lower.find(word_lower, start)
|
||||||
|
if pos == -1:
|
||||||
|
break
|
||||||
|
positions.append(pos)
|
||||||
|
start = pos + 1
|
||||||
|
word_positions[word] = positions
|
||||||
|
|
||||||
|
# If any word is missing, score is 0
|
||||||
|
if any(len(positions) == 0 for positions in word_positions.values()):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Calculate minimum distance between all query words
|
||||||
|
# For each combination of positions, find the span (max_pos - min_pos)
|
||||||
|
# Smaller span = higher score
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
# Get all combinations of positions for all words
|
||||||
|
all_position_combos = list(itertools.product(*word_positions.values()))
|
||||||
|
|
||||||
|
if not all_position_combos:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Find the combination with minimum span
|
||||||
|
min_span = float('inf')
|
||||||
|
for combo in all_position_combos:
|
||||||
|
span = max(combo) - min(combo)
|
||||||
|
if span < min_span:
|
||||||
|
min_span = span
|
||||||
|
|
||||||
|
# Convert span to score (smaller span = higher score)
|
||||||
|
# Use inverse: 1000 / (span + 1) so adjacent words score ~1000, far apart score low
|
||||||
|
score = 1000.0 / (min_span + 1)
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
results = lookup_fts(cleaned_keywords)
|
results = lookup_fts(cleaned_keywords)
|
||||||
|
|
||||||
|
# Sort results by proximity score if we have multiple query words
|
||||||
|
if results and len(cleaned_keywords.split()) > 1:
|
||||||
|
query_words = cleaned_keywords.split()
|
||||||
|
scored_results = []
|
||||||
|
for result in results:
|
||||||
|
verse_text = result[4] # The text is at index 4
|
||||||
|
score = score_result_by_proximity(verse_text, query_words)
|
||||||
|
scored_results.append((score, result))
|
||||||
|
|
||||||
|
# Sort by score descending, then keep just the results
|
||||||
|
scored_results.sort(key=lambda x: x[0], reverse=True)
|
||||||
|
results = [result for score, result in scored_results]
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
h = hunspell.HunSpell("data/kjv.dic", "data/kjv.aff")
|
# PRIORITY 1: Try grammatical variant expansion first
|
||||||
suggestions = h.suggest(cleaned_keywords)
|
words = cleaned_keywords.split()
|
||||||
return "{} not found, try one of the following: {}".format(
|
|
||||||
cleaned_keywords, ", ".join(suggestions)
|
def sort_variants_by_similarity(original, variants):
|
||||||
)
|
"""Sort variants by morphological similarity: forwards (longer/more specific) then backwards (shorter/simpler)"""
|
||||||
|
def get_common_prefix_length(w1, w2):
|
||||||
|
"""Find how many characters match from the start"""
|
||||||
|
prefix_len = 0
|
||||||
|
for i in range(min(len(w1), len(w2))):
|
||||||
|
if w1[i].lower() == w2[i].lower():
|
||||||
|
prefix_len += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return prefix_len
|
||||||
|
|
||||||
|
scored_variants = []
|
||||||
|
for variant in variants:
|
||||||
|
prefix_len = get_common_prefix_length(original, variant)
|
||||||
|
|
||||||
|
# Reject variants that don't share the stem (beginning doesn't match)
|
||||||
|
if prefix_len < min(2, len(original) // 2):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Score: prioritize common prefix length (stem preservation) heavily,
|
||||||
|
# then prefer longer variants (forward/more specific) over shorter (backward/simpler)
|
||||||
|
# Prefix is weighted 1000x to ensure stem-preservation is primary
|
||||||
|
score = (prefix_len * 1000) + len(variant)
|
||||||
|
scored_variants.append((score, variant))
|
||||||
|
|
||||||
|
# Sort by score descending: highest score = most similar with longest form first
|
||||||
|
return [v for _, v in sorted(scored_variants, key=lambda x: x[0], reverse=True)]
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
variants = cached["grammatical_variants"].get(word.lower(), [])
|
||||||
|
if variants:
|
||||||
|
# Sort variants: forwards in likeness (longer/specific) then backwards (shorter/simple)
|
||||||
|
sorted_variants = sort_variants_by_similarity(word.lower(), variants)
|
||||||
|
|
||||||
|
# Try each variant in order of similarity
|
||||||
|
for variant in sorted_variants:
|
||||||
|
variant_query = cleaned_keywords.replace(word, variant, 1)
|
||||||
|
results = lookup_fts(variant_query)
|
||||||
|
if results:
|
||||||
response_list = scripture_response(results)
|
response_list = scripture_response(results)
|
||||||
arg_view = request.args.get("view", None)
|
arg_view = request.args.get("view", None)
|
||||||
if arg_view == "plain":
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = f"Showing results for '{variant_query}' (grammatical variant of '{cleaned_keywords}')\n<br>" + "<br>".join(response_list)
|
||||||
elif arg_view == "rich":
|
elif arg_view == "rich":
|
||||||
|
variant_notice = f"<div class='variant-notice'>Showing results for '<strong>{variant_query}</strong>' (grammatical variant of '{cleaned_keywords}')</div>"
|
||||||
|
response_str = "".join(response_list)
|
||||||
|
response = response_str.replace("<div class='responses'>", f"<div class='responses'>{variant_notice}", 1)
|
||||||
|
else:
|
||||||
|
response = jsonify({"original": cleaned_keywords, "variant": variant_query, "results": response_list})
|
||||||
|
return response
|
||||||
|
|
||||||
|
# PRIORITY 2: If grammatical variants didn't help, try spell-check
|
||||||
|
h = hunspell.HunSpell("data/kjv.dic", "data/kjv.aff")
|
||||||
|
corrected_words = []
|
||||||
|
had_corrections = False
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
if h.spell(word):
|
||||||
|
# Word is spelled correctly
|
||||||
|
corrected_words.append(word)
|
||||||
|
else:
|
||||||
|
# Word is misspelled, get suggestions
|
||||||
|
word_suggestions = h.suggest(word)
|
||||||
|
if word_suggestions:
|
||||||
|
corrected_words.append(word_suggestions[0])
|
||||||
|
had_corrections = True
|
||||||
|
else:
|
||||||
|
# No suggestions, keep original
|
||||||
|
corrected_words.append(word)
|
||||||
|
|
||||||
|
if had_corrections:
|
||||||
|
corrected_keyword = " ".join(corrected_words)
|
||||||
|
results = lookup_fts(corrected_keyword)
|
||||||
|
if results:
|
||||||
|
response_list = scripture_response(results)
|
||||||
|
arg_view = request.args.get("view", None)
|
||||||
|
if arg_view == "plain":
|
||||||
|
response = f"Showing results for '{corrected_keyword}' (corrected from '{cleaned_keywords}')\n<br>" + "<br>".join(response_list)
|
||||||
|
elif arg_view == "rich":
|
||||||
|
correction_notice = f"<div class='correction-notice'>Showing results for '<strong>{corrected_keyword}</strong>' (corrected from '{cleaned_keywords}')</div>"
|
||||||
|
response_str = "".join(response_list)
|
||||||
|
response = response_str.replace("<div class='responses'>", f"<div class='responses'>{correction_notice}", 1)
|
||||||
|
else:
|
||||||
|
response = jsonify({"corrected_from": cleaned_keywords, "corrected_to": corrected_keyword, "results": response_list})
|
||||||
|
return response
|
||||||
|
|
||||||
|
# PRIORITY 3: Try theological synonym expansion
|
||||||
|
for word in words:
|
||||||
|
synonyms = cached["theological_synonyms"].get(word.lower(), None)
|
||||||
|
if synonyms:
|
||||||
|
for synonym in synonyms:
|
||||||
|
synonym_query = cleaned_keywords.replace(word, synonym, 1)
|
||||||
|
results = lookup_fts(synonym_query)
|
||||||
|
if results:
|
||||||
|
response_list = scripture_response(results)
|
||||||
|
arg_view = request.args.get("view", None)
|
||||||
|
explanation = f"In the KJV, '{word}' and '{synonym}' often refer to similar theological concepts. Consider also searching for related verses."
|
||||||
|
if arg_view == "plain":
|
||||||
|
response = f"Showing results for '{synonym_query}' (theological equivalent of '{cleaned_keywords}')\n{explanation}\n<br>" + "<br>".join(response_list)
|
||||||
|
elif arg_view == "rich":
|
||||||
|
theological_notice = f"<div class='theological-notice'>Showing results for '<strong>{synonym_query}</strong>' (theological equivalent of '{cleaned_keywords}')<br><em>{explanation}</em></div>"
|
||||||
|
response_str = "".join(response_list)
|
||||||
|
response = response_str.replace("<div class='responses'>", f"<div class='responses'>{theological_notice}", 1)
|
||||||
|
else:
|
||||||
|
response = jsonify({"original": cleaned_keywords, "theological_equivalent": synonym_query, "explanation": explanation, "results": response_list})
|
||||||
|
return response
|
||||||
|
|
||||||
|
# PRIORITY 4: If we still have no results, show spelling suggestions
|
||||||
|
suggestions = h.suggest(cleaned_keywords)
|
||||||
|
arg_view = request.args.get("view", "rich")
|
||||||
|
|
||||||
|
if arg_view == "rich":
|
||||||
|
# Build a nice no-results page with header
|
||||||
|
no_results_html = cached["html_navbar"].format(
|
||||||
|
query=cleaned_keywords,
|
||||||
|
current_chapter_name="",
|
||||||
|
next_chapter_name="",
|
||||||
|
next_chapter_link="",
|
||||||
|
prev_chapter_name="",
|
||||||
|
prev_chapter_link=""
|
||||||
|
)
|
||||||
|
no_results_html += cached["css_navbar"]
|
||||||
|
no_results_html += "<div class='responses'>"
|
||||||
|
no_results_html += f"<div class='correction-notice'><strong>No results found</strong> for '{cleaned_keywords}'.</div>"
|
||||||
|
|
||||||
|
if suggestions:
|
||||||
|
no_results_html += "<div class='theological-suggestions'><h3>Did you mean?</h3>"
|
||||||
|
no_results_html += "<p>We couldn't find any verses matching your search. Here are some spelling suggestions:</p>"
|
||||||
|
for suggestion in suggestions[:5]: # Limit to 5 suggestions
|
||||||
|
search_link = f"/search?kw={suggestion}&view=rich"
|
||||||
|
no_results_html += f"<div class='suggestion-item'><a href='{search_link}'><strong>{suggestion}</strong></a></div>"
|
||||||
|
no_results_html += "</div>"
|
||||||
|
else:
|
||||||
|
no_results_html += "<div class='theological-suggestions'><h3>Search Tips</h3>"
|
||||||
|
no_results_html += "<p>We couldn't find any verses matching your search. Try:</p>"
|
||||||
|
no_results_html += "<ul style='font-family: var(--font-ui); color: var(--color-text-secondary);'>"
|
||||||
|
no_results_html += "<li>Checking your spelling</li>"
|
||||||
|
no_results_html += "<li>Using different keywords</li>"
|
||||||
|
no_results_html += "<li>Searching for <a href='/search?kw=love&view=rich'>love</a>, <a href='/search?kw=faith&view=rich'>faith</a>, or <a href='/search?kw=hope&view=rich'>hope</a></li>"
|
||||||
|
no_results_html += "</ul></div>"
|
||||||
|
|
||||||
|
no_results_html += "</div>"
|
||||||
|
return no_results_html
|
||||||
|
else:
|
||||||
|
# Plain text fallback
|
||||||
|
return "{} not found, try one of the following: {}".format(
|
||||||
|
cleaned_keywords, ", ".join(suggestions) if suggestions else "No suggestions available"
|
||||||
|
)
|
||||||
|
response_list = scripture_response(results)
|
||||||
|
|
||||||
|
# Check if any words in the search have theological synonyms to suggest
|
||||||
|
theological_suggestions = []
|
||||||
|
words = cleaned_keywords.split()
|
||||||
|
for word in words:
|
||||||
|
synonyms = cached["theological_synonyms"].get(word.lower(), None)
|
||||||
|
if synonyms:
|
||||||
|
for synonym in synonyms:
|
||||||
|
theological_suggestions.append(synonym)
|
||||||
|
|
||||||
|
arg_view = request.args.get("view", "rich")
|
||||||
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = "<br>".join(response_list)
|
||||||
|
if concept_notice:
|
||||||
|
response = concept_notice + "<br>" + response
|
||||||
|
if theological_suggestions:
|
||||||
|
response += "<br><br>Also consider searching: "
|
||||||
|
for suggestion in theological_suggestions:
|
||||||
|
response += f"{suggestion}<br>"
|
||||||
|
elif arg_view == "rich":
|
||||||
|
response = "".join(response_list)
|
||||||
|
# Insert concept notice at the top if it exists
|
||||||
|
if concept_notice:
|
||||||
|
# Find the first </div> after the opening <div class='responses'> and insert after it
|
||||||
|
responses_start = response.find("<div class='responses'>")
|
||||||
|
if responses_start != -1:
|
||||||
|
# Find the results-count div closing
|
||||||
|
insert_pos = response.find("</div>", responses_start + len("<div class='responses'>"))
|
||||||
|
if insert_pos != -1:
|
||||||
|
insert_pos += len("</div>")
|
||||||
|
response = response[:insert_pos] + concept_notice + response[insert_pos:]
|
||||||
|
if theological_suggestions:
|
||||||
|
# Add suggestions before the LAST closing </div> which closes the responses container
|
||||||
|
suggestions_html = "<div class='theological-suggestions'><h3>Related Theological Concepts</h3>"
|
||||||
|
suggestions_html += "<p>In the KJV, these terms often refer to similar theological concepts. Consider also searching for related verses.</p>"
|
||||||
|
for suggestion in theological_suggestions:
|
||||||
|
search_link = f"/search?kw={suggestion}&view=rich"
|
||||||
|
suggestions_html += f"<div class='suggestion-item'>"
|
||||||
|
suggestions_html += f"<a href='{search_link}'><strong>{suggestion.capitalize()}</strong></a>"
|
||||||
|
suggestions_html += "</div>"
|
||||||
|
suggestions_html += "</div>"
|
||||||
|
# Find the last </div> and insert before it
|
||||||
|
last_div_pos = response.rfind("</div>")
|
||||||
|
if last_div_pos != -1:
|
||||||
|
response = response[:last_div_pos] + suggestions_html + response[last_div_pos:]
|
||||||
else:
|
else:
|
||||||
response = jsonify(response_list)
|
response = jsonify(response_list)
|
||||||
#logging.debug(f"Search Response: {response}")
|
#logging.debug(f"Search Response: {response}")
|
||||||
@@ -935,8 +1288,10 @@ def sequential_read(start=0, num=1, uid=None, getuid=False):
|
|||||||
# only look at a small set of the oldest unused entries.
|
# only look at a small set of the oldest unused entries.
|
||||||
seq_conn.commit()
|
seq_conn.commit()
|
||||||
arg_view = request.args.get("view", None)
|
arg_view = request.args.get("view", None)
|
||||||
if arg_view == "plain" or arg_view == "rich":
|
if arg_view == "plain":
|
||||||
response = "<br>".join(response_list)
|
response = "<br>".join(response_list)
|
||||||
|
elif arg_view == "rich":
|
||||||
|
response = "".join(response_list)
|
||||||
else:
|
else:
|
||||||
response = jsonify(response_list)
|
response = jsonify(response_list)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"query": "rapture",
|
||||||
|
"explanation": "The word 'rapture' does not appear in the KJV, but is usually a reference to believers being caught up to meet Christ in the air.",
|
||||||
|
"search_terms": ["caught up", "meet the lord in the air"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "trinity",
|
||||||
|
"explanation": "The word 'trinity' does not appear in the KJV, but is usually a reference to the doctrine of the triune God (Father, Son, and Holy Spirit).",
|
||||||
|
"search_terms": ["father son holy ghost", "three that bear record"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "missionary",
|
||||||
|
"explanation": "The word 'missionary' does not appear in the KJV, but is usually a reference to being sent to preach the gospel.",
|
||||||
|
"search_terms": ["go ye therefore", "preach the gospel", "sent"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "bible",
|
||||||
|
"explanation": "The word 'bible' does not appear in the KJV, but is usually a reference to the scriptures and God's word.",
|
||||||
|
"search_terms": ["scripture", "word of god", "holy scriptures"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "inerrancy",
|
||||||
|
"explanation": "The word 'inerrancy' does not appear in the KJV, but is usually a reference to the perfection and preservation of God's word.",
|
||||||
|
"search_terms": ["word of god", "scripture", "thou hast magnified thy word"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "antichrist system",
|
||||||
|
"explanation": "The phrase 'antichrist system' is usually a reference to the antichrist and end times deception.",
|
||||||
|
"search_terms": ["antichrist", "man of sin", "son of perdition", "beast"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "pre-tribulation",
|
||||||
|
"explanation": "The phrase 'pre-tribulation' does not appear in the KJV, but is usually a reference to Christ's return before the tribulation.",
|
||||||
|
"search_terms": ["caught up", "great tribulation", "day of the lord"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "soul winning",
|
||||||
|
"explanation": "The phrase 'soul winning' does not appear in the KJV, but is usually a reference to winning souls and converting sinners.",
|
||||||
|
"search_terms": ["winneth souls", "save souls", "convert"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "guardian angel",
|
||||||
|
"explanation": "The phrase 'guardian angel' does not appear in the KJV, but is usually a reference to angels and their protective ministry.",
|
||||||
|
"search_terms": ["angel", "ministering spirits", "charge over thee"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "angel wings",
|
||||||
|
"explanation": "The phrase 'angel wings' does not appear in the KJV, but is usually a reference to biblical descriptions of angels.",
|
||||||
|
"search_terms": ["angel", "seraphim", "cherubim", "wings"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "7 deadly sins",
|
||||||
|
"explanation": "The phrase '7 deadly sins' does not appear in the KJV (it's a Catholic tradition), but is usually a reference to biblical lists of sins.",
|
||||||
|
"search_terms": ["abomination", "works of the flesh", "evil"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "purgatory",
|
||||||
|
"explanation": "The word 'purgatory' does not appear in the KJV. This is a Catholic doctrine not found in scripture.",
|
||||||
|
"search_terms": ["appointed unto men once to die", "after this the judgment", "absent from the body"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "christmas",
|
||||||
|
"explanation": "The word 'christmas' does not appear in the KJV, but is usually a reference to the birth of Christ.",
|
||||||
|
"search_terms": ["born in bethlehem", "manger", "wise men", "shepherds"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "easter",
|
||||||
|
"explanation": "The word 'easter' appears once in the KJV (Acts 12:4) referring to Passover, but is usually a reference to Christ's resurrection.",
|
||||||
|
"search_terms": ["easter", "resurrection", "rose again", "third day"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "lucifer",
|
||||||
|
"explanation": "The name 'Lucifer' appears once in the KJV (Isaiah 14:12), but is usually a reference to Satan and his fall.",
|
||||||
|
"search_terms": ["lucifer", "devil", "satan", "dragon", "serpent"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "666",
|
||||||
|
"explanation": "The number '666' is usually a reference to the mark of the beast.",
|
||||||
|
"search_terms": ["six hundred threescore and six", "mark", "number of his name"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "four horsemen",
|
||||||
|
"explanation": "The phrase 'four horsemen' does not appear exactly in the KJV, but is usually a reference to the four horsemen of the apocalypse.",
|
||||||
|
"search_terms": ["horse", "seal", "rider"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "end of the world",
|
||||||
|
"explanation": "The phrase 'end of the world' is usually a reference to the end times and Christ's return.",
|
||||||
|
"search_terms": ["end of the world", "last days", "coming of the lord"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "left behind",
|
||||||
|
"explanation": "The phrase 'left behind' is usually a reference to the rapture and second coming.",
|
||||||
|
"search_terms": ["one shall be taken", "left", "coming of the son of man"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "prosperity gospel",
|
||||||
|
"explanation": "The phrase 'prosperity gospel' does not appear in the KJV. This is a false doctrine. Biblical warnings against covetousness and false teachers:",
|
||||||
|
"search_terms": ["love of money", "covetousness", "false prophets", "perverse disputings"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "speaking in tongues",
|
||||||
|
"explanation": "The phrase 'speaking in tongues' is usually a reference to tongues and spiritual gifts.",
|
||||||
|
"search_terms": ["tongues", "unknown tongue", "interpretation"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "slain in the spirit",
|
||||||
|
"explanation": "The phrase 'slain in the spirit' does not appear in the KJV. This is a charismatic practice not found in scripture.",
|
||||||
|
"search_terms": ["holy ghost", "filled with the spirit", "spirit of god"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "original sin",
|
||||||
|
"explanation": "The phrase 'original sin' does not appear in the KJV, but is usually a reference to the doctrine of inherited sin from Adam.",
|
||||||
|
"search_terms": ["all have sinned", "by one man sin entered", "in adam all die"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "the chosen",
|
||||||
|
"explanation": "The phrase 'the chosen' is usually a reference to God's elect and chosen people.",
|
||||||
|
"search_terms": ["chosen", "elect", "called"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "good samaritan",
|
||||||
|
"explanation": "The phrase 'good samaritan' is usually a reference to the parable of the good Samaritan.",
|
||||||
|
"search_terms": ["certain samaritan", "neighbour", "priest levite"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "prodigal son",
|
||||||
|
"explanation": "The phrase 'prodigal son' is usually a reference to the parable of the prodigal son.",
|
||||||
|
"search_terms": ["younger son", "riotous living", "father said", "was lost"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "jezebel spirit",
|
||||||
|
"explanation": "The phrase 'jezebel spirit' does not appear in the KJV (it's a charismatic term), but is usually a reference to Jezebel and false prophetesses.",
|
||||||
|
"search_terms": ["jezebel", "that woman", "seduceth"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "generational curse",
|
||||||
|
"explanation": "The phrase 'generational curse' does not appear in the KJV (it's a charismatic term), but is usually a reference to visiting iniquity upon children.",
|
||||||
|
"search_terms": ["visiting the iniquity", "fathers", "children"]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
[
|
||||||
|
["you", "ye", "thee", "thou"],
|
||||||
|
["your", "thy", "thine"],
|
||||||
|
["have", "has", "hath", "hast"],
|
||||||
|
["do", "does", "doth", "doest"],
|
||||||
|
["say", "says", "said", "saith"],
|
||||||
|
["are", "art"],
|
||||||
|
["was", "were", "wast", "wert"],
|
||||||
|
["will", "wilt", "shall", "shalt"],
|
||||||
|
["go", "goes", "goeth", "goest"],
|
||||||
|
["know", "knows", "knoweth", "knowest"],
|
||||||
|
["see", "sees", "seeth", "seest"],
|
||||||
|
["give", "gives", "giveth", "givest"],
|
||||||
|
["make", "makes", "maketh", "makest"],
|
||||||
|
["come", "comes", "cometh", "comest"],
|
||||||
|
["take", "takes", "taketh", "takest"],
|
||||||
|
["can", "canst"],
|
||||||
|
["may", "mayest"],
|
||||||
|
["hear", "hears", "heareth", "hearest"],
|
||||||
|
["walk", "walks", "walketh", "walkest"],
|
||||||
|
["speak", "speaks", "speaketh", "speakest"],
|
||||||
|
["live", "lives", "liveth", "livest"],
|
||||||
|
["call", "calls", "calleth", "callest"],
|
||||||
|
["fear", "fears", "feareth", "fearest"],
|
||||||
|
["love", "loves", "loveth", "lovest"],
|
||||||
|
["believe", "believes", "believeth", "believest"],
|
||||||
|
["did", "didst"],
|
||||||
|
["eat", "eats", "eateth", "eatest"],
|
||||||
|
["among", "amongst"],
|
||||||
|
["until", "till", "unto"],
|
||||||
|
["afterward", "afterwards"],
|
||||||
|
["before", "ere"],
|
||||||
|
["also", "moreover", "furthermore"],
|
||||||
|
["therefore", "wherefore", "thus"],
|
||||||
|
["because", "for"],
|
||||||
|
["indeed", "verily"],
|
||||||
|
["behold", "lo"],
|
||||||
|
["ask", "asks", "asketh", "askest"],
|
||||||
|
["find", "finds", "findeth", "findest"],
|
||||||
|
["seek", "seeks", "seeketh", "seekest"],
|
||||||
|
["trust", "trusts", "trusteth", "trustest"],
|
||||||
|
["follow", "follows", "followeth", "followest"],
|
||||||
|
["serve", "serves", "serveth", "servest"],
|
||||||
|
["teach", "teaches", "teacheth", "teachest"],
|
||||||
|
["think", "thinks", "thinketh", "thinkest"],
|
||||||
|
["lead", "leads", "leadeth", "leadest"],
|
||||||
|
["rejoice", "rejoices", "rejoiceth", "rejoicest"],
|
||||||
|
["keep", "keeps", "keepeth", "keepest"],
|
||||||
|
["abide", "abides", "abideth", "abidest"],
|
||||||
|
["judge", "judges", "judgeth", "judgest"],
|
||||||
|
["die", "dies", "dieth", "diest"],
|
||||||
|
["rise", "rises", "riseth", "risest"],
|
||||||
|
["reign", "reigns", "reigneth", "reignest"]
|
||||||
|
]
|
||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,104 @@
|
|||||||
|
[
|
||||||
|
["charity", "love"],
|
||||||
|
["ghost", "spirit"],
|
||||||
|
["righteousness", "holiness"],
|
||||||
|
["salvation", "redemption"],
|
||||||
|
["faith", "belief"],
|
||||||
|
["sin", "iniquity", "transgression"],
|
||||||
|
["forgive", "pardon"],
|
||||||
|
["anger", "wrath"],
|
||||||
|
["witness", "testify", "testimony"],
|
||||||
|
["heaven", "paradise"],
|
||||||
|
["hell", "damnation"],
|
||||||
|
["pray", "prayer", "supplication"],
|
||||||
|
["bless", "blessed", "blessing"],
|
||||||
|
["promise", "covenant"],
|
||||||
|
["worship", "praise"],
|
||||||
|
["disciple", "apostle"],
|
||||||
|
["eternal", "everlasting"],
|
||||||
|
["demon", "devil"],
|
||||||
|
["miracle", "wonder", "sign"],
|
||||||
|
["prophesy", "prophecy"],
|
||||||
|
["resurrect", "resurrection", "rise", "risen"],
|
||||||
|
["baptize", "baptism"],
|
||||||
|
["tempt", "temptation"],
|
||||||
|
["humble", "meek"],
|
||||||
|
["wise", "wisdom"],
|
||||||
|
["truth", "true"],
|
||||||
|
["glory", "glorify"],
|
||||||
|
["mercy", "compassion"],
|
||||||
|
["grace", "favor"],
|
||||||
|
["judge", "judgment", "condemn", "condemnation"],
|
||||||
|
["command", "commandment"],
|
||||||
|
["rejoice", "joy", "gladness"],
|
||||||
|
["reverence", "fear"],
|
||||||
|
["teach", "doctrine"],
|
||||||
|
["preach", "proclaim"],
|
||||||
|
["suffer", "suffering", "affliction"],
|
||||||
|
["comfort", "console"],
|
||||||
|
["obey", "obedience"],
|
||||||
|
["peace", "rest"],
|
||||||
|
["repent", "repentance", "turn"],
|
||||||
|
["holy", "sanctify", "sanctified"],
|
||||||
|
["nation", "gentile"],
|
||||||
|
["Israel", "Jew", "Hebrew"],
|
||||||
|
["king", "kingdom", "reign"],
|
||||||
|
["priest", "priesthood"],
|
||||||
|
["covenant", "testament"],
|
||||||
|
["temple", "tabernacle", "sanctuary"],
|
||||||
|
["altar", "sacrifice", "offering"],
|
||||||
|
["righteous", "just", "justice"],
|
||||||
|
["wicked", "evil", "ungodly"],
|
||||||
|
["servant", "minister"],
|
||||||
|
["fornication", "whoredom", "adultery"],
|
||||||
|
["sodomite", "abomination"],
|
||||||
|
["pervert", "corrupt"],
|
||||||
|
["destroy", "destruction", "perish"],
|
||||||
|
["harlot", "whore"],
|
||||||
|
["idolater", "idolatry"],
|
||||||
|
["covetous", "covetousness", "greed"],
|
||||||
|
["enemy", "adversary"],
|
||||||
|
["avenge", "vengeance", "recompense"],
|
||||||
|
["tribulation", "trouble", "distress"],
|
||||||
|
["scorn", "mock", "despise"],
|
||||||
|
["proud", "pride", "haughty"],
|
||||||
|
["meek", "lowly", "humble"],
|
||||||
|
["wisdom", "understanding", "knowledge"],
|
||||||
|
["foolish", "fool", "folly"],
|
||||||
|
["strengthen", "strong", "might"],
|
||||||
|
["weary", "faint"],
|
||||||
|
["prevail", "overcome"],
|
||||||
|
["remnant", "residue"],
|
||||||
|
["inheritance", "heir"],
|
||||||
|
["generation", "seed", "offspring"],
|
||||||
|
["pestilence", "plague"],
|
||||||
|
["famine", "hunger"],
|
||||||
|
["captivity", "bondage"],
|
||||||
|
["deliver", "deliverance", "save"],
|
||||||
|
["redeem", "ransom"],
|
||||||
|
["provoke", "anger", "wrath"],
|
||||||
|
["abhor", "hate", "detest"],
|
||||||
|
["trust", "confidence"],
|
||||||
|
["persecute", "persecution"],
|
||||||
|
["blaspheme", "blasphemy"],
|
||||||
|
["stone", "stoning"],
|
||||||
|
["burn", "fire", "flame"],
|
||||||
|
["beloved", "wellbeloved"],
|
||||||
|
["delight", "pleasure"],
|
||||||
|
["kindness", "lovingkindness"],
|
||||||
|
["tender", "tenderness"],
|
||||||
|
["longsuffering", "patience", "forbearance"],
|
||||||
|
["gentleness", "meekness"],
|
||||||
|
["goodness", "kindness"],
|
||||||
|
["brotherly", "brother"],
|
||||||
|
["wrath", "indignation", "fury"],
|
||||||
|
["vengeance", "recompense", "reward"],
|
||||||
|
["end times", "last days", "latter days"],
|
||||||
|
["appearing", "coming", "return"],
|
||||||
|
["trumpet", "shout", "voice"],
|
||||||
|
["watch", "watchful", "vigilant"],
|
||||||
|
["seal", "sealed"],
|
||||||
|
["vial", "bowl"],
|
||||||
|
["scroll", "book"],
|
||||||
|
["lamb", "lion"]
|
||||||
|
]
|
||||||
+24
-21
@@ -1,24 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Bible Dictionary - KJV API</title>
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<style>body{background-color:#1a1a1a}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="/">Home</a> | <a href="/define">Dictionary</a> |
|
||||||
|
<a href="/random?view=rich">Random Verse</a> | <a href="/votd?view=rich">Verse of the Day</a>
|
||||||
|
</header>
|
||||||
<h2>Bible Dictionary</h2>
|
<h2>Bible Dictionary</h2>
|
||||||
<form action="/define">
|
<form action="/define">
|
||||||
<label for='kw'>Word lookup</label><br>
|
<label for='kw'>Word lookup</label>
|
||||||
<input type="text" name="kw"><br><br>
|
<input type="text" name="kw" placeholder="Enter a word to define...">
|
||||||
|
<label for='view'>View</label>
|
||||||
<label for='view'>View<label><br>
|
<input type="radio" name="view" value="rich" checked="checked"> Rich
|
||||||
<input type="radio" name="view" value="rich" checked="checked">Rich<br>
|
<input type="radio" name="view" value="plain"> Plain
|
||||||
<input type="radio" name="view" value="plain">Plain<br>
|
<input type="radio" name="view" value="json"> JSON
|
||||||
<input type="radio" name="view" value="json">JSON<br>
|
<input type="submit" value="Define">
|
||||||
|
</form>
|
||||||
<input type="submit">
|
|
||||||
<form>
|
|
||||||
<p>This tool uses an abridged 1828 Noah Webster dictionary.</p>
|
<p>This tool uses an abridged 1828 Noah Webster dictionary.</p>
|
||||||
|
</body>
|
||||||
<style>
|
</html>
|
||||||
html{
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
width: 768px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #547720
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+19
-19
@@ -1,24 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Quick Search - KJV API</title>
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<style>body{background-color:#1a1a1a}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a href="/">Home</a> | <a href="/find">Quick Search</a> |
|
<a href="/">Home</a> | <a href="/find">Quick Search</a> |
|
||||||
<a href="/random?view=rich">Random Verse</a> |
|
<a href="/random?view=rich">Random Verse</a> |
|
||||||
<a href="/votd?view=rich">Verse of the Day</a></header>
|
<a href="/votd?view=rich">Verse of the Day</a>
|
||||||
|
</header>
|
||||||
<h2>Quick Search</h2>
|
<h2>Quick Search</h2>
|
||||||
<form action="/find">
|
<form action="/find">
|
||||||
<label for='kw'>Query</label><br>
|
<label for='kw'>Query</label>
|
||||||
<input type="text" name="kw"><br><br>
|
<input type="text" name="kw" placeholder="Enter verse reference or keywords...">
|
||||||
|
<input type="hidden" name="view" value="rich">
|
||||||
<input type="hidden" name="view" value="rich"><br>
|
<input type="submit" value="Search">
|
||||||
|
</form>
|
||||||
<input type="submit">
|
</body>
|
||||||
<form>
|
</html>
|
||||||
|
|
||||||
<style>
|
|
||||||
html{
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
width: 768px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #547720
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+196
-80
@@ -1,98 +1,212 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>KJV API - Legacy Edition</title>
|
||||||
|
<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>
|
||||||
/* Dark Theme */
|
/* Bible Book Styles */
|
||||||
body {
|
.book-grid {
|
||||||
background-color: #222;
|
display: grid;
|
||||||
color: #fff;
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the header */
|
.book-button {
|
||||||
html {
|
background-color: var(--color-bg-tertiary);
|
||||||
font-family: sans-serif;
|
border: 1px solid var(--color-border);
|
||||||
font-size: 14.5pt;
|
border-radius: var(--border-radius-sm);
|
||||||
width: 768px;
|
padding: 0.75rem 1rem;
|
||||||
padding: 1px;
|
|
||||||
margin-top: 15px;
|
|
||||||
color: #fff; /* Text color */
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #007bff; /* Link color */
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
font-family: sans-serif;
|
|
||||||
padding: 10px 16px;
|
|
||||||
background: #333; /* Dark background */
|
|
||||||
color: #fff; /* Text color */
|
|
||||||
position: fixed;
|
|
||||||
top: 5px;
|
|
||||||
width: 768px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text] {
|
|
||||||
width: 75%;
|
|
||||||
padding: 12px 20px;
|
|
||||||
margin: 8px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 14.5pt;
|
color: var(--color-text-primary);
|
||||||
background-color: #333; /* Dark background */
|
transition: all var(--transition-fast);
|
||||||
color: #fff; /* Text color */
|
|
||||||
border: 1px solid #555; /* Border color */
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=button], input[type=submit], input[type=reset] {
|
|
||||||
font-family: sans-serif;
|
|
||||||
background-color: #555; /* Dark button background */
|
|
||||||
border: none;
|
|
||||||
color: #fff; /* Button text color */
|
|
||||||
padding: 16px 32px;
|
|
||||||
text-decoration: none;
|
|
||||||
margin: 4px 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
.book-button:hover {
|
||||||
background-color: #222;
|
background-color: var(--color-accent-primary);
|
||||||
color: #fff;
|
color: var(--color-bg-primary);
|
||||||
padding: 4px 8px;
|
border-color: var(--color-accent-primary);
|
||||||
border-radius: 4px;
|
transform: translateY(-1px);
|
||||||
font-family: monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.definition {
|
.book-button.active {
|
||||||
padding: 8px;
|
background-color: var(--color-accent-primary);
|
||||||
font-family: sans-serif;
|
color: var(--color-bg-primary);
|
||||||
font-size: 14.5pt;
|
border-color: var(--color-accent-primary);
|
||||||
width: 768px;
|
}
|
||||||
padding: 1px;
|
|
||||||
color: #fff; /* Text color */
|
.chapter-grid {
|
||||||
background-color: #222; /* Dark background */
|
display: none;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(45px, 1fr));
|
||||||
|
gap: 0.4rem;
|
||||||
|
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;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
box-shadow: 0 2px 8px var(--color-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testament-header {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.book-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
<h1>KJV API (Legacy edition)</h1>
|
<h1>KJV API (Legacy edition)</h1>
|
||||||
|
|
||||||
|
<div class="search-section">
|
||||||
<form action="/find">
|
<form action="/find">
|
||||||
<label for='kw'>Keyword Search</label><br>
|
<label for='kw'>Search Scripture</label><br>
|
||||||
<input type="text" name="kw"><br><br>
|
<input type="text" name="kw" id="kw" placeholder="Enter verse reference or search keywords..."><br><br>
|
||||||
<input type="hidden" name="view" value="rich"">
|
<input type="hidden" name="view" value="rich">
|
||||||
<input type="submit">
|
<input type="submit" value="Search">
|
||||||
<form>
|
</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"
|
||||||
@@ -108,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
|
||||||
@@ -152,7 +266,6 @@ radio selector for choosing your output format.
|
|||||||
- /define
|
- /define
|
||||||
- /show
|
- /show
|
||||||
|
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
========
|
========
|
||||||
api.tld/random
|
api.tld/random
|
||||||
@@ -161,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>
|
||||||
|
|||||||
+4
-1
@@ -1 +1,4 @@
|
|||||||
<b><a href="/find?kw={chapter}&view=rich">{passage}</a></b><br>{text}<br>
|
<div class="verse-container">
|
||||||
|
<div class="verse-reference"><a href="/find?kw={chapter}&view=rich">{passage}</a></div>
|
||||||
|
<div class="verse-text">{text}</div>
|
||||||
|
</div>
|
||||||
|
|||||||
+25
-23
@@ -1,28 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Bible Search - KJV API</title>
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<style>body{background-color:#1a1a1a}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="/">Home</a> | <a href="/find">Quick Search</a> | <a href="/search">Bible Search</a> |
|
||||||
|
<a href="/random?view=rich">Random Verse</a> | <a href="/votd?view=rich">Verse of the Day</a>
|
||||||
|
</header>
|
||||||
<h2>Bible Search</h2>
|
<h2>Bible Search</h2>
|
||||||
<form action="/search">
|
<form action="/search">
|
||||||
<label for='kw'>Key Words</label><br>
|
<label for='kw'>Key Words</label>
|
||||||
<input type="text" name="kw"><br><br>
|
<input type="text" name="kw" placeholder="Enter search keywords...">
|
||||||
|
<label for='view'>View</label>
|
||||||
<label for='view'>View<label><br>
|
<input type="radio" name="view" value="rich" checked="checked"> Rich
|
||||||
<input type="radio" name="view" value="rich" checked="checked">Rich<br>
|
<input type="radio" name="view" value="plain"> Plain
|
||||||
<input type="radio" name="view" value="plain">Plain<br>
|
<input type="radio" name="view" value="json"> JSON
|
||||||
<input type="radio" name="view" value="json">JSON<br>
|
<input type="submit" value="Search">
|
||||||
|
</form>
|
||||||
<input type="submit">
|
|
||||||
<form>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<p>You can use standard search operators such as <b>AND</b>, <b>OR</b>, and <b>NOT</b>.</p>
|
<p>You can use standard search operators such as <b>AND</b>, <b>OR</b>, and <b>NOT</b>.</p>
|
||||||
<p>Such as: "reprobate <b>NOT</b> silver".</p>
|
<p>Such as: "reprobate <b>NOT</b> silver".</p>
|
||||||
<p>Multiple word queries without operators will be assumed to be AND.<p>
|
<p>Multiple word queries without operators will be assumed to be AND.</p>
|
||||||
|
</body>
|
||||||
<style>
|
</html>
|
||||||
html{
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
width: 768px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #547720
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+20
-32
@@ -1,40 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Sequential Bible Viewer - KJV API</title>
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<style>body{background-color:#1a1a1a}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a href="/">Home</a> | <a href="/find">Quick Search</a> |
|
<a href="/">Home</a> | <a href="/find">Quick Search</a> | <a href="/seq">Sequential Viewer</a> |
|
||||||
<a href="/random?view=rich">Random Verse</a> |
|
<a href="/random?view=rich">Random Verse</a> | <a href="/votd?view=rich">Verse of the Day</a>
|
||||||
<a href="/votd?view=rich">Verse of the Day</a></header>
|
</header>
|
||||||
|
|
||||||
<h2>Sequential Bible Viewer</h2>
|
<h2>Sequential Bible Viewer</h2>
|
||||||
<p>In order to use this, you first need a UID, which you
|
<p>In order to use this, you first need a UID, which you can get by querying <code>/seq?getuid=true</code> and optionally include <code>&start=BIBLEREF</code> (where BIBLEREF is like 1Cor2:1) to set a start position other than Genesis 1:1.</p>
|
||||||
can get by querying /seq?getuid=true and optionally include
|
<p>After you have your UID, this will keep track of where you are at. Query <code>/seq?uid=YOURUID</code> and optionally include <code>&num=#</code> (where # is an integer of how many verses you want to include from your current position at a time)</p>
|
||||||
&start=BIBLEREF (where BIBLEREF is like 1Cor2:1) to set
|
<p>The <code>&view=</code> argument works with this.</p>
|
||||||
a start position other than Genesis 1:1.</p>
|
|
||||||
|
|
||||||
<p>After you have your UID, this will keep track of where you
|
|
||||||
are at. Query /seq?uid=YOURUID and optionally include
|
|
||||||
&num=# (where # is an integer of how many verses you want to
|
|
||||||
include from your current position at a time)</p>
|
|
||||||
|
|
||||||
<p>The &view= argument works with this.<p>
|
|
||||||
|
|
||||||
<h4>Example Pleroma Bot shell script</h4>
|
<h4>Example Pleroma Bot shell script</h4>
|
||||||
<pre>
|
<pre><code>#!/bin/sh
|
||||||
<code>
|
|
||||||
#!/bin/sh
|
|
||||||
verse=$(curl -XGET https://api.1611.social/seq?uid=MYUID?view=plain);
|
verse=$(curl -XGET https://api.1611.social/seq?uid=MYUID?view=plain);
|
||||||
curl -XPOST -u biblebot:MYPASSWORD \
|
curl -XPOST -u biblebot:MYPASSWORD \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"status": "'"${verse}"'"}' \
|
-d '{"status": "'"${verse}"'"}' \
|
||||||
https://my.fediverse.tld/api/v1/statuses;
|
https://my.fediverse.tld/api/v1/statuses;
|
||||||
</code>
|
</code></pre>
|
||||||
</pre>
|
</body>
|
||||||
|
</html>
|
||||||
<style>
|
|
||||||
html{
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
width: 768px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #547720
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+27
-14
@@ -1,18 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Show Verse - KJV API</title>
|
||||||
|
<link rel="stylesheet" href="/styles/page.css">
|
||||||
|
<style>body{background-color:#1a1a1a}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="/">Home</a> | <a href="/find">Quick Search</a> | <a href="/show">Show Verse</a> |
|
||||||
|
<a href="/random?view=rich">Random Verse</a> | <a href="/votd?view=rich">Verse of the Day</a>
|
||||||
|
</header>
|
||||||
<h2>Show Verse</h2>
|
<h2>Show Verse</h2>
|
||||||
<form action="/show">
|
<form action="/show">
|
||||||
<label for='kw'>Verse References</label><br>
|
<label for='kw'>Verse References</label>
|
||||||
<input type="text" name="kw"><br><br>
|
<input type="text" name="kw" placeholder="e.g., 1 John 5:7 or John3:16,2Peter2:1">
|
||||||
|
<label for='view'>View</label>
|
||||||
<label for='view'>View<label><br>
|
<input type="radio" name="view" value="rich" checked="checked"> Rich
|
||||||
<input type="radio" name="view" value="rich" checked="checked">Rich<br>
|
<input type="radio" name="view" value="plain"> Plain
|
||||||
<input type="radio" name="view" value="plain">Plain<br>
|
<input type="radio" name="view" value="json"> JSON
|
||||||
<input type="radio" name="view" value="json">JSON<br>
|
<input type="submit" value="Show">
|
||||||
|
</form>
|
||||||
<input type="submit">
|
|
||||||
<form>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<p>You can try to search a Bible reference in this box, such as "1 John 5:7".</p>
|
<p>You can search a Bible reference in this box, such as "1 John 5:7".</p>
|
||||||
<p>You can use abbreviations, but you may not get the result you are looking for.</p>
|
<p>You can use abbreviations, but you may not get the result you are looking for.</p>
|
||||||
<p>Use a dash for the verse to get the entire chapter, eg. "John 3:-"<p>
|
<p>Use a dash for the verse to get the entire chapter, e.g., "John 3:-"</p>
|
||||||
<p>You may return multiple verse groups by using a comma, eg. "John3:16,2Peter2:1"</p>
|
<p>You may return multiple verse groups by using a comma, e.g., "John3:16,2Peter2:1"</p>
|
||||||
|
</body>
|
||||||
|
</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 |
+41
-12
@@ -1,25 +1,54 @@
|
|||||||
/* Style the button that is used to open and close the collapsible content */
|
/* Modern Collapsible Styling */
|
||||||
|
|
||||||
.collapsible {
|
.collapsible {
|
||||||
background-color: #eee;
|
background-color: #2f2f2f;
|
||||||
color: #444;
|
color: #e8e8e8;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 18px;
|
padding: 1rem 1.25rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
max-width: 820px;
|
||||||
|
border: 2px solid #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 15px;
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
transition: background-color 150ms ease, border-color 150ms ease, transform 150ms ease;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
|
.collapsible:hover {
|
||||||
.active, .collapsible:hover {
|
background-color: #3a3a3a;
|
||||||
background-color: #ccc;
|
border-color: #6b9bd1;
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the collapsible content. Note: hidden by default */
|
.collapsible.active {
|
||||||
|
background-color: #6b9bd1;
|
||||||
|
color: #1a1a1a;
|
||||||
|
border-color: #6b9bd1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible.active:hover {
|
||||||
|
background-color: #85aede;
|
||||||
|
border-color: #85aede;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapsible content styling */
|
||||||
.content {
|
.content {
|
||||||
padding: 0 18px;
|
padding: 0 1.25rem;
|
||||||
display: none;
|
display: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f1f1f1;
|
background-color: #252525;
|
||||||
|
max-width: 820px;
|
||||||
|
margin: 0 auto 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content.show {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
}
|
}
|
||||||
|
|||||||
+378
-49
@@ -1,70 +1,399 @@
|
|||||||
<style>
|
<style>
|
||||||
/* Dark Theme */
|
/* Modern KJV API Styling - Header & Layout */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Color Palette */
|
||||||
|
--color-bg-primary: #1a1a1a;
|
||||||
|
--color-bg-secondary: #252525;
|
||||||
|
--color-bg-tertiary: #2f2f2f;
|
||||||
|
--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);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--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;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--space-xs: 0.25rem;
|
||||||
|
--space-sm: 0.5rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 1.5rem;
|
||||||
|
--space-xl: 2rem;
|
||||||
|
--space-2xl: 3rem;
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
--content-max-width: 820px;
|
||||||
|
--header-height: 160px;
|
||||||
|
--border-radius: 8px;
|
||||||
|
--border-radius-sm: 4px;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--transition-fast: 150ms ease;
|
||||||
|
--transition-normal: 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #222;
|
background-color: var(--color-bg-primary);
|
||||||
color: #fff;
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the header */
|
/* Header Styling */
|
||||||
|
.header {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
background: linear-gradient(to bottom, var(--color-bg-secondary), var(--color-bg-tertiary));
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 4px 12px var(--color-shadow);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header a {
|
||||||
|
color: var(--color-link);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header a:hover {
|
||||||
|
color: var(--color-link-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Response Container */
|
||||||
.responses {
|
.responses {
|
||||||
font-family: sans-serif;
|
font-family: var(--font-scripture);
|
||||||
font-size: 14.5pt;
|
font-size: 1.125rem;
|
||||||
width: 768px;
|
line-height: 1.8;
|
||||||
padding: 1px;
|
max-width: var(--content-max-width);
|
||||||
margin-top: 100px;
|
margin: 0 auto;
|
||||||
color: #fff; /* Text color */
|
padding: var(--space-md);
|
||||||
width: auto;
|
margin-top: calc(var(--header-height) + var(--space-md));
|
||||||
max-width: 100%; /* Ensure it doesn't exceed the container width */
|
color: var(--color-text-primary);
|
||||||
white-space: pre-wrap; /* Allow word wrapping */
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Correction & Variant Notices */
|
||||||
|
.correction-notice,
|
||||||
|
.variant-notice,
|
||||||
|
.theological-notice {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
padding: var(--space-md) var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
background-color: var(--color-bg-tertiary);
|
||||||
|
border-left: 4px solid var(--color-accent-primary);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.correction-notice strong,
|
||||||
|
.variant-notice strong,
|
||||||
|
.theological-notice strong {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theological-notice em {
|
||||||
|
display: block;
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results Count */
|
||||||
|
.results-count {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theological Suggestions Section */
|
||||||
|
.theological-suggestions {
|
||||||
|
margin-top: var(--space-2xl);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theological-suggestions h3 {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0 0 var(--space-md) 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: var(--space-sm) 0;
|
||||||
|
padding: var(--space-sm) 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item a {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item a:hover {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item em {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dictionary Section */
|
||||||
|
.dictionary-section {
|
||||||
|
margin: var(--space-xl) 0 var(--space-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-section h3 {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-section p {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
a {
|
a {
|
||||||
color: #007bff; /* Link color */
|
color: var(--color-link);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Inputs */
|
||||||
|
input[type=text] {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
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=text]::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
input[type=button],
|
||||||
|
input[type=submit],
|
||||||
|
input[type=reset],
|
||||||
|
button {
|
||||||
|
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-xs) var(--space-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: background-color var(--transition-fast), transform var(--transition-fast);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:hover,
|
||||||
|
input[type=submit]:hover,
|
||||||
|
input[type=reset]:hover,
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:active,
|
||||||
|
input[type=submit]:active,
|
||||||
|
input[type=reset]:active,
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Definition Styling */
|
||||||
|
.definition {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
font-family: var(--font-scripture);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: var(--content-max-width);
|
||||||
|
margin: var(--space-md) auto;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-left: 4px solid var(--color-accent-primary);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verse Container Styling */
|
||||||
|
.verse-container {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-left: 3px solid var(--color-accent-primary);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0.15rem 0;
|
||||||
|
transition: background-color var(--transition-fast), border-color var(--transition-fast);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verse-container:hover {
|
||||||
|
background-color: var(--color-bg-tertiary);
|
||||||
|
border-left-color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verse-reference {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.2;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verse-reference a {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verse-reference a:hover {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verse-text {
|
||||||
|
font-family: var(--font-scripture);
|
||||||
|
font-size: 1.15rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
text-indent: 0;
|
||||||
|
margin: 0.25rem 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:root {
|
||||||
|
--header-height: 140px;
|
||||||
|
--space-xs: 0.2rem;
|
||||||
|
--space-sm: 0.4rem;
|
||||||
|
--space-md: 0.75rem;
|
||||||
|
--space-lg: 1rem;
|
||||||
|
--space-xl: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-family: sans-serif;
|
padding: var(--space-md);
|
||||||
padding: 10px 16px;
|
}
|
||||||
background: #333; /* Dark background */
|
|
||||||
color: #fff; /* Text color */
|
.responses {
|
||||||
position: fixed;
|
font-size: 1rem;
|
||||||
top: 5px;
|
padding: var(--space-md);
|
||||||
width: 98vw;
|
}
|
||||||
max-width: 100%; /* Ensure it doesn't exceed the container width */
|
|
||||||
min-height: 50px;
|
.dictionary-section h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-section p {
|
||||||
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text] {
|
input[type=text] {
|
||||||
width: 75%;
|
max-width: 100%;
|
||||||
padding: 12px 20px;
|
}
|
||||||
margin: 8px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 14.5pt;
|
|
||||||
background-color: #333; /* Dark background */
|
|
||||||
color: #fff; /* Text color */
|
|
||||||
border: 1px solid #555; /* Border color */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=button], input[type=submit], input[type=reset] {
|
@media (max-width: 480px) {
|
||||||
font-family: sans-serif;
|
:root {
|
||||||
background-color: #555; /* Dark button background */
|
--header-height: 120px;
|
||||||
border: none;
|
--space-xs: 0.15rem;
|
||||||
color: #fff; /* Button text color */
|
--space-sm: 0.35rem;
|
||||||
padding: 16px 32px;
|
--space-md: 0.65rem;
|
||||||
text-decoration: none;
|
--space-lg: 0.85rem;
|
||||||
margin: 4px 2px;
|
--space-xl: 1rem;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.definition {
|
.header a {
|
||||||
padding: 8px;
|
font-size: 0.85rem;
|
||||||
font-family: sans-serif;
|
padding: var(--space-xs);
|
||||||
font-size: 14.5pt;
|
}
|
||||||
width: 768px;
|
|
||||||
padding: 1px;
|
.dictionary-section h3 {
|
||||||
color: #fff; /* Text color */
|
font-size: 1.1rem;
|
||||||
background-color: #222; /* Dark background */
|
}
|
||||||
|
|
||||||
|
.dictionary-section p {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button],
|
||||||
|
input[type=submit],
|
||||||
|
input[type=reset],
|
||||||
|
button {
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,66 @@
|
|||||||
<style>
|
<style>
|
||||||
.testhtml{
|
/* Modern KJV API Styling - Human Readable */
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14.5pt;
|
:root {
|
||||||
width: 768px;
|
/* Color Palette */
|
||||||
|
--color-bg-primary: #1a1a1a;
|
||||||
|
--color-bg-secondary: #252525;
|
||||||
|
--color-bg-tertiary: #2f2f2f;
|
||||||
|
--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);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--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;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--space-xs: 0.25rem;
|
||||||
|
--space-sm: 0.5rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 1.5rem;
|
||||||
|
--space-xl: 2rem;
|
||||||
|
--space-2xl: 3rem;
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
--content-max-width: 820px;
|
||||||
|
--border-radius: 8px;
|
||||||
|
--border-radius-sm: 4px;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--transition-fast: 150ms ease;
|
||||||
|
--transition-normal: 250ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.testhtml {
|
||||||
|
font-family: var(--font-scripture);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
max-width: var(--content-max-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-md);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #547720
|
color: var(--color-link);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/* Shared Page Styling for Simple Pages */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-bg-primary: #1a1a1a;
|
||||||
|
--color-bg-secondary: #252525;
|
||||||
|
--color-text-primary: #e8e8e8;
|
||||||
|
--color-text-secondary: #b8b8b8;
|
||||||
|
--color-accent-primary: #6b9bd1;
|
||||||
|
--color-accent-hover: #85aede;
|
||||||
|
--color-link: #6b9bd1;
|
||||||
|
--color-link-hover: #85aede;
|
||||||
|
--color-border: #3a3a3a;
|
||||||
|
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--border-radius-sm: 4px;
|
||||||
|
--border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: var(--color-bg-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-width: 820px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
border-bottom: 2px solid var(--color-border);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-link);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin: 0.5rem 0 1rem 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
background-color: var(--color-bg-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: border-color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit], button {
|
||||||
|
background-color: var(--color-accent-primary);
|
||||||
|
color: var(--color-bg-primary);
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 150ms ease, transform 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit]:hover, button:hover {
|
||||||
|
background-color: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
@@ -10,9 +10,13 @@ def check_sanity():
|
|||||||
|
|
||||||
|
|
||||||
def check_database_permissions():
|
def check_database_permissions():
|
||||||
|
from os.path import exists
|
||||||
dependancies = ['data/seq.db', "data/shortcodes.db"]
|
dependancies = ['data/seq.db', "data/shortcodes.db"]
|
||||||
current_uid = getuid()
|
current_uid = getuid()
|
||||||
for dependancy in dependancies:
|
for dependancy in dependancies:
|
||||||
|
# Skip check if file doesn't exist yet - SQLite will create it on first use
|
||||||
|
if not exists(dependancy):
|
||||||
|
continue
|
||||||
if stat(dependancy).st_uid != current_uid:
|
if stat(dependancy).st_uid != current_uid:
|
||||||
raise PermissionError(dependancy)
|
raise PermissionError(dependancy)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user