Remove bootstrap
This commit is contained in:
1 parent
04ea39607d
commit
531a32e394
14 files changed
+153
-394
No files matched your search
+6
-7
@@ -4,18 +4,17 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>World English Bible Updated</title>
|
<title>World English Bible Updated</title>
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="css/app.css">
|
<link rel="stylesheet" href="css/app.css">
|
||||||
<meta name="theme-color" content="#1a202c">
|
<meta name="theme-color" content="#1a202c">
|
||||||
<link rel="icon" href="icons/favicon.ico" sizes="16x16 32x32 48x48">
|
<link rel="icon" href="icons/favicon.ico" sizes="16x16 32x32 48x48">
|
||||||
<link rel="icon" href="icons/favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="icons/favicon.svg" type="image/svg+xml">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-body-tertiary">
|
<body>
|
||||||
|
|
||||||
<main class="boot container py-5 text-center">
|
<main class="boot">
|
||||||
<div class="spinner-border text-secondary mb-3" role="status" aria-hidden="true"></div>
|
<div class="spinner" role="status" aria-hidden="true"></div>
|
||||||
<p id="boot-status" class="text-body-secondary"></p>
|
<p id="boot-status"></p>
|
||||||
<noscript><p class="alert alert-warning d-inline-block">This app builds its
|
<noscript><p class="boot-warning">This app builds its
|
||||||
pages on the device and needs JavaScript.</p></noscript>
|
pages on the device and needs JavaScript.</p></noscript>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
// worker, so a device that cannot install one has nothing to read here --
|
// worker, so a device that cannot install one has nothing to read here --
|
||||||
// and a spinner that turns forever says less than the reason does.
|
// and a spinner that turns forever says less than the reason does.
|
||||||
function fail(why) {
|
function fail(why) {
|
||||||
var spinner = document.querySelector('.boot .spinner-border');
|
var spinner = document.querySelector('.boot .spinner');
|
||||||
if (spinner) spinner.remove();
|
if (spinner) spinner.remove();
|
||||||
status(why);
|
status(why);
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-13
@@ -1,6 +1,92 @@
|
|||||||
|
/* Colours and type for the whole app. The values are the ones Bootstrap
|
||||||
|
gave the page before it was taken out, so nothing shifts. */
|
||||||
:root {
|
:root {
|
||||||
--bs-body-font-family: Georgia, "Iowan Old Style", "Palatino Linotype",
|
--font-body: Georgia, "Iowan Old Style", "Palatino Linotype",
|
||||||
"Times New Roman", serif;
|
"Times New Roman", serif;
|
||||||
|
--text: #212529;
|
||||||
|
--text-muted: rgb(33 37 41 / .75);
|
||||||
|
--background: #f8f9fa;
|
||||||
|
--link: #0d6efd;
|
||||||
|
--link-hover: #0a58ca;
|
||||||
|
--focus: #0d6efd;
|
||||||
|
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root { scroll-behavior: smooth; }
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after { box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--background);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 0 0 .5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings grow with the viewport up to their full size on a wide screen. */
|
||||||
|
h1 { font-size: calc(1.375rem + 1.5vw); }
|
||||||
|
h2 { font-size: calc(1.325rem + .9vw); }
|
||||||
|
h3 { font-size: calc(1.3rem + .6vw); }
|
||||||
|
h4 { font-size: calc(1.275rem + .3vw); }
|
||||||
|
h5 { font-size: 1.25rem; }
|
||||||
|
h6 { font-size: 1rem; }
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 { font-size: 2.5rem; }
|
||||||
|
h2 { font-size: 2rem; }
|
||||||
|
h3 { font-size: 1.75rem; }
|
||||||
|
h4 { font-size: 1.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol { margin: 0 0 1rem; }
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol { padding-left: 2rem; }
|
||||||
|
|
||||||
|
:is(ul, ol) :is(ul, ol) { margin-bottom: 0; }
|
||||||
|
|
||||||
|
/* Footnote callers sit above the line without pushing it apart. */
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
top: -.5em;
|
||||||
|
font-size: .75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: var(--link); }
|
||||||
|
a:hover { color: var(--link-hover); }
|
||||||
|
|
||||||
|
/* One reading column: the full width less a gutter on anything narrower than
|
||||||
|
a laptop, and a measure that grows a step with the screen beyond that. */
|
||||||
|
main {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
padding: 0 .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
main { max-width: 760px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1400px) {
|
||||||
|
main { max-width: 880px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only A-Z and a-z: the compiler puts a drop cap on a chapter only when it
|
/* Only A-Z and a-z: the compiler puts a drop cap on a chapter only when it
|
||||||
@@ -16,7 +102,7 @@
|
|||||||
of a sentence reads as part of the text. */
|
of a sentence reads as part of the text. */
|
||||||
.v,
|
.v,
|
||||||
.chapter-number a {
|
.chapter-number a {
|
||||||
color: var(--bs-body-color);
|
color: var(--text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +117,7 @@
|
|||||||
|
|
||||||
.v:focus-visible,
|
.v:focus-visible,
|
||||||
.chapter-number a:focus-visible {
|
.chapter-number a:focus-visible {
|
||||||
outline: 2px solid var(--bs-primary);
|
outline: 2px solid var(--focus);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
@@ -41,7 +127,7 @@
|
|||||||
is -- the way a printed Bible drops it. */
|
is -- the way a printed Bible drops it. */
|
||||||
.dropcap {
|
.dropcap {
|
||||||
float: left;
|
float: left;
|
||||||
font-family: "Day Leaf Initials", var(--bs-body-font-family);
|
font-family: "Day Leaf Initials", var(--font-body);
|
||||||
font-size: 5em;
|
font-size: 5em;
|
||||||
line-height: .8;
|
line-height: .8;
|
||||||
margin: .06em .08em 0 0;
|
margin: .06em .08em 0 0;
|
||||||
@@ -110,7 +196,7 @@
|
|||||||
gap: .4em;
|
gap: .4em;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--bs-secondary-color);
|
color: var(--text-muted);
|
||||||
margin: 5rem 0 .75rem;
|
margin: 5rem 0 .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,12 +249,12 @@
|
|||||||
|
|
||||||
.d {
|
.d {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--bs-secondary-color);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footnotes {
|
.footnotes {
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color: var(--bs-secondary-color);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
.footnotes-title { font-size: 1rem; }
|
.footnotes-title { font-size: 1rem; }
|
||||||
.marker { font-weight: 700; }
|
.marker { font-weight: 700; }
|
||||||
@@ -177,12 +263,7 @@
|
|||||||
.marker { font-variant-emoji: text; }
|
.marker { font-variant-emoji: text; }
|
||||||
|
|
||||||
.chapter,
|
.chapter,
|
||||||
.v { scroll-margin-top: 4.5rem; }
|
.v { scroll-margin-top: 1rem; }
|
||||||
|
|
||||||
.reference .form-select {
|
|
||||||
width: auto;
|
|
||||||
padding-right: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index ul {
|
.index ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -223,3 +304,47 @@
|
|||||||
-webkit-hyphens: manual;
|
-webkit-hyphens: manual;
|
||||||
hyphens: manual;
|
hyphens: manual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
margin: 3rem 0 .5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-version p {
|
||||||
|
margin: .25rem 0 0;
|
||||||
|
font-size: .875em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The page shown while the service worker installs. */
|
||||||
|
.boot {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 3rem .75rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot p { color: var(--text-muted); }
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: .25em solid #6c757d;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin .75s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot .boot-warning {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #664d03;
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffe69c;
|
||||||
|
border-radius: .375rem;
|
||||||
|
}
|
||||||
Vendored
-5
@@ -1,5 +0,0 @@
|
|||||||
@charset "UTF-8";/*!
|
|
||||||
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2025 The Bootstrap Authors
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
*/:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherLine truncated
|
|
||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
*/
|
|
||||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),s=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),o=t=>{t.dispatchEvent(new Event(i))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=t=>r(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,l=t=>{if(!r(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},_=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,b=(t,e,n=!0)=>{if(!n)return void _(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),_(t))};e.addEventListener(i,a),setTimeout(()=>{r||o(e)},s)},v=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,A=/::\d+$/,E={};let T=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},O=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=x(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function L(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function S(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return O.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=S(e,i,n);if(e in C){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=k(t),c=l[a]||(l[a]={}),h=L(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function $(t,e,i,n,s){const o=L(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&$(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(w,""),C[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=k(t),c=l[rLine truncated
|
|
||||||
+2
-12
@@ -11,12 +11,7 @@
|
|||||||
'page.njk',
|
'page.njk',
|
||||||
'book.njk',
|
'book.njk',
|
||||||
'index.njk',
|
'index.njk',
|
||||||
'bookmark.njk',
|
|
||||||
'reference.njk',
|
|
||||||
'verses.njk',
|
|
||||||
'verse.njk',
|
|
||||||
'version.njk',
|
'version.njk',
|
||||||
'toast.njk',
|
|
||||||
'notice.njk'
|
'notice.njk'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -62,16 +57,11 @@
|
|||||||
return {
|
return {
|
||||||
render: render,
|
render: render,
|
||||||
|
|
||||||
compose: function (fragment, chrome) {
|
compose: function (fragment) {
|
||||||
var title = (TITLE_RE.exec(fragment) || ['', ''])[1].trim();
|
var title = (TITLE_RE.exec(fragment) || ['', ''])[1].trim();
|
||||||
var content = fragment.replace(TITLE_RE, '');
|
var content = fragment.replace(TITLE_RE, '');
|
||||||
var name = (TEMPLATE_RE.exec(content) || ['', 'page'])[1] + '.njk';
|
var name = (TEMPLATE_RE.exec(content) || ['', 'page'])[1] + '.njk';
|
||||||
return render(name, {
|
return render(name, { title: title, content: content });
|
||||||
title: title,
|
|
||||||
content: content,
|
|
||||||
mark: chrome.mark,
|
|
||||||
reference: chrome.reference
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
notice: function (heading, text) {
|
notice: function (heading, text) {
|
||||||
|
|||||||
+3
-202
@@ -22,11 +22,9 @@ const PRECACHE = [
|
|||||||
'./',
|
'./',
|
||||||
'./manifest.webmanifest',
|
'./manifest.webmanifest',
|
||||||
'./css/app.css',
|
'./css/app.css',
|
||||||
'./css/bootstrap.min.css',
|
|
||||||
'./fonts/DayLeafInitials.woff2',
|
'./fonts/DayLeafInitials.woff2',
|
||||||
'./ornaments/group-l-49.svg',
|
'./ornaments/group-l-49.svg',
|
||||||
'./js/htmx.min.js',
|
'./js/htmx.min.js'
|
||||||
'./js/bootstrap.bundle.min.js'
|
|
||||||
].concat(TEMPLATE_URLS);
|
].concat(TEMPLATE_URLS);
|
||||||
|
|
||||||
function cacheFor(req) {
|
function cacheFor(req) {
|
||||||
@@ -125,88 +123,6 @@ const view = createRenderer(readTemplate);
|
|||||||
|
|
||||||
const router = new Router(new URL('./', location.href).pathname);
|
const router = new Router(new URL('./', location.href).pathname);
|
||||||
|
|
||||||
const VERSES_URL = './verses.json';
|
|
||||||
|
|
||||||
let verses = null;
|
|
||||||
|
|
||||||
function readVerses() {
|
|
||||||
if (!verses) {
|
|
||||||
verses = readText(pageRequest(VERSES_URL))
|
|
||||||
.then(JSON.parse)
|
|
||||||
.catch((err) => { verses = null; throw err; });
|
|
||||||
}
|
|
||||||
return verses;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAnchor(id) {
|
|
||||||
const chapter = /^ch(\d+)$/.exec(id || '');
|
|
||||||
if (chapter) return { chapter: +chapter[1], verse: null };
|
|
||||||
const verse = /^v(\d+)_[0-9-]+$/.exec(id || '');
|
|
||||||
if (verse) return { chapter: +verse[1], verse: id };
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verseLabels(chapters, number) {
|
|
||||||
const entry = chapters[number - 1];
|
|
||||||
if (typeof entry !== 'number') return entry || [];
|
|
||||||
const out = [];
|
|
||||||
for (let i = 1; i <= entry; i++) out.push(String(i));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function referenceFor(map, code, anchor) {
|
|
||||||
const chapters = map && Object.prototype.hasOwnProperty.call(map, code)
|
|
||||||
? map[code] : null;
|
|
||||||
if (!chapters || !chapters.length) return null;
|
|
||||||
|
|
||||||
if (!chapters.some((entry) => (typeof entry === 'number' ? entry : entry.length))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const at = parseAnchor(anchor) || { chapter: 1, verse: null };
|
|
||||||
const number = at.chapter >= 1 && at.chapter <= chapters.length ? at.chapter : 1;
|
|
||||||
const verse = at.chapter === number ? at.verse : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
book: code,
|
|
||||||
chapter: 'ch' + number,
|
|
||||||
chapters: chapters.map((_, i) => ({ id: 'ch' + (i + 1), label: String(i + 1) })),
|
|
||||||
verse: verse,
|
|
||||||
verses: verseLabels(chapters, number)
|
|
||||||
.map((label) => ({ id: 'v' + number + '_' + label, label: label }))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function bookOf(url) {
|
|
||||||
return url ? router.relative(url).slice(1) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function referenceOf(code, anchor) {
|
|
||||||
if (!code) return null;
|
|
||||||
try {
|
|
||||||
return referenceFor(await readVerses(), code, anchor);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verseOf(page, at) {
|
|
||||||
const code = bookOf(page);
|
|
||||||
const anchor = parseAnchor(at);
|
|
||||||
if (!page || !code || !anchor || !anchor.verse) return null;
|
|
||||||
|
|
||||||
const reference = await referenceOf(code, at);
|
|
||||||
if (!reference) return null;
|
|
||||||
const option = reference.verses.find((verse) => verse.id === at);
|
|
||||||
if (!option) return null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
ref: code + ' ' + anchor.chapter + ':' + option.label,
|
|
||||||
href: code + '#' + at,
|
|
||||||
url: page.origin + page.pathname + '#' + at
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const OFFLINE_NOTICE = ['Not available offline',
|
const OFFLINE_NOTICE = ['Not available offline',
|
||||||
'This page has not been opened on this device, so there is no copy stored ' +
|
'This page has not been opened on this device, so there is no copy stored ' +
|
||||||
'here. Reconnect and try again, or open one you have read before.'];
|
'here. Reconnect and try again, or open one you have read before.'];
|
||||||
@@ -227,14 +143,9 @@ async function servePage(path, code, call) {
|
|||||||
if (!code) {
|
if (!code) {
|
||||||
fragment += '\n' + await view.render('version.njk', { version: VERSIONS });
|
fragment += '\n' + await view.render('version.njk', { version: VERSIONS });
|
||||||
}
|
}
|
||||||
const reference = await referenceOf(code, null);
|
if (!call.navigate && !call.restore) return fragment;
|
||||||
if (!call.navigate && !call.restore) {
|
|
||||||
return fragment + '\n'
|
|
||||||
+ await view.render('reference.njk', { reference: reference, oob: true });
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return await view.compose(fragment, { mark: await getBookmark(),
|
return await view.compose(fragment);
|
||||||
reference: reference });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('compose failed: ' + err.message);
|
console.error('compose failed: ' + err.message);
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -245,76 +156,6 @@ router.get('/', (call) => servePage('./', '', call));
|
|||||||
router.get('/:page', (call) =>
|
router.get('/:page', (call) =>
|
||||||
servePage('./' + call.params.page, call.params.page, call));
|
servePage('./' + call.params.page, call.params.page, call));
|
||||||
|
|
||||||
router.get('/api/reference', async (call) => {
|
|
||||||
const params = call.url.searchParams;
|
|
||||||
const page = call.currentUrl();
|
|
||||||
const moved = params.has('at') ? params.get('at') : null;
|
|
||||||
|
|
||||||
const reference = await referenceOf(
|
|
||||||
bookOf(page), moved === null ? (page && page.hash.slice(1)) : moved);
|
|
||||||
|
|
||||||
if (moved === null) {
|
|
||||||
return view.render('reference.njk', { reference: reference, settled: true });
|
|
||||||
}
|
|
||||||
if (!reference) return new Response('', { status: 204 });
|
|
||||||
|
|
||||||
const target = reference.verse || reference.chapter;
|
|
||||||
return new Response(await view.render('verses.njk', { reference: reference }), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'HX-Reswap': 'innerHTML settle:0 show:#' + target + ':top',
|
|
||||||
'HX-Replace-Url': page.pathname + page.search + '#' + target
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/api/verse', async (call) => {
|
|
||||||
const verse = await verseOf(call.currentUrl(), call.url.searchParams.get('at'));
|
|
||||||
if (!verse) return new Response('', { status: 204 });
|
|
||||||
return view.render('verse.njk', { verse: verse });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/api/bookmark', async (call) => {
|
|
||||||
try {
|
|
||||||
const form = await call.form();
|
|
||||||
const ref = String(form.get('ref') || '').trim();
|
|
||||||
const href = String(form.get('href') || '').trim();
|
|
||||||
if (!ref || !href) return new Response('', { status: 400 });
|
|
||||||
await withStore('readwrite',
|
|
||||||
(s) => s.put({ ref: ref, href: href, savedAt: Date.now() }, BOOKMARK_KEY));
|
|
||||||
return new Response(await bookmarkFragment('bookmarked'), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'HX-Trigger-After-Settle': 'verse-done'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
return bookmarkFragment('bookmark-refused');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const TOASTS = {
|
|
||||||
'copied': { text: 'Link copied.' },
|
|
||||||
'copy-refused': { text: 'Copy the link above: this browser would not '
|
|
||||||
+ 'take it.', bad: true },
|
|
||||||
'bookmarked': { text: 'Bookmark set.' },
|
|
||||||
'bookmark-refused': { text: 'Could not save the bookmark: storage '
|
|
||||||
+ 'unavailable.', bad: true }
|
|
||||||
};
|
|
||||||
|
|
||||||
function toastFragment(say, oob) {
|
|
||||||
const toast = TOASTS[say];
|
|
||||||
return view.render('toast.njk', { text: toast.text, bad: toast.bad, oob: oob });
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get('/api/toast', (call) => {
|
|
||||||
const say = call.url.searchParams.get('say');
|
|
||||||
if (!TOASTS[say]) return new Response('', { status: 204 });
|
|
||||||
return toastFragment(say, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
let filling = null;
|
let filling = null;
|
||||||
let complete = false;
|
let complete = false;
|
||||||
|
|
||||||
@@ -351,43 +192,3 @@ async function fillLibrary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DB_NAME = 'bible';
|
|
||||||
const DB_STORE = 'kv';
|
|
||||||
const BOOKMARK_KEY = 'bookmark';
|
|
||||||
|
|
||||||
const DEFAULT_BOOKMARK = { ref: 'GEN 1:1', href: 'GEN#v1_1' };
|
|
||||||
|
|
||||||
function openDb() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const req = indexedDB.open(DB_NAME, 1);
|
|
||||||
req.onupgradeneeded = () => req.result.createObjectStore(DB_STORE);
|
|
||||||
req.onsuccess = () => resolve(req.result);
|
|
||||||
req.onerror = () => reject(req.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function withStore(mode, fn) {
|
|
||||||
const db = await openDb();
|
|
||||||
try {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const tx = db.transaction(DB_STORE, mode);
|
|
||||||
const req = fn(tx.objectStore(DB_STORE));
|
|
||||||
tx.oncomplete = () => resolve(req ? req.result : undefined);
|
|
||||||
tx.onabort = tx.onerror = () => reject(tx.error);
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBookmark() {
|
|
||||||
return withStore('readonly', (s) => s.get(BOOKMARK_KEY))
|
|
||||||
.catch(() => null)
|
|
||||||
.then((mark) => mark || DEFAULT_BOOKMARK);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bookmarkFragment(say) {
|
|
||||||
return await view.render('bookmark.njk', { mark: await getBookmark() })
|
|
||||||
+ '\n' + await toastFragment(say, true);
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,9 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}{{ title | safe }}{% endblock %}</title>
|
<title>{% block title %}{{ title | safe }}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="css/app.css">
|
<link rel="stylesheet" href="css/app.css">
|
||||||
<meta name="htmx-config"
|
<meta name="htmx-config"
|
||||||
content='{"historyCacheSize":0,"refreshOnHistoryMiss":false}'>
|
content='{"historyCacheSize":0,"refreshOnHistoryMiss":false}'>
|
||||||
<script src="js/bootstrap.bundle.min.js" defer></script>
|
|
||||||
<script src="js/htmx.min.js"></script>
|
<script src="js/htmx.min.js"></script>
|
||||||
<link rel="manifest" href="manifest.webmanifest">
|
<link rel="manifest" href="manifest.webmanifest">
|
||||||
<meta name="theme-color" content="#1a202c">
|
<meta name="theme-color" content="#1a202c">
|
||||||
@@ -20,55 +18,12 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="Bible">
|
<meta name="apple-mobile-web-app-title" content="Bible">
|
||||||
</head>
|
</head>
|
||||||
<body class="{% block body_class %}{% endblock %} bg-body-tertiary"
|
<body class="{% block body_class %}{% endblock %}"
|
||||||
hx-boost="true" hx-target="#content" hx-swap="innerHTML show:window:top">
|
hx-boost="true" hx-target="#content" hx-swap="innerHTML show:window:top">
|
||||||
|
|
||||||
<nav class="site-header navbar navbar-expand-lg bg-dark sticky-top"
|
<main id="content" hx-history-elt>
|
||||||
data-bs-theme="dark">
|
|
||||||
<div class="container-xl">
|
|
||||||
<a class="navbar-brand" href="./">Bible</a>
|
|
||||||
{% include "reference.njk" %}
|
|
||||||
<button class="navbar-toggler" type="button"
|
|
||||||
data-bs-toggle="collapse" data-bs-target="#site-nav"
|
|
||||||
aria-controls="site-nav" aria-expanded="false"
|
|
||||||
aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="site-nav">
|
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
||||||
{% block navbar_nav %}
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
{% include "bookmark.njk" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="container-xl my-4">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
{# A verse number is a link to itself, so the browser handles the jump and
|
|
||||||
the address bar; this only adds the dialog on top. Enter comes free
|
|
||||||
with the link, which is why there is no key handler here. #}
|
|
||||||
<div id="content" class="col-12 col-xl-8" hx-history-elt
|
|
||||||
hx-on:click="var verse = event.target.closest('.v[id]');
|
|
||||||
if (verse) htmx.ajax('GET', 'api/verse?at=' + verse.id,
|
|
||||||
{ target: '#verse-dialog .modal-content',
|
|
||||||
swap: 'innerHTML' });">
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div class="modal fade" id="verse-dialog" tabindex="-1"
|
|
||||||
aria-labelledby="verse-dialog-ref" aria-hidden="true"
|
|
||||||
hx-on::after-swap="bootstrap.Modal.getOrCreateInstance(this).show();"
|
|
||||||
hx-on:verse-done="bootstrap.Modal.getOrCreateInstance(this).hide();">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toast-container position-fixed bottom-0 end-0 p-3" id="toasts"></div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<span class="bookmark navbar-text d-flex align-items-center gap-1" id="bookmark">
|
|
||||||
<span class="bookmark-label">Bookmark:</span>
|
|
||||||
<a class="bookmark-link link-light text-truncate" href="{{ mark.href }}"
|
|
||||||
hx-boost="false">{{ mark.ref }}</a>
|
|
||||||
</span>
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
{% extends "base.njk" %}
|
{% extends "base.njk" %}
|
||||||
{% block body_class %}browsing{% endblock %}
|
{% block body_class %}browsing{% endblock %}
|
||||||
{% block navbar_nav %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}{{ content | safe }}{% endblock %}
|
{% block content %}{{ content | safe }}{% endblock %}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<div class="reference navbar-text d-flex{% if not reference %} invisible{% endif %} align-items-center gap-1 ms-3"
|
|
||||||
id="reference"
|
|
||||||
{%- if oob %} hx-swap-oob="true"{% endif %}
|
|
||||||
hx-get="api/reference"
|
|
||||||
{# htmx keeps an event only when the filter returns true exactly, so a
|
|
||||||
string is discarded. #}
|
|
||||||
hx-trigger="{% if reference and not settled %}load[!!location.hash], {% endif %}htmx:historyRestore from:body"
|
|
||||||
hx-target="this" hx-swap="outerHTML">
|
|
||||||
<span class="reference-book text-light fw-semibold"
|
|
||||||
id="reference-book">{{ reference.book }}</span>
|
|
||||||
<label class="visually-hidden" for="reference-chapter">Chapter</label>
|
|
||||||
<select class="reference-chapter form-select form-select-sm"
|
|
||||||
id="reference-chapter" name="at"
|
|
||||||
hx-get="api/reference" hx-target="#reference-verse"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
{%- for chapter in reference.chapters %}
|
|
||||||
<option value="{{ chapter.id }}"
|
|
||||||
{%- if chapter.id == reference.chapter %} selected{% endif %}>{{ chapter.label }}</option>
|
|
||||||
{%- endfor %}
|
|
||||||
</select>
|
|
||||||
<span class="reference-sep text-light">:</span>
|
|
||||||
<label class="visually-hidden" for="reference-verse">Verse</label>
|
|
||||||
<select class="reference-verse form-select form-select-sm"
|
|
||||||
id="reference-verse" name="at"
|
|
||||||
hx-get="api/reference" hx-target="this" hx-swap="innerHTML">
|
|
||||||
{% include "verses.njk" %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{% if oob %}<div hx-swap-oob="beforeend:#toasts">{% endif %}
|
|
||||||
<div class="toast align-items-center{% if bad %} text-bg-danger{% endif %}"
|
|
||||||
role="alert" aria-live="assertive" aria-atomic="true"
|
|
||||||
hx-on::load="bootstrap.Toast.getOrCreateInstance(this).show();"
|
|
||||||
hx-on:hidden.bs.toast="this.remove();">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body">{{ text }}</div>
|
|
||||||
<button type="button"
|
|
||||||
class="btn-close{% if bad %} btn-close-white{% endif %} me-2 m-auto"
|
|
||||||
data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if oob %}</div>{% endif %}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-title fs-5 verse-dialog-ref" id="verse-dialog-ref">{{ verse.ref }}</h2>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<label class="visually-hidden" for="verse-dialog-url">Link to {{ verse.ref }}</label>
|
|
||||||
<input class="verse-dialog-url form-control form-control-sm" id="verse-dialog-url"
|
|
||||||
type="text" readonly value="{{ verse.url }}">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<form class="verse-dialog-actions d-flex flex-wrap gap-2 m-0"
|
|
||||||
hx-post="api/bookmark" hx-target="#bookmark" hx-swap="outerHTML">
|
|
||||||
<input type="hidden" name="ref" value="{{ verse.ref }}">
|
|
||||||
<input type="hidden" name="href" value="{{ verse.href }}">
|
|
||||||
<button class="btn btn-outline-secondary" type="button"
|
|
||||||
hx-on:click="var button = this;
|
|
||||||
var field = document.getElementById('verse-dialog-url');
|
|
||||||
var toast = function (say) {
|
|
||||||
htmx.ajax('GET', 'api/toast?say=' + say,
|
|
||||||
{ target: '#toasts', swap: 'beforeend' });
|
|
||||||
};
|
|
||||||
var copied = function () {
|
|
||||||
toast('copied');
|
|
||||||
button.dispatchEvent(
|
|
||||||
new CustomEvent('verse-done', { bubbles: true }));
|
|
||||||
};
|
|
||||||
var select = function () {
|
|
||||||
field.focus();
|
|
||||||
field.select();
|
|
||||||
};
|
|
||||||
var refused = function () {
|
|
||||||
select();
|
|
||||||
toast('copy-refused');
|
|
||||||
};
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
navigator.clipboard.writeText(field.value)
|
|
||||||
.then(copied, refused);
|
|
||||||
} else {
|
|
||||||
select();
|
|
||||||
if (document.execCommand('copy')) copied();
|
|
||||||
else toast('copy-refused');
|
|
||||||
}">Copy link</button>
|
|
||||||
<button class="btn btn-primary" type="submit">Set bookmark</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{% for verse in reference.verses -%}
|
|
||||||
<option value="{{ verse.id }}"
|
|
||||||
{%- if verse.id == reference.verse %} selected{% endif %}>{{ verse.label }}</option>
|
|
||||||
{% endfor -%}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
<div class="app-version text-center mt-5 mb-2">
|
<div class="app-version">
|
||||||
<p class="app-version-digests form-text m-0">
|
<p>shell {{ version.shell }} · library {{ version.library }}</p>
|
||||||
shell {{ version.shell }} · library {{ version.library }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
Reference in new issue
Block a user