├── client
├── robots.txt
├── favicon.ico
├── images
│ ├── favicon.png
│ └── scroll_210.png
├── fonts
│ ├── BF_Hebrew.woff
│ ├── TeXGyrePagella-Bold.woff
│ ├── BitstreamVeraSans-Bold.woff
│ ├── TeXGyrePagella-Italic.woff
│ ├── TeXGyrePagella-Regular.woff
│ ├── BitstreamVeraSans-Oblique.woff
│ ├── BitstreamVeraSans-Roman.woff
│ ├── TeXGyrePagella-BoldItalic.woff
│ └── BitstreamVeraSans-BoldOblique.woff
├── index.html
├── styles
│ ├── night.css
│ └── base.css
└── js
│ └── misc
│ └── zh_segment.js
├── readme
├── copyright.txt
├── credits.txt
├── mit.txt
├── gust_font_license.txt
├── ofl-1.1.txt
└── bitstream_font_license.txt
├── readme.md
├── .gitignore
└── server
├── modules
├── email.js
└── db.js
├── config.sample.js
├── index_non-js.html
└── bibleforge.js
/client/robots.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/favicon.ico
--------------------------------------------------------------------------------
/client/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/images/favicon.png
--------------------------------------------------------------------------------
/client/fonts/BF_Hebrew.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/BF_Hebrew.woff
--------------------------------------------------------------------------------
/client/images/scroll_210.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/images/scroll_210.png
--------------------------------------------------------------------------------
/client/fonts/TeXGyrePagella-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/TeXGyrePagella-Bold.woff
--------------------------------------------------------------------------------
/client/fonts/BitstreamVeraSans-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/BitstreamVeraSans-Bold.woff
--------------------------------------------------------------------------------
/client/fonts/TeXGyrePagella-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/TeXGyrePagella-Italic.woff
--------------------------------------------------------------------------------
/client/fonts/TeXGyrePagella-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/TeXGyrePagella-Regular.woff
--------------------------------------------------------------------------------
/client/fonts/BitstreamVeraSans-Oblique.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/BitstreamVeraSans-Oblique.woff
--------------------------------------------------------------------------------
/client/fonts/BitstreamVeraSans-Roman.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/BitstreamVeraSans-Roman.woff
--------------------------------------------------------------------------------
/client/fonts/TeXGyrePagella-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/TeXGyrePagella-BoldItalic.woff
--------------------------------------------------------------------------------
/client/fonts/BitstreamVeraSans-BoldOblique.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bibleforge/BibleForge/HEAD/client/fonts/BitstreamVeraSans-BoldOblique.woff
--------------------------------------------------------------------------------
/readme/copyright.txt:
--------------------------------------------------------------------------------
1 | BibleForge follows the teachings of Jesus, namely
2 |
3 | "all things whatsoever ye would that men should do to you, do ye even so to them."
4 | —Jesus (Matthew 7:12)
5 |
6 | Therefore, BibleForge is free, open source software. BibleForge is licensed under the MIT license.
7 | This basically means that you can copy it, change it, and even sell it. See mit.txt for details.
8 |
9 | Also, see credits.txt for details about specific files.
10 |
11 | Font licenses:
12 |
13 | The Bitstream Vera Fonts fonts are copyrighted under an open source license as found
14 | in bitstream_font_license.txt. The Tex Gyre Pagella fonts are also copyrighted under an
15 | open source copyright as found in gust_font_license.txt. The BF Hebrew and IM Fell DW Pica
16 | fonts are licensed under the SIL Open Font License as found in ofl-1.1.txt.
17 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
BibleForge
2 | Bible study done right
3 |
4 |
About
5 |
6 | BibleForge is a free, open source Bible Study web app. It aspires to be easy to use, pleasing to use, and useful. It is currently in the early stages of software development. If you would like to help, please dive in.
7 |
8 | To use BibleForge, all you have to do is go to bibleforge.com; however, unlike most web apps, BibleForge can be downloaded and installed. See the instructions on the wiki for details.
9 |
10 |
License
11 |
12 | Most of the BibleForge code is licensed under the MIT. See the readme directory for more details.
13 |
14 |
101 |
--------------------------------------------------------------------------------
/readme/ofl-1.1.txt:
--------------------------------------------------------------------------------
1 | SIL OPEN FONT LICENSE Version 1.1 (26 February 2007)
2 |
3 | PREAMBLE
4 |
5 | The goals of the Open Font License (OFL) are to stimulate worldwide
6 | development of collaborative font projects, to support the font creation
7 | efforts of academic and linguistic communities, and to provide a free
8 | and open framework in which fonts may be shared and improved in
9 | partnership with others.
10 |
11 | The OFL allows the licensed fonts to be used, studied, modified and
12 | redistributed freely as long as they are not sold by themselves. The
13 | fonts, including any derivative works, can be bundled, embedded,
14 | redistributed and/or sold with any software provided that any reserved
15 | names are not used by derivative works. The fonts and derivatives,
16 | however, cannot be released under any other type of license. The
17 | requirement for fonts to remain under this license does not apply to
18 | any document created using the fonts or their derivatives.
19 |
20 | DEFINITIONS
21 |
22 | "Font Software" refers to the set of files released by the Copyright
23 | Holder(s) under this license and clearly marked as such. This may
24 | include source files, build scripts and documentation.
25 |
26 | "Reserved Font Name" refers to any names specified as such after the
27 | copyright statement(s).
28 |
29 | "Original Version" refers to the collection of Font Software components
30 | as distributed by the Copyright Holder(s).
31 |
32 | "Modified Version" refers to any derivative made by adding to, deleting,
33 | or substituting -- in part or in whole -- any of the components of the
34 | Original Version, by changing formats or by porting the Font Software
35 | to a new environment.
36 |
37 | "Author" refers to any designer, engineer, programmer, technical writer
38 | or other person who contributed to the Font Software.
39 |
40 | PERMISSION & CONDITIONS
41 |
42 | Permission is hereby granted, free of charge, to any person obtaining a
43 | copy of the Font Software, to use, study, copy, merge, embed, modify,
44 | redistribute, and sell modified and unmodified copies of the Font
45 | Software, subject to the following conditions:
46 |
47 | 1) Neither the Font Software nor any of its individual components, in
48 | Original or Modified Versions, may be sold by itself.
49 |
50 | 2) Original or Modified Versions of the Font Software may be bundled,
51 | redistributed and/or sold with any software, provided that each copy
52 | contains the above copyright notice and this license. These can be
53 | included either as stand-alone text files, human-readable headers or in
54 | the appropriate machine-readable metadata fields within text or binary
55 | files as long as those fields can be easily viewed by the user.
56 |
57 | 3) No Modified Version of the Font Software may use the Reserved Font
58 | Name(s) unless explicit written permission is granted by the
59 | corresponding Copyright Holder. This restriction only applies to the
60 | primary font name as presented to the users.
61 |
62 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
63 | Software shall not be used to promote, endorse or advertise any
64 | Modified Version, except to acknowledge the contribution(s) of the
65 | Copyright Holder(s) and the Author(s) or with their explicit written
66 | permission.
67 |
68 | 5) The Font Software, modified or unmodified, in part or in whole,
69 | must be distributed entirely under this license, and must not be
70 | distributed under any other license. The requirement for fonts to
71 | remain under this license does not apply to any document created
72 | using the Font Software.
73 |
74 | TERMINATION
75 |
76 | This license becomes null and void if any of the above conditions are
77 | not met.
78 |
79 | DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS",
80 | WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
81 | INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
82 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
83 | NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR
84 | OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
85 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
86 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR
87 | CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF
88 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE
89 | USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
90 | OTHER DEALINGS IN THE FONT SOFTWARE.
91 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
67 |
68 | BibleForge
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
81 |
82 |
85 |
92 |
93 |
94 |
95 |
96 |
122 |
--------------------------------------------------------------------------------
/readme/bitstream_font_license.txt:
--------------------------------------------------------------------------------
1 | Bitstream Vera Fonts Copyright
2 |
3 | The fonts have a generous copyright, allowing derivative works (as
4 | long as "Bitstream" or "Vera" are not in the names), and full
5 | redistribution (so long as they are not *sold* by themselves). They
6 | can be be bundled, redistributed and sold with any software.
7 |
8 | The fonts are distributed under the following copyright:
9 |
10 | Copyright
11 | =========
12 |
13 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
14 | Vera is a trademark of Bitstream, Inc.
15 |
16 | Permission is hereby granted, free of charge, to any person obtaining
17 | a copy of the fonts accompanying this license ("Fonts") and associated
18 | documentation files (the "Font Software"), to reproduce and distribute
19 | the Font Software, including without limitation the rights to use,
20 | copy, merge, publish, distribute, and/or sell copies of the Font
21 | Software, and to permit persons to whom the Font Software is furnished
22 | to do so, subject to the following conditions:
23 |
24 | The above copyright and trademark notices and this permission notice
25 | shall be included in all copies of one or more of the Font Software
26 | typefaces.
27 |
28 | The Font Software may be modified, altered, or added to, and in
29 | particular the designs of glyphs or characters in the Fonts may be
30 | modified and additional glyphs or characters may be added to the
31 | Fonts, only if the fonts are renamed to names not containing either
32 | the words "Bitstream" or the word "Vera".
33 |
34 | This License becomes null and void to the extent applicable to Fonts
35 | or Font Software that has been modified and is distributed under the
36 | "Bitstream Vera" names.
37 |
38 | The Font Software may be sold as part of a larger software package but
39 | no copy of one or more of the Font Software typefaces may be sold by
40 | itself.
41 |
42 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
44 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
45 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
46 | BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
47 | OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
48 | OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
49 | OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
50 | SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
51 |
52 | Except as contained in this notice, the names of Gnome, the Gnome
53 | Foundation, and Bitstream Inc., shall not be used in advertising or
54 | otherwise to promote the sale, use or other dealings in this Font
55 | Software without prior written authorization from the Gnome Foundation
56 | or Bitstream Inc., respectively. For further information, contact:
57 | fonts at gnome dot org.
58 |
59 | Copyright FAQ
60 | =============
61 |
62 | 1. I don't understand the resale restriction... What gives?
63 |
64 | Bitstream is giving away these fonts, but wishes to ensure its
65 | competitors can't just drop the fonts as is into a font sale system
66 | and sell them as is. It seems fair that if Bitstream can't make money
67 | from the Bitstream Vera fonts, their competitors should not be able to
68 | do so either. You can sell the fonts as part of any software package,
69 | however.
70 |
71 | 2. I want to package these fonts separately for distribution and
72 | sale as part of a larger software package or system. Can I do so?
73 |
74 | Yes. A RPM or Debian package is a "larger software package" to begin
75 | with, and you aren't selling them independently by themselves.
76 | See 1. above.
77 |
78 | 3. Are derivative works allowed?
79 | Yes!
80 |
81 | 4. Can I change or add to the font(s)?
82 | Yes, but you must change the name(s) of the font(s).
83 |
84 | 5. Under what terms are derivative works allowed?
85 |
86 | You must change the name(s) of the fonts. This is to ensure the
87 | quality of the fonts, both to protect Bitstream and Gnome. We want to
88 | ensure that if an application has opened a font specifically of these
89 | names, it gets what it expects (though of course, using fontconfig,
90 | substitutions could still could have occurred during font
91 | opening). You must include the Bitstream copyright. Additional
92 | copyrights can be added, as per copyright law. Happy Font Hacking!
93 |
94 | 6. If I have improvements for Bitstream Vera, is it possible they might get
95 | adopted in future versions?
96 |
97 | Yes. The contract between the Gnome Foundation and Bitstream has
98 | provisions for working with Bitstream to ensure quality additions to
99 | the Bitstream Vera font family. Please contact us if you have such
100 | additions. Note, that in general, we will want such additions for the
101 | entire family, not just a single font, and that you'll have to keep
102 | both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
103 | glyphs to the font, they must be stylistically in keeping with Vera's
104 | design. Vera cannot become a "ransom note" font. Jim Lyles will be
105 | providing a document describing the design elements used in Vera, as a
106 | guide and aid for people interested in contributing to Vera.
107 |
108 | 7. I want to sell a software package that uses these fonts: Can I do so?
109 |
110 | Sure. Bundle the fonts with your software and sell your software
111 | with the fonts. That is the intent of the copyright.
112 |
113 | 8. If applications have built the names "Bitstream Vera" into them,
114 | can I override this somehow to use fonts of my choosing?
115 |
116 | This depends on exact details of the software. Most open source
117 | systems and software (e.g., Gnome, KDE, etc.) are now converting to
118 | use fontconfig (see www.fontconfig.org) to handle font configuration,
119 | selection and substitution; it has provisions for overriding font
120 | names and subsituting alternatives. An example is provided by the
121 | supplied local.conf file, which chooses the family Bitstream Vera for
122 | "sans", "serif" and "monospace". Other software (e.g., the XFree86
123 | core server) has other mechanisms for font substitution.
124 |
125 |
--------------------------------------------------------------------------------
/client/styles/night.css:
--------------------------------------------------------------------------------
1 | /**
2 | * BibleForge
3 | *
4 | * @date 11-30-12
5 | * @version alpha (α)
6 | * @link http://BibleForge.com
7 | * @license The MIT License (MIT)
8 | */
9 |
10 | /*!
11 | * The BibleForge motto:
12 | *
13 | * "all things whatsoever ye would that men should do to you, do ye even so to them."
14 | * —Jesus (Matthew 7:12)
15 | */
16 |
17 | /*!
18 | * Copyright (C) 2022
19 | *
20 | * Permission is hereby granted, free of charge, to any person obtaining
21 | * a copy of this software and associated documentation files (the
22 | * “Software”), to deal in the Software without restriction, including
23 | * without limitation the rights to use, copy, modify, merge, publish,
24 | * distribute, sublicense, and/or sell copies of the Software, and to
25 | * permit persons to whom the Software is furnished to do so.
26 | *
27 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | */
35 |
36 | /* NOTE: It might be nice to apply transitions to all of these elements (without the .night) in order to fade in and out of Night Mode; */
37 | /* however, currently gradients do not transition (although the spec says they should), so it doesn't really work right now. */
38 |
39 | /* NOTE: Both the and tags have a background color. */
40 | /* Also, if the tag's background is not set, parts of the viewport may appear white. */
41 | /* NOTE: Since the tag has the class, the class name must come after its tag name. */
42 | .night body, html.night {
43 | background: #121212;
44 | color: #a7a7a3;
45 | }
46 |
47 | .night .q {
48 | color: #F33;
49 | }
50 | .night .black_letter .q {
51 | color: #828282;
52 | }
53 |
54 | .night .searchBars {
55 | /* Transparent Black background for Firefox 3.5-/Opera 10 */
56 | background: rgba(0,0,0,.79);
57 | /* Chrome/Safari 5.1+ */
58 | background: -webkit-linear-gradient(rgba(0,0,0,.93), rgba(0,0,0,.86) 25%, rgba(0,0,0,.7));
59 | /* IE 10, Firefox 16+, Opera 12.10+ */
60 | background: linear-gradient(rgba(0,0,0,.93), rgba(0,0,0,.86) 25%, rgba(0,0,0,.7));
61 | }
62 |
63 | .night .infoBars {
64 | background: rgba(0,0,0,0.65);
65 | border-color: #000;
66 | }
67 |
68 | .night .infoLeft {
69 | /* Add a slight radial blur to make sure the text is always readable.*/
70 | /* Chrome/Safari 5.1+ */
71 | background: -webkit-radial-gradient(ellipse farthest-corner, #000 70%, rgba(0,0,0,0) 100%);
72 | /* IE 10, Firefox 16+, Opera 12.10+ */
73 | background: radial-gradient(ellipse farthest-corner, #000 70%, rgba(0,0,0,0) 100%);
74 | }
75 |
76 | .night .queryInput {
77 | background: rgba(0,0,0,.2);
78 | color: rgba(130,130,130,.7);
79 | border-color: rgba(130,130,130,.5);
80 | }
81 |
82 | .night .queryInput:hover {
83 | border-color: rgba(130,130,130,.85);
84 | }
85 |
86 | .night .queryInput:focus {
87 | background: #000;
88 | color: #828282;
89 | border-color: #828282;
90 | }
91 |
92 | .night .lang {
93 | color: rgba(130,130,130,.7);
94 | border-right-color: rgba(130,130,130,.55);
95 | }
96 |
97 | /* NOTE: The activeLang class is used to keep the language button highlighted while the context menu is open. */
98 | .night .lang:hover, .night .activeLang {
99 | color: #828282;
100 | border-right-color: #828282;
101 | }
102 |
103 |
104 | .night .callout, .night .panel {
105 | background: rgba(18,18,18,.93);
106 | border-color: #828282;
107 | }
108 |
109 | .night .callout .pointer-down {
110 | /* Chrome/Safari 5.1+ */
111 | background: -webkit-linear-gradient(left top, rgba(0,0,0,0) 41%, rgba(18,18,18,.97) 41%);
112 | /* IE 10, Firefox 16+, Opera 12.10+ */
113 | background: linear-gradient(to right bottom, rgba(0,0,0,0) 41%, rgba(18,18,18,.97) 41%);
114 | border-color: #828282;
115 | }
116 |
117 | .night .callout .pointer-up {
118 | /* Chrome/Safari 5.1+ */
119 | background: -webkit-linear-gradient(right bottom, rgba(0,0,0,0) 45%, rgba(18,18,18,.97) 45%);
120 | /* IE 10, Firefox 16+, Opera 12.10+ */
121 | background: linear-gradient(to left top, rgba(0,0,0,0) 45%, rgba(18,18,18,.97) 45%);
122 | border-color: #828282;
123 | }
124 |
125 | .night .transparent_el {
126 | background: #000;
127 | }
128 |
129 | .night .more-button {
130 | border-color: rgba(130,130,130,.4);
131 | color: rgba(130,130,130,.6);
132 | background: rgba(0,0,0,0);
133 | }
134 |
135 | .night .button {
136 | border-color: rgba(130,130,130,.55);
137 | color: rgba(130,130,130,.7);
138 | background: rgba(0,0,0,0);
139 | }
140 |
141 | .night .more-button:hover, .night .button:hover {
142 | border-color: #828282;
143 | color: #828282;
144 | background: rgba(0,0,0,1);
145 | }
146 |
147 | .night .button {
148 | box-shadow: none;
149 | text-shadow: none;
150 | }
151 |
152 | .night .emailForm input, .night .emailForm textarea {
153 | background: #121212;
154 | color: #828282;
155 | border-color: #828282;
156 | }
157 |
158 |
159 | .night .contextMenu {
160 | background: rgba(18,18,18,.94);
161 | }
162 |
163 | .night .menu_item_selected {
164 | background: #828282;
165 | color: #000;
166 | }
167 |
168 | .night .panel a {
169 | color: #EEE;
170 | }
171 |
172 | .night .inputIcon {
173 | background-image: url(data:image/gif;base64,R0lGODdhEAAQAMIIAAEBAS0tLWFhYYyMjKWlpcnJyeLi4vv7+ywAAAAAEAAQAAADQwi6EHTHDEbFISGIcgIFlscMx3cIH1BMTJkKBhO430zRlN2KFEH0OMWMJ4TwLCjZiQSBJBdOWapJnBpTDMMVu+BUsQkAOw==);
174 | }
175 |
176 | .night .loaders {
177 | background-image: url(data:image/gif;base64,R0lGODlhIAAgAOMMABISEhsbGycnJzk5OUNDQ0lJSWVlZXt7e6mpqcnJyeHh4fv7+////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQECgD/ACwAAAAAIAAgAAAE5xDISalZpurNp1pKJQSdVhzVolJDUZRUoCgIpS5T4BIwNSsvyW1CcAl6k8MsMRkCBDskJTFDAZyuAkkqKfxIQ2hhQBFvAYVEIjNBVDW6XNE4MagTiOBAwe60smQUCHd4Rz1ZBQRnFAWDd0hihh12CUE9kjABVlycXIg7AQMGB6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YBvpJivxNaGmLHT0VnOgWYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHQLYKhKP1oZmADdEAAAh+QQFCgAPACwAAAAAGAAXAAAEcvDJSasqNeuZVKqHsYmUYlLJsmjJV56SoSpsS0wmLSnqoRktBA52mG0eiBYp9xCoFqRNy8VEqFzNw+GGwlJkgwlBqxVIBAnMkVIghwLrjcDti2/Gh7D9qN774wEEBYOEfw+ChIV/gYmDho+QkZKTR3p7EQAh+QQFCgAPACwBAAAAHQAOAAAEcvDJSWdJNev9UEJZwU0CUSUohSjKKB2HOKGYVLD1CB/CnEoJlktC2PlyuKHEADM9aI8ASyGbBDYC4zN1YIEmh8XCECgQehNmTNNaKsQLXmFOuEYPg9EgAV/UBnNzeUp9VBQBBIFOLmFxWHNoQwiRWEocEQAh+QQFCgAPACwHAAAAGQARAAAEafDJ+cYZNOttjsnf9gRCdpxZkohFgU3nQani0xYBjEqIGmqCFkEnexhUiNqD0Co9YhLVShm4SaAPWkahKFAGTU0h4RxzFWJnDXFWJJWb9pTihRu5dvghl+/7NQuBggo/fYKHC38PiAuEEQAh+QQFCgAPACwOAAAAEgAYAAAEZlAY8aq9uBj8COWPIV7GcYDhWAnmCYpb1RIoXBEtmsbt944FU6xiSCRQg0PAgjAmhiBB06mzOBMIWrVihG6/4K9iTD5WyWjFOZ0ohMMH3/ewWLi+9brCCxok8gtmWwYKeXdbdAtuEQAh+QQFCgAPACwOAAAAEgAeAAAEgDCQ8Kq9mBSCB75FCBrGV4XFRaYmahWk14rPQJbm4z53foo2A6tCOBwEJsFQYDRyfAFD84DzTY8+y8CZxQy74GxiTEZ8P+R0QqxGhN8WytugUMDr9cQ5icAr3CYHCzIFCXgmCAsLaxeGJgGKC1Vdggt2bwqKB28FkXAJinJhVCYRACH5BAUKAA8ALA8AAQARAB8AAAR88Mn5RKAYj1KyDxzhZQQnjJQQohRXXGzFDSkHU/cTlCa7uTSUq0DIeVSFU0yiXDo9h6jUEIRKr6hrlPrsOguJhMEZDiOe5cTIoKAgymPKQaE4TwxhyWIvoSs6HnsLE3RqgXwSCXQjghR+h4MTCIsZjRiAGAoLbU4GC3FLEQAh+QQFCgAPACwIAA4AGAASAAAEbPDJSesrOJNg+8wg4Xkgto1oihYqOhyH2FpwLcxUYNSHMReJ20PAkz0Gi0MnkagQYMJHYrFAPBRYCbM5MlAXHKxCYmBaPQqq8pqVIJg+GnUsEVO2nfQizqZPmB1UXHVtE3wVOxUJCoM4H34qEQAh+QQFCgAPACwCABIAHQAOAAAEePDJSat94lKBtBVFkV3HshweRYQFEVCFYpopNbDFICXzkuieweH1CKxCgpJJYZAImJbC4dCcCFYBgwk1QSgUqIRYMj2MLMRJ7LsbPwjl2iTBbicmhilB/vje7VZxNXQKBXNuEnlcKV8dh38TBGcehhUICY58cpA1EQAh+QQFCgAPACwAAA8AGQARAAAEZ9CoReu7OOtb+9pgJnlfaJ7oGRwpqihGCybvK2dIrSCoUGy1xO8i3PgKg0zBpkkkLofopUANoJzPR5T1OBJOCGdMK70QqIKQwcmDlh8B6nCWdXMvA2rIqdlqBFZqGgYHYzcaAwdJGxEAIfkEBQoADwAsAQAIABEAGAAABF2wrHWevfieuZTJ4JNwSzKEmKGQaLZRbSZUcW3feK7vqJEkNgLi96sREwhBy/dDXAKHE+j3uRgOh8KjwEXNsBauNoQ9EMJdEKF8EZOxSvTYlcW4QYK5hVA43wIEAREAIfkEBQoADwAsAAACAA4AHQAABHLwyfnMoXiulTLeS+FNx6aMk7JdqAEKaUclG5IqSiI+w8JKCBwOAfMUEsIT6oEzLB+B57KASFivmatWRqFuudLwDkU4HAYjgXntsawNUUzZbEBLCvGH+kCgDAoFRR4BgGMeBICCGQKAfWSAeUYFdigBihEAOw==);
178 | }
179 |
180 | .night .crown_loader {
181 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAQAAAAngNWGAAAAAXNSR0IArs4c6QAAAXZJREFUKM+F0rtLFQAUB+CfXnWQCIMeQxQEYQ8IJcqgpTIiC4cM2/oferuIDU5tRkUNikWBNBT0IEWEmtKgLtSW4GSbWVLpZNevIeLSvYJnO4fvwDmHk1SF7R5lrVDnnWVM27Q6OGOzLh0+Gteq2bAlHbrtqoRjSn5ZwJD6RJ+SRV/xxtUy22rZlcSAaRPggzGjiU7sK8MuS4kNOJ54rjHRhJbEmN4yPOm7AbxO1GlIEqeN4L6ii+VNP+GzE85WTL7TJOZt/ptOGTdkIlH3H6tJ3FZ0z6zGuKGkVQG1VUd77Kf2BPtjjxXNCUarYI05bQmeRcG0YX3eq7euio54oceChiQ2WlQyan0FOqbWIGj7V+qw6BVuOqWQJGoT1zFpxqVy7zlziRbsThK3bEtwPvFUfxnusKIz8cCUO77goUFvE3uVtJdht28YV0TRUYe8xIwnfvtRud8BvS6YdzdJ9OCyfoddW/0vt5gFCw6u9eMFA45oqqz/ATzLTtJ03pdKAAAAAElFTkSuQmCC);
182 | opacity: .4;
183 | }
184 |
185 | .night .dropdown, .night .lang:after, .night .expandable_summary:before {
186 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAQAAAC/O6xzAAAAAXNSR0IArs4c6QAAAF9JREFUCNdj6OLqSu163/UfCU7uEmPoYuvS7lrY9RUueLTLp4uLoYsJqN6oawlU4nhXYBd/FzMDA0iCGyixuOsLUGVgl0AXCwMEgHXodZV0uQNVwgShEmxAIQ6wdiAAAAUwNSUbjgKTAAAAAElFTkSuQmCC);
187 | }
188 |
189 | .night .wrenchIcon {
190 | background-image: url(data:image/gif;base64,R0lGODdhEAAQAMIIAAMDAyoqKk9PT3R0dJeXl7Gxsc7Ozv39/SwAAAAAEAAQAAADQwi6S3eGsBnMe2My+0zQi3Adn5JNw1gAQXFo6UhwFDc+61LdY6m4vIeAAbwQChEfoIgBLW8njUPmBNwk1ekBW31yqwkAOw==);
191 | opacity: .25;
192 | }
193 |
194 | .lang:active, .activeLang, .more-button:active {
195 | background: rgba(0,0,0,.2);
196 | }
197 |
198 | .night .lang:after {
199 | opacity: .5;
200 | }
201 | .night .lang:hover:after, .activeLang:after {
202 | opacity: .9!important;
203 | }
204 |
205 | .night .menu_item_line div {
206 | border-top: 1px solid #828282;
207 | }
208 |
209 | /* Remove shadows to speed up painting. */
210 | .night .infoBars, .night .queryInput, .night .contextMenu, .night .panel, .night .button, .night .callout, .night .pointer-down, .night .pointer-up, .night .more-button {
211 | box-shadow: none;
212 | text-shadow: none;
213 | }
214 |
215 | /* WebKit's default font color is too bright, so it needs to be lightened. */
216 | .night input::-webkit-input-placeholder, .night textarea::-webkit-input-placeholder {
217 | color: rgba(130,130,130,.65);
218 | }
219 |
220 | .night ::-moz-selection {
221 | background: #828282;
222 | color: #000;
223 | }
224 | .night ::selection {
225 | /* Because WebKit handles selection background color differently, it needs to be lighter. */
226 | background: rgba(130,130,130,.5);
227 | color: #000;
228 | }
229 |
230 | .night .q::-moz-selection {
231 | color: #A00;
232 | }
233 | .night .q::selection {
234 | color: #A00;
235 | }
236 |
237 | .night .menu_item_selected ::-moz-selection, .night .panel a::-moz-selection {
238 | color: #EEE;
239 | }
240 | .night .menu_item_selected ::selection, .night .panel a::selection {
241 | color: #EEE;
242 | }
243 |
244 |
245 | /***********************/
246 | /* WebKit specific CSS */
247 | /***********************/
248 |
249 | /* Set the style for the main scrollbar. */
250 | /* NOTE: Other scrollbars (like in
tags) do not need to be styled because they are transparent and the background color shows through. */
251 | html.night > ::-webkit-scrollbar {
252 | background: #000;
253 | }
254 |
--------------------------------------------------------------------------------
/server/modules/db.js:
--------------------------------------------------------------------------------
1 | /**
2 | * BibleForge
3 | *
4 | * @date 05-28-12
5 | * @version alpha (α)
6 | * @link http://BibleForge.com
7 | * @license The MIT License (MIT)
8 | */
9 |
10 | /*!
11 | * The BibleForge motto:
12 | *
13 | * "all things whatsoever ye would that men should do to you, do ye even so to them."
14 | * —Jesus (Matthew 7:12)
15 | */
16 |
17 | /*!
18 | * Copyright (C) 2022
19 | *
20 | * Permission is hereby granted, free of charge, to any person obtaining
21 | * a copy of this software and associated documentation files (the
22 | * “Software”), to deal in the Software without restriction, including
23 | * without limitation the rights to use, copy, modify, merge, publish,
24 | * distribute, sublicense, and/or sell copies of the Software, and to
25 | * permit persons to whom the Software is furnished to do so.
26 | *
27 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | */
35 |
36 | /// Set JSHint options.
37 | // jshint bitwise:true, curly:true, eqeqeq:true, forin:true, immed:true, latedef:true, newcap:true, noarg:true, noempty:true, nonew:true, onevar:true, plusplus:true, quotmark:double, strict:true, undef:true, unused:strict, node:true
38 |
39 | "use strict";
40 |
41 | /// How long to wait after an error
42 | var reconnect_delay = 1000;
43 |
44 | /**
45 | * Check if a Node.js module exists.
46 | *
47 | * @param name (string) The name of the module to check for
48 | * @return TRUE if the module was found and FALSE if not
49 | */
50 | function module_exists(name)
51 | {
52 | try {
53 | require.resolve(name);
54 | return true;
55 | } catch (err) {
56 | return false;
57 | }
58 | }
59 |
60 | /**
61 | * Escape special symbols used in Sphinx and the db connectors.
62 | *
63 | * @param str (string) The string to escape.
64 | * @return An escaped string
65 | */
66 | function sphinx_escape(str)
67 | {
68 | /// Because question marks (?) are a special symbol to the database connectors, they must be escaped too.
69 | /// Semicolons are special symbols in SphinxQL and need to be escaped.
70 | return str.replace(/\?/g, "\\?").replace(/;/g, "\\\\;");
71 | }
72 |
73 | /**
74 | * Create an abstraction around the non-blocking Maria/MySQL connector.
75 | *
76 | * @param config (object) A BibleForge DB config object (see init() for details)
77 | */
78 | function create_connection_non_blocking(config, set_status)
79 | {
80 | var client = new (require("mariasql"))(),
81 | connecting,
82 | connected;
83 |
84 | /**
85 | * Open a connection to the database if one does not already exist.
86 | */
87 | function connect()
88 | {
89 | /// Make sure an open connection has not already been established or in the process of being established; otherwise, an infinite connection loop could occur.
90 | if (!connected && !connecting) {
91 | connecting = true;
92 | client.connect({
93 | host: config.host,
94 | user: config.user,
95 | port: config.port,
96 | password: config.pass,
97 | db: config.base,
98 | });
99 | }
100 | }
101 |
102 | /**
103 | * Reconnect on disconnections.
104 | *
105 | * @param err (object) (optional) An error message from the server
106 | */
107 | function handle_disconnect(err)
108 | {
109 | connecting = false;
110 | ///TODO: Log disconnection.
111 | connected = false;
112 | set_status(0);
113 | setTimeout(connect, reconnect_delay);
114 |
115 | if (err) {
116 | console.log(err);
117 | }
118 | }
119 |
120 | /// Open a connection.
121 | connect();
122 |
123 | /// Attach functions to the connect, error, and close events.
124 | client.on("connect", function ()
125 | {
126 | connecting = false;
127 | connected = true;
128 | set_status(1);
129 | }).on("error", handle_disconnect).on("close", handle_disconnect);
130 |
131 | return {
132 | /**
133 | * Escape a sphinx query argument.
134 | *
135 | * @example var sql = "SELECT * FROM `table` WHERE query = \"" + db_client.escape_sphinx("fake query;limit=99999") + ";ranker=none\"";
136 | * @param str (string) The string to escape.
137 | * @return An escaped string.
138 | * @note This requires an open connection to a database.
139 | */
140 | escape_sphinx: function (str)
141 | {
142 | return sphinx_escape(client.escape(str));
143 | },
144 | /**
145 | * Send a simple SQL query and get any and all results.
146 | *
147 | * @param sql (string) The SQL string to execute.
148 | * @param callback (function) (optional) The function to call after all of the data is returned or an error occurred
149 | * The callback() function will receive one variable if the query succeeds
150 | * and two (the first will be undefined) on errors.
151 | */
152 | query: function (sql, callback)
153 | {
154 | /// Send the query.
155 | client.query(sql).on("result", function (res)
156 | {
157 | var data = [];
158 |
159 | res.on("row", function (row)
160 | {
161 | /// Store each row in an array.
162 | data[data.length] = row;
163 | }).on("end", function ()
164 | {
165 | /// Once the query has finished, send the results to the callback, if any.
166 | if (callback) {
167 | callback(data);
168 | }
169 | });
170 | }).on("error", function (err)
171 | {
172 | /// Catch errors.
173 | ///TODO: Log errors.
174 | if (callback) {
175 | callback(undefined, err);
176 | }
177 | });
178 | },
179 | };
180 | }
181 |
182 | /**
183 | * Create an abstraction around the pure JavaScript Maria/MySQL connector.
184 | *
185 | * @param config (object) A BibleForge DB config object (see init() for details)
186 | */
187 | function create_connection_js(config, set_status)
188 | {
189 | var client,
190 | connecting,
191 | connected;
192 |
193 | /**
194 | * Create the connection object and make the connection.
195 | */
196 | function create_connection()
197 | {
198 | client = require("mysql").createConnection({
199 | host: config.host,
200 | user: config.user,
201 | port: config.port,
202 | password: config.pass,
203 | database: config.base,
204 | socketPath: config.sock,
205 | });
206 | }
207 |
208 | /**
209 | * Open and prepare a connection to the database if one does not already exist.
210 | */
211 | function connect()
212 | {
213 | if (!connected && !connecting) {
214 | connecting = true;
215 | /// In this module, a connection can only be used once (it cannot be used to reconnect to a server), so a new client object has to be created each time.
216 | create_connection();
217 | /// Attach functions to the connect, error, and close events.
218 | ///NOTE: This has to be done each time because this connector cannot reuse a connection object.
219 | client.on("error", handle_disconnect);
220 | client.connect(function on_connect(err)
221 | {
222 | /// Was there an error connecting?
223 | if (err) {
224 | handle_disconnect(err);
225 | } else {
226 | /// If no error was reported then the connection apparently opened correctly.
227 | connecting = false;
228 | connected = true;
229 | set_status(1);
230 | }
231 | });
232 | }
233 | }
234 |
235 | /**
236 | * Reconnect on disconnections.
237 | *
238 | * @param err (object) (optional) An error message from the server
239 | */
240 | function handle_disconnect(err)
241 | {
242 | connecting = false;
243 | ///TODO: Log disconnection.
244 | connected = false;
245 | set_status(0);
246 | setTimeout(connect, reconnect_delay);
247 |
248 | if (err) {
249 | console.log(err);
250 | }
251 | }
252 |
253 | /// Open a connection.
254 | connect();
255 |
256 | return {
257 | /**
258 | * Escape a sphinx query argument.
259 | *
260 | * @example var sql = "SELECT * FROM `table` WHERE query = \"" + db_client.escape_sphinx("fake query;limit=99999") + ";ranker=none\"";
261 | * @param str (string) The string to escape.
262 | * @return An escaped string.
263 | * @note This requires an open connection to a database.
264 | */
265 | escape_sphinx: function (str)
266 | {
267 | /// This module adds quotations around strings, but those will cause issues with Sphinx.
268 | return sphinx_escape(client.escape(str)).slice(1, -1);
269 | },
270 | /**
271 | * Send a simple SQL query and get the results.
272 | *
273 | * @param sql (string) The SQL string to execute.
274 | * @param callback (function) (optional) The function to call after all of the data is returned or an error occurred
275 | * The callback() function will receive one variable if the query succeeds
276 | * and two (the first will be undefined) on errors.
277 | */
278 | query: function (sql, callback)
279 | {
280 | /// Send the query.
281 | client.query(sql, function (err, data)
282 | {
283 | if (callback) {
284 | callback(data, err);
285 | }
286 | });
287 | },
288 | };
289 | }
290 |
291 | /**
292 | * Create the database abstraction layer.
293 | *
294 | * @param db_config (array || object) An object or array of objects describing how to configure the database (see config.sample.js)
295 | * Object structure:
296 | * {
297 | * host: "The host's address",
298 | * port: "The port to listen to",
299 | * sock: "The Unix file socket if not listening to a port",
300 | * user: "The database username",
301 | * user: "The database user's password",
302 | * base: "The database to connect to",
303 | * }
304 | */
305 | exports.db = function init(db_config)
306 | {
307 | var create_connection,
308 | request_a_client,
309 | servers = [],
310 | servers_count;
311 |
312 | /// If the mariasql module exists use that.
313 | ///NOTE: The mariasql module is faster and non-blocking, but it is a C module, so it is not as portable and may not be installed.
314 | if (module_exists("mariasql")) {
315 | create_connection = create_connection_non_blocking;
316 | } else {
317 | /// As a fallback, use the pure JavaScript client.
318 | create_connection = create_connection_js;
319 | }
320 |
321 | /// If only an object was sent, turn it into an array for convenience’s sake.
322 | if (!Array.isArray(db_config)) {
323 | db_config = [db_config];
324 | }
325 |
326 | servers_count = db_config.length - 1;
327 |
328 | /// Create a connection to each server.
329 | db_config.forEach(function (config)
330 | {
331 | var which = servers.length;
332 | servers[which] = {
333 | connection: create_connection(config, function set_status(status)
334 | {
335 | servers[which].online = status === 1;
336 | })
337 | };
338 | });
339 |
340 | /**
341 | * Get a free database client
342 | *
343 | * @return A client object or FALSE if none could be found
344 | * @note This uses a round robin method.
345 | * @note It would be nice if we could make a server as a backup server and only query it when the other servers are down.
346 | */
347 | request_a_client = (function ()
348 | {
349 | /// Since it adds 1 at the beginning of the loop, we start with -1 to go to 0 (i.e., the first server).
350 | var which_server = -1;
351 |
352 | return function ()
353 | {
354 | var tries = 0;
355 |
356 | /// Loop through all available servers (from where we left off) and try to find one online.
357 | for (;;) {
358 | /// Try the next server.
359 | which_server += 1;
360 |
361 | if (which_server > servers_count) {
362 | which_server = 0;
363 | }
364 |
365 | if (servers[which_server].online) {
366 | /// If a server is online, Stop looping and use that server.
367 | break;
368 | }
369 |
370 | /// If that server is offline, keep track of how many we've tried and try the next one. This way we know when we've tried them all.
371 | tries += 1;
372 |
373 | /// If we have tried all of the clients, give up.
374 | if (tries > servers_count) {
375 | return false;
376 | }
377 | }
378 |
379 | ///TODO: Connection pooling.
380 | return servers[which_server].connection;
381 | };
382 | }());
383 |
384 | return {
385 | /**
386 | * Execute a query.
387 | *
388 | * @param sql (string) The SQL query to execute
389 | * @param callback (function) The function to call after the query returns
390 | * @note If the database has not yet been connected to, the query will be queued.
391 | */
392 | query: function query(sql, callback)
393 | {
394 | var client = request_a_client();
395 |
396 | /// Were no clients available?
397 | if (!client) {
398 | /// If we cannot get a client, wait and try again later.
399 | setTimeout(function ()
400 | {
401 | query(sql, callback);
402 | }, 100);
403 | return;
404 | }
405 |
406 | /// Hand off the query to the client.
407 | client.query(sql, callback);
408 | },
409 | /// For more complex queries (like ones involving escaping) a client object and be requested directly.
410 | /// Client's have the following functions:
411 | /// .escape_sphinx()
412 | /// .query()
413 | request_a_client: request_a_client,
414 | };
415 | };
416 |
--------------------------------------------------------------------------------
/client/js/misc/zh_segment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * BibleForge
3 | *
4 | * @date 03-19-13
5 | * @version alpha (α)
6 | * @link http://BibleForge.com
7 | * @license The MIT License (MIT)
8 | */
9 |
10 | /*!
11 | * The BibleForge motto:
12 | *
13 | * "all things whatsoever ye would that men should do to you, do ye even so to them."
14 | * —Jesus (Matthew 7:12)
15 | */
16 |
17 | /*!
18 | * Copyright (C) 2022
19 | *
20 | * Permission is hereby granted, free of charge, to any person obtaining
21 | * a copy of this software and associated documentation files (the
22 | * “Software”), to deal in the Software without restriction, including
23 | * without limitation the rights to use, copy, modify, merge, publish,
24 | * distribute, sublicense, and/or sell copies of the Software, and to
25 | * permit persons to whom the Software is furnished to do so.
26 | *
27 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | */
35 |
36 | /// Set JSHint options.
37 | // jshint bitwise:true, curly:true, eqeqeq:true, forin:true, immed:true, latedef:true, newcap:true, noarg:true, noempty:true, nonew:true, onevar:true, plusplus:true, quotmark:double, strict:true, undef:true, unused:strict, browser:true, node:true
38 |
39 | (function ()
40 | {
41 | "use strict";
42 |
43 | var analysis,
44 | dict,
45 | p;
46 |
47 | function get_significance(x)
48 | {
49 | var i;
50 |
51 | /// Find which p element to use.
52 | for (i = p.length - 2; i > 0; i -= 1) {
53 | if (x >= p[i].x) {
54 | break;
55 | }
56 | }
57 |
58 | /// If the forumal has not been solved yet for these two points (i, i + 1), solve it now.
59 | if (typeof p[i].a === "undefined") {
60 | p[i].a = Math.pow((p[i + 1].y / p[i].y), (1 / ((p[i + 1].x - p[i].x))));
61 | p[i].yint = p[i].y / (Math.pow(p[i].a, p[i].x));
62 | }
63 |
64 | return p[i].yint * Math.pow(p[i].a, x);
65 | }
66 |
67 | function copy(mixed)
68 | {
69 | return JSON.parse(JSON.stringify(mixed));
70 | }
71 |
72 | function get_probabilities(str)
73 | {
74 | var i,
75 | len = str.length,
76 | prob,
77 | probs = [];
78 |
79 | for (i = 0; i < len; i += 1) {
80 | prob = analysis[str[i]];
81 | if (!prob) {
82 | /// If the character is unknown, just assign it a simple probability.
83 | prob = {L: 50, M: 1, R: 50, guess: true};
84 | }
85 |
86 | /// Expand short hand.
87 | if (prob.R === 1 && typeof prob.L === "undefined") {
88 | prob.R = 100;
89 | } else if (prob.L === 1 && typeof prob.R === "undefined") {
90 | prob.L = 100;
91 | }
92 |
93 | probs[probs.length] = prob;
94 | }
95 |
96 | return probs;
97 | }
98 |
99 | function isolate_chinese_characters(str)
100 | {
101 | ///NOTE: This would include punctuation too (\u3000-\u3020\u2014-\u2027\u4e36\ufe4f-\uffe5)
102 | return {
103 | /// Divide the string at non-Chinese parts.
104 | cn: str.split(/[^\u4e00-\u4e35\u4e37-\u9fff\u3400-\u4dff]+/),
105 | /// Get an array of the non-Chinese parts to fill in later.
106 | ///NOTE: If there are no matches, return an empty array so that it has a valid length.
107 | non: str.match(/[^\u4e00-\u4e35\u4e37-\u9fff\u3400-\u4dff]+/g) || []
108 | };
109 | }
110 |
111 | function calculate_prob(str, prob, starting, len)
112 | {
113 | var i,
114 | /// Assuming at least a two letter word.
115 | sample_count = 2,
116 | tmp_prob,
117 | total_prob = 0,
118 | word_prob = {
119 | inner_valid: true,
120 | outter_valid: true,
121 | middle_valid: true
122 | };
123 |
124 | /// Get the left probability.
125 | tmp_prob = prob[starting].L;
126 | if (isNaN(tmp_prob)) {
127 | word_prob.inner_valid = false;
128 | }
129 | total_prob += tmp_prob || 0;
130 |
131 | /// Get the right probability.
132 | tmp_prob = prob[starting + len - 1].R;
133 | if (isNaN(tmp_prob)) {
134 | word_prob.inner_valid = false;
135 | }
136 | total_prob += tmp_prob || 0;
137 |
138 | word_prob.inner = total_prob / sample_count;
139 |
140 | /// Get the outer probability (if any).
141 | if (starting > 0) {
142 | /// The previous character's right probability.
143 | tmp_prob = prob[starting - 1].R;
144 | if (isNaN(tmp_prob)) {
145 | word_prob.outter_valid = false;
146 | }
147 | total_prob += tmp_prob || 0;
148 | sample_count += 1;
149 | }
150 | if (len + starting < str.length - 1) {
151 | /// The next character's left probability.
152 | tmp_prob = prob[starting + len].L;
153 | if (isNaN(tmp_prob)) {
154 | word_prob.outter_valid = false;
155 | }
156 | total_prob += tmp_prob || 0;
157 | sample_count += 1;
158 | }
159 |
160 | word_prob.outter = total_prob / sample_count;
161 |
162 | word_prob.ave_prob = (word_prob.inner + word_prob.outter) / 2;
163 |
164 | /// Check for middle validity.
165 | for (i = starting + 1; i < len + starting - 1; i += 1) {
166 | if (isNaN(prob[i].M)) {
167 | word_prob.middle_valid = false;
168 | break;
169 | }
170 | }
171 |
172 | word_prob.invalid_count = 0;
173 | if (!word_prob.inner_valid) {
174 | word_prob.invalid_count += 1;
175 | }
176 | if (!word_prob.outter_valid) {
177 | word_prob.invalid_count += 1;
178 | }
179 | if (!word_prob.middle_valid) {
180 | word_prob.invalid_count += 1;
181 | }
182 |
183 | return word_prob;
184 | }
185 |
186 | function extract_word(str, prob, starting, len)
187 | {
188 | var dict_pos,
189 | word = {str: str.substr(starting, len)};
190 |
191 | if ((dict_pos = dict.indexOf("\n" + word.str + "\n")) > -1) {
192 | word.in_dict = true;
193 | word.dict_pos = dict_pos;
194 | /// Check to see if the last character is very commonly found at the end (like 的 and 了).
195 | ///TODO: Measure words after a number.
196 | } else if (word.str.length > 2 && /[了子的儿着吗者呢啊得个过地兒著嗎個過]/.test(word.str.slice(-1)) && (dict_pos = dict.indexOf("\n" + word.str.slice(0, -1) + "\n")) > -1) {
197 | word.in_dict = true;
198 | word.dict_pos = dict_pos;
199 | /// Indicate that one character is not actually in the dictionary.
200 | word.not_in_dict = 1;
201 | /// Check for two character endings, like 的人 and 了吗.
202 | } else if (word.str.length > 3 && /(?:的人|了(?:吗|嗎))/.test(word.str.slice(-2)) && (dict_pos = dict.indexOf("\n" + word.str.slice(0, -2) + "\n")) > -1) {
203 | word.in_dict = true;
204 | word.dict_pos = dict_pos;
205 | /// Indicate that two characters are not actually in the dictionary.
206 | word.not_in_dict = 2;
207 | /// Check for short separable words: 吃了饭
208 | } else if (word.str.length === 3 && /[了着过著過]/.test(word.str.substr(1, 1)) && (dict_pos = dict.indexOf("\n" + word.str.substr(0, 1) + word.str.slice(-1) + "\n")) > -1) {
209 | word.in_dict = true;
210 | word.dict_pos = dict_pos;
211 | /// Indicate that one character is not actually in the dictionary.
212 | word.not_in_dict = 1;
213 | word.separable = true;
214 | }
215 | /// Misc matching that could be added:
216 | /// 在……
217 | ///NOTE: Also match duplication, like 刚刚、高高兴兴.
218 |
219 | word.prob = calculate_prob(str, prob, starting, len);
220 |
221 | return word;
222 | }
223 |
224 | function examine_str(str, prob, starting)
225 | {
226 | var dictionary_max_len = 5,
227 | i,
228 | len = str.length - starting,
229 | word,
230 | likely_words = [],
231 | possible_words = [];
232 |
233 | if (len > dictionary_max_len) {
234 | len = dictionary_max_len;
235 | }
236 |
237 | for (i = 2; i <= len; i += 1) {
238 | word = extract_word(str, prob, starting, i);
239 | ///TODO: There should be a way to override this.
240 | if (word.in_dict || word.prob.invalid_count === 0 || (word.prob.invalid_count === 1 && !word.prob.middle_valid)) {
241 | possible_words[possible_words.length] = word;
242 | }
243 |
244 | ///TODO: Make a way to override this.
245 | if (!word.in_dict && (word.prob.invalid_count > 1 || (word.prob.invalid_count === 1 && !word.prob.middle_valid))) {
246 | break;
247 | }
248 | }
249 |
250 | possible_words.forEach(function (word)
251 | {
252 | /// The acceptable probability should probably be changeable.
253 | if (word.in_dict || word.prob.ave_prob > 60) {
254 | likely_words[likely_words.length] = word;
255 | }
256 | });
257 |
258 | return likely_words;
259 | }
260 |
261 | function find_known_and_likely_words(str, prob)
262 | {
263 | var branch,
264 | branches = [],
265 | i,
266 | len = str.length;
267 |
268 | /// Subtract one because we want to examine at least 2 characters.
269 | for (i = 0; i < len - 1; i += 1) {
270 | branch = examine_str(str, prob, i);
271 | branches[i] = branch;
272 | }
273 |
274 | return branches;
275 | }
276 |
277 | function create_char_arr(branches)
278 | {
279 | var char_arr = [];
280 |
281 | branches.forEach(function (branch, x)
282 | {
283 | branch.forEach(function (word_obj)
284 | {
285 | var i;
286 |
287 | for (i = word_obj.str.length - 1; i >= 0; i -= 1) {
288 | if (!char_arr[x + i]) {
289 | char_arr[x + i] = [];
290 | }
291 | char_arr[x + i].push({
292 | x: x + i,
293 | origin_x: x,
294 | obj: word_obj,
295 | is_beginning: i === 0,
296 | is_end: i === word_obj.str.length - 1,
297 | total_len: word_obj.str.length,
298 | len_left: word_obj.str.length - 1 - i,
299 | });
300 | }
301 | });
302 | });
303 |
304 | return char_arr;
305 | }
306 |
307 | function mark_resolved_branches(char_arr)
308 | {
309 | var currently_disputed,
310 | disputed_count = -1,
311 | resolved_branch = [];
312 |
313 | function at_ending(arr)
314 | {
315 | var i;
316 |
317 | for (i = arr.length - 1; i >= 0; i -= 1) {
318 | if (!arr[i].is_end) {
319 | return false;
320 | }
321 | }
322 |
323 | return true;
324 | }
325 |
326 | function tag_disputed(arr, disputed_count)
327 | {
328 | arr.forEach(function (el)
329 | {
330 | el.obj.disputed_group = disputed_count;
331 | });
332 | }
333 |
334 | char_arr.forEach(function (arr)
335 | {
336 | /// If there is more than one word, it is disputed.
337 | if (arr.length > 1) {
338 | /// If it's not already known to be disputed, inc group to keep track of the groups.
339 | if (!currently_disputed) {
340 | disputed_count += 1;
341 | }
342 | currently_disputed = true;
343 | /// If it is not disputed and we are at the end, it's resolved!
344 | } else if (!currently_disputed && arr[0].is_end) {
345 | resolved_branch[arr[0].origin_x] = arr[0].obj;
346 | arr[0].obj.resolved = true;
347 | }
348 |
349 | /// Tag them all each time just to make sure no one is missed.
350 | if (currently_disputed) {
351 | tag_disputed(arr, disputed_count);
352 | }
353 |
354 | /// If they are all ending, it's the end of a group.
355 | if (currently_disputed && at_ending(arr)) {
356 | currently_disputed = false;
357 | }
358 | });
359 |
360 | return disputed_count;
361 | }
362 |
363 | function walk_branches(branch_holder, branches, disputed_group, branch_num_obj, x, mark_callback)
364 | {
365 | var at_end_of_branch = true,
366 | i,
367 | stop_before = branches.length;
368 |
369 | function create_mark_func(obj, callback)
370 | {
371 | return function (branch_num)
372 | {
373 | /// Create new branch object to track branches.
374 | if (!obj.branch) {
375 | obj.branch = {};
376 | }
377 |
378 | obj.branch[branch_num] = 1;
379 |
380 | /// Add this branch if it's not already known.
381 | if (!branch_holder[branch_num]) {
382 | branch_holder[branch_num] = [];
383 | }
384 | /// Add this word to the branch tracker.
385 | branch_holder[branch_num].push(obj);
386 |
387 | if (callback) {
388 | /// Loop back through the whole tree.
389 | callback(branch_num);
390 | }
391 | };
392 | }
393 |
394 | for (; x < stop_before; x += 1) {
395 | /// Are there any words in this column?
396 | if (branches[x] && branches[x].length > 0) {
397 | /// Just look for the specific group.
398 | if (branches[x][0].resolved || branches[x][0].disputed_group < disputed_group) {
399 | continue;
400 | }
401 | if (branches[x][0].disputed_group > disputed_group) {
402 | break;
403 | }
404 |
405 | /// Could make it reverse later; just doing it more straightforwardly at first to make it easier to understand.
406 | for (i = 0; i < branches[x].length; i += 1) {
407 | /// stop_before needs to keep moving in based on the shortest distance from start to finish.
408 | if (x + branches[x][i].str.length < stop_before) {
409 | stop_before = x + branches[x][i].str.length;
410 | }
411 |
412 | /// Walk to the next step.
413 | walk_branches(branch_holder, branches, disputed_group, branch_num_obj, x + branches[x][i].str.length, create_mark_func(branches[x][i], mark_callback));
414 |
415 | /// If it found another route, it's not at a branches end, so it does not need to marked the branches at the end.
416 | at_end_of_branch = false;
417 | }
418 | }
419 | }
420 |
421 | /// Trigger the mark cascade, if any.
422 | if (at_end_of_branch && mark_callback) {
423 | mark_callback(branch_num_obj.num);
424 | branch_num_obj.num += 1;
425 | }
426 | }
427 |
428 | function is_more_likely(sig1, sig2, prob1, prob2)
429 | {
430 | var i,
431 | index,
432 | sig_t1,
433 | sig_t2,
434 | calc_sig;
435 |
436 | function sum(a, b)
437 | {
438 | return a + b;
439 | }
440 |
441 | /// Remove words that are the same so that it doesn't overshadow the average of the significance.
442 | for (i = sig1.length; i >= 0; i -= 1) {
443 | index = sig2.indexOf(sig1[i]);
444 | if (index > -1) {
445 | sig1.splice(i, 1);
446 | sig2.splice(index, 1);
447 | }
448 | }
449 |
450 | if (sig1.length > 0) {
451 | sig_t1 = sig1.map(get_significance).reduce(sum);
452 | } else {
453 | sig_t1 = 0;
454 | }
455 | if (sig2.length > 0) {
456 | sig_t2 = sig2.map(get_significance).reduce(sum);
457 | calc_sig = sig_t1 / sig_t2;
458 | } else {
459 | sig_t2 = 0;
460 | calc_sig = 0;
461 | }
462 |
463 | if (prob2 > 0) {
464 | ///NOTE: Weigh significance a little more since it seems more reliable.
465 | calc_sig = (calc_sig * 2 + (prob1 / prob2)) / 3;
466 | }
467 |
468 | return calc_sig > 1;
469 | }
470 |
471 | function keep_best_branch(branches_lined_up, branches, disputed_group)
472 | {
473 | var best_branch = 0,
474 | best_words_in_dict = 0,
475 | best_prob = 0,
476 | best_sig = [],
477 | i,
478 | j,
479 | clear_branch;
480 |
481 | branches_lined_up.forEach(function (lined_up_branch, branch_num)
482 | {
483 | var this_words_in_dict = 0,
484 | this_prob_tmp = 0,
485 | this_prob,
486 | this_sig = [];
487 |
488 | lined_up_branch.forEach(function(word)
489 | {
490 | if (word.in_dict) {
491 | ///NOTE: If some parts of the word are not actually in the dictionary, weight them a little less.
492 | this_words_in_dict += word.str.length - (word.not_in_dict ? word.not_in_dict / 1.5 : 0);
493 | this_sig[this_sig.length] = word.dict_pos;
494 | }
495 | this_prob_tmp += word.prob.ave_prob;
496 | });
497 |
498 | this_prob = this_prob_tmp / lined_up_branch.length;
499 |
500 | ///NOTE: Since the arrays are mutated, just send a copy.
501 | if (branch_num === 0 || this_words_in_dict > best_words_in_dict || (this_words_in_dict - 1 >= best_words_in_dict && is_more_likely(copy(this_sig), copy(best_sig), this_prob, best_prob))) {
502 | best_branch = branch_num;
503 | best_words_in_dict = this_words_in_dict;
504 | best_prob = this_prob;
505 | best_sig = this_sig;
506 | }
507 | });
508 |
509 | /// Now, remove the others.
510 | for (i = 0; i < branches.length; i += 1) {
511 | for (j = branches[i].length - 1; j >= 0; j -= 1) {
512 | clear_branch = false;
513 | /// If it reached a later branch, we are done.
514 | if (branches[i][j].disputed_group > disputed_group) {
515 | return;
516 | }
517 | /// Skip if it is not the right branch.
518 | ///NOTE: If it finds a resolved branch after already finding the disputed branch, it could return there too.
519 | if (branches[i][j].resolved || branches[i][j].disputed_group < disputed_group) {
520 | break;
521 | }
522 |
523 | if (branches[i][j].branch[best_branch]) {
524 | branches[i] = [branches[i][j]];
525 | /// Break since there's only one per column.
526 | break;
527 | } else {
528 | clear_branch = true;
529 | }
530 | }
531 | if (clear_branch) {
532 | ///NOTE: Deleting just sets the element to undefined, so it's fastest just to replace it all.
533 | branches[i] = [];
534 | }
535 | }
536 | }
537 |
538 | function resolve_multiple_branches(branches)
539 | {
540 | /// First, add known good branches (single branches) to a resolved variable
541 | /// while adding unresolved branches to an array of unresolved branches.
542 | /// Then, recursively loop through all of the branches in the array and total up
543 | /// how many known characters were found and the overall probability.
544 | var branches_lined_up = [],
545 | disputed_count,
546 | i;
547 |
548 | disputed_count = mark_resolved_branches(create_char_arr(branches));
549 |
550 | for (i = 0; i <= disputed_count; i += 1) {
551 | branches_lined_up[i] = [];
552 | walk_branches(branches_lined_up[i], branches, i, {num: 0}, 0);
553 | }
554 | for (i = 0; i <= disputed_count; i += 1) {
555 | keep_best_branch(branches_lined_up[i], branches, i);
556 | }
557 | }
558 |
559 | function format_nicely(branches, str)
560 | {
561 | var res = [],
562 | x = 0;
563 |
564 | for (;;) {
565 | if (x >= str.length) {
566 | break;
567 | }
568 |
569 | /// If there are no words, use a single character.
570 | if (!branches[x] || branches[x].length === 0) {
571 | res[res.length] = str.substr(x, 1);
572 | x += 1;
573 | } else {
574 | /// Use a word if present.
575 | res[res.length] = branches[x][0].str;
576 | x += branches[x][0].str.length;
577 | }
578 | }
579 |
580 | return res;
581 | }
582 |
583 | /**
584 | * Segment a string of Chinese text.
585 | *
586 | * @param lang_analysis (object) The analyzed language data
587 | * @param lang_dict (string) A dictionary list of words
588 | * @param lang_p (object) The dictionary plot data
589 | * @param orig_str (string) The string to segment
590 | * @param return_raw (boolean) Whether or not to return the raw array of segmented strings (default) or a string with a space separating each segmentation
591 | * @note When compling the CKJV in the Forge, it needs the raw data.
592 | */
593 | function segment(lang_analysis, lang_dict, lang_p, orig_str, return_raw)
594 | {
595 | var chunks = isolate_chinese_characters(orig_str),
596 | res = [],
597 | text;
598 |
599 | /// Set the closure variables for this language.
600 | analysis = lang_analysis;
601 | dict = lang_dict;
602 | p = lang_p;
603 |
604 | chunks.cn.forEach(function (str, i)
605 | {
606 | var branches;
607 |
608 | if (str !== "") {
609 | /// Step 1
610 | branches = find_known_and_likely_words(str, get_probabilities(str));
611 |
612 | /// Step 2
613 | resolve_multiple_branches(branches);
614 |
615 | /// Step 3
616 | res[res.length] = format_nicely(branches, str);
617 | }
618 |
619 | /// Fill in with any non-Chinese parts.
620 | if (chunks.non.length > i) {
621 | res[res.length] = chunks.non[i];
622 | }
623 | });
624 |
625 | /// Should it convert the data into a string separated by spaces between segments?
626 | if (!return_raw) {
627 | text = "";
628 | res.forEach(function (sec, i)
629 | {
630 | if (typeof sec === "string") {
631 | /// Adding a space, even before non-Chinese parts, helps to keep segments separate.
632 | /// But don't add a space before a space.
633 | if (i > 0 && sec !== " ") {
634 | text += " ";
635 | }
636 | text += sec;
637 | } else {
638 | sec.forEach(function (word, i)
639 | {
640 | /// Add a space to separate words.
641 | if (i > 0) {
642 | text += " ";
643 | }
644 | text += word;
645 | });
646 | }
647 | });
648 | return text;
649 | }
650 |
651 | return res;
652 | };
653 |
654 | /// Is this Node.js?
655 | if (typeof exports !== "undefined") {
656 | exports.segment = segment;
657 | } else {
658 | /// If this is a browser, return a function that allows segment() to be attached to an object.
659 | return function init(context)
660 | {
661 | context.segment = segment;
662 | };
663 | }
664 | }());
665 |
--------------------------------------------------------------------------------
/client/styles/base.css:
--------------------------------------------------------------------------------
1 | /**
2 | * BibleForge
3 | *
4 | * @date 10-30-08
5 | * @version alpha (α)
6 | * @link http://BibleForge.com
7 | * @license The MIT License (MIT)
8 | */
9 |
10 | /*!
11 | * The BibleForge motto:
12 | *
13 | * "all things whatsoever ye would that men should do to you, do ye even so to them."
14 | * —Jesus (Matthew 7:12)
15 | */
16 |
17 | /*!
18 | * Copyright (C) 2022
19 | *
20 | * Permission is hereby granted, free of charge, to any person obtaining
21 | * a copy of this software and associated documentation files (the
22 | * “Software”), to deal in the Software without restriction, including
23 | * without limitation the rights to use, copy, modify, merge, publish,
24 | * distribute, sublicense, and/or sell copies of the Software, and to
25 | * permit persons to whom the Software is furnished to do so.
26 | *
27 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | */
35 |
36 | /*TODO: Completely document the CSS. */
37 | /*TODO: Re-order (as well as maybe group) the styles alphabetically or in a logical order. */
38 |
39 | /* Normalize Mozilla button styles. */
40 | /* Also, remove the ugly dashed border on the text of buttons in Mozilla when clicked. */
41 | button::-moz-focus-inner, input::-moz-focus-inner {
42 | border: 0;
43 | padding: 0;
44 | }
45 |
46 | input, textarea {
47 | /* This removes the awful, non-rounded selection rectangle that WebKit imposes. */
48 | outline: 0;
49 | }
50 |
51 |
52 | html, body {
53 | margin: 0;
54 | padding: 0;
55 | border: 0;
56 | width: 100%;
57 | /* System/user settings are not be the same for every user, so the background and text color must be explicitly set. */
58 | background: #FFF;
59 | color: #000;
60 | }
61 |
62 | html {
63 | overflow-y: scroll;
64 | overflow-x: hidden;
65 | font-family: Verdana, 'Bitstream Vera Sans', 'Bitstream Vera Sans Webfont', 'DejaVu Sans', sans-serif;
66 | font-size: 15px;
67 |
68 | /* Improve the text rendering and enable support for ligatures and kerning in Mozilla. */
69 | /*NOTE: This may have a slight speed cost. */
70 | /*NOTE: This does not effect Mozilla on Macs. */
71 | text-rendering: optimizeLegibility;
72 | }
73 |
74 | input, .callout, .lang {
75 | /*NOTE: Input fields do not inherit from the HTML element. */
76 | /* If a font-family is not specified, it will use a system default. */
77 | /* On Windows, that font is "MS Shell Dlg," which is actually "Microsoft Sans Serif." */
78 | /*TODO: Make/find a good replacement font for Arial. */
79 | font-family: Arial, 'DejaVu Sans', sans-serif;
80 | }
81 |
82 | body {
83 | overflow: hidden;
84 | }
85 |
86 |
87 | .topBars {
88 | position: fixed;
89 | top: 0;
90 | left: 0;
91 | width: 100%;
92 | height: 30px;
93 | /* This needs to be above most floating elements (excluding the context menu). */
94 | z-index: 999999;
95 | }
96 |
97 | .searchBars {
98 | /* A solid background for IE/Mozilla 2-/Opera 9.6- */
99 | background: #FFF;
100 | /* Transparent white background for Firefox 3.5-/Opera 10 */
101 | background: rgba(255,255,255,.9);
102 | /* Chrome/Safari 5.1+ */
103 | background: -webkit-linear-gradient(rgba(255,255,255,.98), rgba(255,255,255,.93) 25%, rgba(255,255,255,.87));
104 | /* IE 10, Firefox 16+, Opera 12.10+ */
105 | background: linear-gradient(rgba(255,255,255,.98), rgba(255,255,255,.93) 25%, rgba(255,255,255,.87));
106 | width: 100%;
107 | height: 100%;
108 | text-align: center;
109 | }
110 |
111 | .infoBars {
112 | /* A solid background for IE/Mozilla (Firefox 2-)/Opera 9.6- */
113 | background: #FFD;
114 | /* Transparent yellow background */
115 | background: rgba(255,255,221,.87);
116 | border-top: #EEE999 solid 1px;
117 | border-bottom: #EEE999 solid 1px;
118 |
119 | padding-left: 3px;
120 |
121 | width: 100%;
122 | height: 18px;
123 |
124 | /* Prevent a long search string from overflowing the info bar by cutting it short and adding an ellipsis. (Mozilla has no support for this yet.) */
125 | /*FIXME: This does not work yet. */
126 | text-overflow: ellipsis;
127 | /* Opera specific */
128 | -o-text-overflow: ellipsis;
129 | /* This is what actually hides the text, currently. */
130 | overflow: hidden;
131 |
132 | /* Prevent excess whitespace from collapsing when displaying user generated search terms. */
133 | white-space: pre-wrap;
134 |
135 | font-size: 13px;
136 |
137 | box-shadow: 0 3px 6px rgba(0,0,0,.08);
138 | }
139 |
140 | .infoLeft {
141 | /* Make the
not take up the entire width of the infoBar. */
142 | float: left;
143 |
144 | /* Add a slight radial blur to make sure the text is always readable.*/
145 | /* Chrome/Safari 5.1+ */
146 | background: -webkit-radial-gradient(ellipse farthest-corner, #FFFFE2 70%, rgba(255,255,226,0) 100%);
147 | /* IE 10, Firefox 16+, Opera 12.10+ */
148 | background: radial-gradient(ellipse farthest-corner, #FFFFE2 70%, rgba(255,255,226,0) 100%);
149 | }
150 |
151 | .queryInput {
152 | background: rgba(255,255,255,.5);
153 |
154 | /* Padding is used to push the text away from the rounded corners. */
155 | padding: 3px 25px 1px 7px;
156 |
157 | font-size: 15px;
158 |
159 | max-width: 480px;
160 | min-width: 30px;
161 | /*NOTE: 100% width cannot be used because it extends off the page */
162 | /* 70% is used because it is a good balance when the page is wide or narrow. */
163 | width: 70%;
164 | height: 18px;
165 |
166 | /* Dim the text when not in focus (or hovering) so that it does not distract from the text. */
167 | color: #A7A7A7;
168 |
169 | border-style: solid;
170 | border-width: 1px;
171 | /* This gives the input box a slight 3D effect. */
172 | border-color: #BBB #CACACA #C3C3C3 #A1A19B;
173 | border-radius: 15px;
174 |
175 | /* Add slight gradient shadow to the top of the input box. */
176 | box-shadow: inset 0 2px 3px rgba(0,0,0,.16);
177 |
178 | /* The margin balances moves the input box over a little so as not to cover the wrench icon. */
179 | margin: 2px 30px;
180 |
181 | /* Apply transitions to all changes (in particular, background-color, border-color, and color). */
182 | /* Transition for Mozilla (Firefox 4.0+) */
183 | -moz-transition: all .3s;
184 | /* Transition for WebKit */
185 | -webkit-transition: all .3s;
186 | /* Transition for Opera 10.5+ */
187 | transition: all .3s;
188 | }
189 |
190 | /**
191 | * Highlight the input box partially on hover.
192 | */
193 | .queryInput:hover {
194 | border-color: #96968E;
195 | }
196 |
197 | /**
198 | * Highlight the input box.
199 | */
200 | .queryInput:focus {
201 | border-color: #96968E;
202 |
203 | /* Darken the text so that it is more prominent. */
204 | color: #555;
205 | background-color: #FFF;
206 | }
207 |
208 | /**
209 | * Keep the placeholder text grayed out.
210 | */
211 | /* NOTE: WebKit does not need this because the placeholder text does not change colors on focus. */
212 | /* However, in Night Mode, WebKit's font color is too bright, so it is lightened in night.css.
213 | /* NOTE: WebKit's placeholder property is "::-webkit-input-placeholder" (note the double colons as well as different text). */
214 | /* NOTE: IE 10 might use ":-ms-input-placeholder". */
215 | /* NOTE: Different browsers use different styles by default to style the placeholder text. Some set the opacity; some set a semi-opaque color. */
216 | .queryInput:-moz-placeholder {
217 | color: #A7A7A7;
218 | }
219 |
220 |
221 | .queryPadding {
222 | padding: 8px;
223 | }
224 |
225 | .lang {
226 | background: rgba(0,0,200,.05);
227 | color: rgba(0,0,0,.2);
228 | font-size: 13px;
229 |
230 | cursor: pointer;
231 |
232 | /* Round just the left side to match queryInput. */
233 | border-radius: 15px 0 0 15px;
234 |
235 | /* Just have a single border on the right to separate it from queryInput. */
236 | border: none transparent;
237 | border-right: 1px solid rgba(0,0,0,.1);
238 |
239 | /* Align the element inside of queryInput. */
240 | position: absolute;
241 | margin: 0 0 0 31px;
242 | top: 3px;
243 | height: 22px;
244 |
245 | /* The extra right padding is for the drop down arrow (see .lang:after). */
246 | padding-right: 14px;
247 | /* Center the text vertically. */
248 | padding-top: 2px;
249 |
250 | font-size: 13px;
251 |
252 | /* Hide by default so that the default language name can be added first. */
253 | visibility: hidden;
254 |
255 | /* Apply transitions to all changes. */
256 | /* Transition for Mozilla (Firefox 4.0+) */
257 | -moz-transition: all .3s;
258 | /* Transition for WebKit */
259 | -webkit-transition: all .3s;
260 | /* Transition for Opera 10.5+ */
261 | transition: all .3s;
262 | }
263 |
264 | .lang:hover, .more-button:hover {
265 | color: rgba(0,0,0,.6);
266 | }
267 |
268 | /*BUG Opera does not properly render the arrow. It flickers when the mouse hovers over it. */
269 | .lang:after {
270 | opacity: .15;
271 |
272 | width: 11px;
273 | height: 6px;
274 |
275 | /* This is necessary (for some reason) to make the element visible. */
276 | content: "";
277 |
278 | position: absolute;
279 | top: 45%;
280 | right: 1px;
281 |
282 | -moz-transition: opacity .3s;
283 | /*BUG This does not work at least in Chrome 22. */
284 | -webkit-transition: opacity .3s;
285 | transition: opacity .3s;
286 | }
287 |
288 | .lang:hover:after, .lang:active:after, .activeLang:after {
289 | opacity: .4;
290 | }
291 |
292 | .lang:active, .activeLang, .more-button:active {
293 | background: rgba(0,0,255,.12);
294 | color: rgba(0,0,0,.6);
295 | }
296 |
297 | .more-button:hover {
298 | background: rgba(0,0,200,.08);
299 | }
300 |
301 |
302 | .crown_loader {
303 | width: 20px;
304 | height: 20px;
305 | /* The Crown of Thorns icon */
306 | background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAQAAAAngNWGAAABcUlEQVQoFQXB72uMARwA8M+ee1ArOsW80FxKNsSQ215IGGuje2E6L5Q/QuydvPFqr3aSvJkiSl6cInWXVqg1P+buhXdWU+KNZlzHSZlnX58PAABscx8AAACA1BsrwoLNAEACOKPHuDENbUV95nwypqwPAKAu80tLuG0Nrsp0fBNemABgqxWXUbFgRggNdTWUhL0AjPuNjcIJPNGNvDCAuisAjPqhIjxHai047YFwR9NFgNR74YMRZwEAO7wSlvWQYtZX7xTMSAFAl0VN68xr6GdSZr+ckAAAeOinYYSD7LJqJ0INAECXJYMIj3NazivYZ70R3f4CAA44qVfREGzSkanZAAA4LjEthEESLCv7I7Rdd0oOJDgis8dHl8wDnLOEAaEf3NCLcAGPXANgu1Ul3PXaTV+Ee6bNYbfMMABl34VnmkLTMUOeCouq/mkDKaiqOmRUR8FbLzGr5Ja8KYcBAIAtPguhpQgAAACQU3FUHgDgP8wYe6XaJYMMAAAAAElFTkSuQmCC);
307 | opacity: .2;
308 |
309 | /* Prepare to make the graphic spin. */
310 | /*NOTE: Mozilla has some issues that are fixed by the Mozilla specific code at the bottom. */
311 | -webkit-animation-name: rotate;
312 | -moz-animation-name: rotate;
313 | animation-name: rotate;
314 | -webkit-animation-duration: 2s;
315 | -moz-animation-duration: 2s;
316 | animation-duration: 2s;
317 | -webkit-animation-iteration-count: infinite;
318 | -moz-animation-iteration-count: infinite;
319 | animation-iteration-count: infinite;
320 | -webkit-animation-timing-function: linear;
321 | -moz-animation-timing-function: linear;
322 | animation-timing-function: linear;
323 | }
324 |
325 | /* Make the Crown of Thorns spin. */
326 | @-webkit-keyframes rotate {
327 | from {-webkit-transform: rotate(0deg);}
328 | to {-webkit-transform: rotate(360deg);}
329 | }
330 | @-moz-keyframes rotate {
331 | from {-moz-transform: rotate(0deg);}
332 | to {-moz-transform: rotate(360deg);}
333 | }
334 | @keyframes rotate {
335 | from {transform: rotate(0deg);}
336 | to {transform: rotate(360deg);}
337 | }
338 |
339 |
340 | .inputIcon {
341 | /* Move the icon over the right end of the input box. */
342 | margin-left: -55px;
343 | /* Aligns the image in a somewhat cross-browser way. */
344 | vertical-align: bottom;
345 |
346 | /* Remove the hideous selection rectangle when clicked. */
347 | outline: 0;
348 |
349 | /* Fade out slightly when the mouse cursor moves off. */
350 | /* Transition for Mozilla (Firefox 4.0+) */
351 | -moz-transition: opacity .3s ease;
352 | /* Transition for WebKit */
353 | -webkit-transition: opacity .3s ease;
354 | /* Transition for Opera 10.5+ */
355 | transition: opacity .3s ease;
356 | opacity: .3;
357 |
358 | position: absolute;
359 |
360 | /* Buttons do not automatically get a hand cursor. */
361 | cursor: pointer;
362 |
363 | width: 25px;
364 | height: 26px;
365 | border: 0;
366 | background: no-repeat left 60% url(data:image/gif;base64,R0lGODlhEAAQAPIHAAIFARweHDU3NFlbWHJ0cZ2fnNDTz/3//CH5BAAKAAAALAAAAAAQABAAAANDeLpnAyAQRgsYxhQBDD2WxxDABxTfIUxMmRYBY7jfTNGU3YrUMPQ4xYwnhPAsKNmJBIEkF05ZqkmcGlOMwBW74FSxCQA7);
367 | }
368 |
369 | /**
370 | * Fade in icon on hover.
371 | */
372 | .inputIcon:hover, .wrenchIcon:hover, .wrenchPadding:hover .wrenchIcon, .activeWrenchIcon {
373 | /* !important is used to enforce the opacity for .activeWrenchIcon when the context menu is open. */
374 | opacity: .6!important;
375 | }
376 |
377 | /**
378 | * Emulate button pressing.
379 | *
380 | * @note This does not work in IE.
381 | */
382 | .inputIcon:active {
383 | /* This is a simple way of moving the icon over slightly when clicked. */
384 | border-left: 1px solid transparent;
385 | /* Keep the icon highlighted even in the mouse moves off. */
386 | opacity: .6;
387 | }
388 |
389 |
390 | .wrenchPadding {
391 | position: absolute;
392 | /* Add padding around wrench with extra padding below and on the right. */
393 | padding: 1px 6px 8px 1px;
394 |
395 | /* Use the hand cursor for the padded area, just like the button. */
396 | cursor: pointer;
397 |
398 | /*TODO: Use fade class. */
399 | /* Transition for Mozilla (Firefox 4.0+) */
400 | -moz-transition: opacity 1s ease-in;
401 | /* Transition for WebKit */
402 | -webkit-transition: opacity 1s ease-in;
403 | /* Transition for Opera 10.5+ */
404 | transition: opacity 1s ease-in;
405 | }
406 |
407 |
408 | .wrenchIcon {
409 | /* Darken the text so that it is more prominent. */
410 | /* Transition for Mozilla (Firefox 4.0+) */
411 | -moz-transition: opacity .6s ease;
412 | /* Transition for WebKit */
413 | -webkit-transition: opacity .6s ease;
414 | /* Transition for Opera 10.5+ */
415 | transition: opacity .6s ease;
416 |
417 | /* Fade out slightly when the mouse cursor moves off. */
418 | opacity: .2;
419 |
420 | padding: 4px;
421 |
422 | /* Remove the hideous selection rectangle when clicked. */
423 | outline: 0;
424 |
425 | /* Buttons do not automatically get a hand cursor. */
426 | cursor: pointer;
427 |
428 | border: 0;
429 | width: 24px;
430 | height: 24px;
431 | background: center no-repeat url(data:image/gif;base64,R0lGODdhEAAQAMIIAAEDADAyL05OSWlpYYyLg7GwqNjVyP/97iwAAAAAEAAQAAADQ3i6OwBhsGnCe2Qy+4LRS3EBn5JNxCgchgBo6ThwFDc+61LdY6m4vEeBAbwMBBHfoYgBLW8njUPmPNwk1SkAW31yqwkAOw==);
432 | }
433 |
434 |
435 | .contextMenu {
436 | opacity: 0;
437 | background: #FFF;
438 | background: rgba(255,255,255,.95);
439 | border: #A3A3A3 1px solid;
440 | padding: 3px;
441 | /* This element must be on top (because it is created later, it is also above the topBar too). */
442 | z-index: 999999;
443 |
444 | /* Transition for Mozilla (Firefox 4.0+) */
445 | -moz-transition: opacity .2s ease-in;
446 | /* Transition for WebKit */
447 | -webkit-transition: opacity .2s ease-in;
448 | /* Transition for Opera 10.5+ */
449 | transition: opacity .2s ease-in;
450 |
451 | /* Rounded corners for Opera 10.5+, IE 9+, WebKit 532.5+ (Chrome 4, Safari 5), Gecko 2 (Mozilla 4.0) */
452 | border-radius: 3px;
453 |
454 | /* This shadow is slightly dimmer than the panel because it is supposed to look higher up. */
455 | /*TODO: The direction of the shadow could be set dynamically based on where it is on the page. */
456 | box-shadow: 2px 2px 3px rgba(0,0,0,.2);
457 | }
458 |
459 | .contextMenu div {
460 | /* Ensure that the cursor will look like a hand even if not hovering over the text. */
461 | /*NOTE: Since Chromium fires the onmousemove event when the mouse cursor changes, we must set the cursor for the entire
, not just on hover */
462 | /* because if the user uses the keyboard arrows while the mouse is over an element, the onmousemove event will keep firing. */
463 | cursor: pointer;
464 | }
465 |
466 | /* Menu items */
467 | .contextMenu div a {
468 | color: #555;
469 | /* Block table-row-group is needed to make the possible table cells to display correctly (e.g., the pronunciation selection menu). */
470 | /* Any display type that is block (as opposed to inline) is needed to make the width become 100%. */
471 | /* NOTE: table-row could also works except for the fact that in Chrome, the element is treated as inline (cannot click on the space to the right) even though the background extends all the way. */
472 | display: table-row-group;
473 | text-decoration: none;
474 | /* Prevent the text from wrapping. */
475 | white-space: nowrap;
476 | width: 100%;
477 |
478 | /* Make the background and font color fade when the mouse hovers/leaves. */
479 | -moz-transition: background .2s, color .2s;
480 | -webkit-transition: background .2s, color .2s;
481 | transition: background .2s, color .2s;
482 | }
483 |
484 | /* The container for a (text) menu item. */
485 | .contextMenu div a div {
486 | padding: 1px 0;
487 | }
488 |
489 | /* CSS table cell */
490 | /* The pronunciation context menu uses this. */
491 | .cell {
492 | display: table-cell;
493 | /* Add some space between the columns. */
494 | padding-right: 8px;
495 | }
496 |
497 | /* The last column. */
498 | .cell:last-of-type {
499 | /* Prevent the last column from displaying space on the right of it. */
500 | padding-right: 0;
501 | }
502 |
503 | .menu_item_selected {
504 | /*TODO: Determine if it is better to use the color that is similar to the yellow before the transparency is applied (#DDF) or use transparency. */
505 | background: #E1E1FF;
506 | color: #3985A8;
507 | }
508 |
509 | .menu_item_line div {
510 | border-top: 1px solid #A3A3A3;
511 | }
512 |
513 |
514 | .panel {
515 | display: none;
516 | position: fixed;
517 | /* A solid background for IE/Mozilla 2-/Opera 9.6- */
518 | background: #FFF;
519 | /* Transparent white background for Mozilla/Opera 10/WebKit */
520 | background: rgba(255,255,255,.97);
521 | /* Chrome/Safari 5.1+ */
522 | background: -webkit-linear-gradient(bottom, rgba(240,240,240,.98), rgba(255,255,255,.98) 60px);
523 | /* IE 10, Firefox 16+, Opera 12.10+ */
524 | background: linear-gradient(to top, rgba(240,240,240,.98), rgba(255,255,255,.98) 60px);
525 | border: #A3A3A3 1px solid;
526 | /* Add enough padding to the top to push the contents past the top bar. */
527 | /* Add extra padding on the sides so that the optional scroll bars will not cover up any of the content. */
528 | padding: 60px 16px 16px 16px;
529 | /* Add scroll bars when the contents extend past the height or width. */
530 | overflow: auto;
531 |
532 | /* Rounded corners for Opera 10.5+, IE 9+, WebKit 532.5+ (Chrome 4, Safari 5), Gecko 2 (Mozilla 4.0) */
533 | border-radius: 9px;
534 |
535 | /* Add both a drop shadow and an inset shadow to give a slightly rounded effect. */
536 | box-shadow: 3px 3px 6px rgba(0,0,0,.2), inset -1px 0 2px rgba(0,0,0,.2);
537 |
538 | /* Just below the topBar (and context menu) but above callouts. */
539 | z-index: 999990;
540 |
541 | /* Make sure that the panel does not get too large. */
542 | max-width: 90%;
543 | }
544 |
545 |
546 | .panel a {
547 | color: #3985A8;
548 |
549 | text-decoration: none;
550 | }
551 |
552 | .panel a:hover {
553 | /*TODO: Do something better. */
554 | text-decoration: underline;
555 | }
556 |
557 |
558 | .slide {
559 | /* Transition for Mozilla (Firefox 4.0+) */
560 | -moz-transition: top .6s ease;
561 | /* Transition for WebKit */
562 | -webkit-transition: top .6s ease;
563 | /* Transition for Opera 10.5+ */
564 | transition: top .6s ease;
565 | }
566 |
567 |
568 | fieldset {
569 | border: 0;
570 | }
571 |
572 | legend {
573 | font-size: 20px;
574 | margin-left: -10px;
575 | }
576 |
577 | /* Make sure that the panel does not get too big. */
578 | /*NOTE: It needs to be set here because the panel must have "max-width: 100%" to prevent the panel as a whole from getting too big.*/
579 | .aboutBible {
580 | max-width: 700px;
581 | }
582 |
583 | /* Align the first button to the left. */
584 | .button1of2 {
585 | float: left;
586 | }
587 |
588 | /* The done button and the second of two should be right aligned. */
589 | .doneButton, .button2of2 {
590 | float: right;
591 | }
592 |
593 | .button {
594 | border: 1px solid #8EC1DA;
595 |
596 | border-radius: 4px;
597 |
598 | background-color: rgba(0,0,200,.05);
599 |
600 | box-shadow: inset 0 1px 3px rgba(255,255,255,.5), inset 0 -15px 3px rgba(0,0,200,.05);
601 |
602 | color: #3985A8;
603 |
604 | text-shadow: 0 1px #FFF;
605 |
606 | padding: 5px 14px;
607 | outline: 0;
608 |
609 | cursor: pointer;
610 |
611 | /* Fade the background color on mouse down. */
612 | /* Transition for Mozilla (Firefox 4.0+) */
613 | -moz-transition: background-color .2s;
614 | /* Transition for WebKit */
615 | -webkit-transition: background-color .2s;
616 | /* Transition for Opera 10.5+ */
617 | transition: background-color .2s;
618 | }
619 |
620 | .button:hover {
621 | background-color: rgba(0,0,200,.07);
622 | }
623 |
624 | /**
625 | * Emulate button pressing.
626 | *
627 | * @note This does not work in IE.
628 | */
629 | .button:active {
630 | /*TODO: Determine if it would be better just to change the text shadow color. (Opera already moves the text.) */
631 | padding: 6px 13px 4px 15px;
632 |
633 | background-color: rgba(0,0,200,.1);
634 | }
635 |
636 | .emailForm input, .emailForm textarea {
637 | display: block;
638 | background: #FFF;
639 | color: #000;
640 | border: 1px solid #CCC;
641 | border-radius: 3px;
642 | margin: 5px;
643 | width: 230px;
644 | max-width: 100%;
645 | font-size: 14px;
646 | padding: 2px;
647 | }
648 |
649 | .emailForm textarea {
650 | /* Override the width to make it larger than the input boxes. */
651 | width: 500px;
652 | height: 120px;
653 | }
654 |
655 | .emailForm div {
656 | /* Line up the email address link with the input boxes. */
657 | padding-left: 4px;
658 | }
659 |
660 |
661 |
662 | .scrolls {
663 | overflow: hidden;
664 | max-width: 800px;
665 | min-width: 200px;
666 | margin-left: auto;
667 | margin-right: auto;
668 | /* Fixes the clipping of italics p's at the beginning of a line (e.g., Acts 5:3 with a large window) or f's at the end of a line (Ex. 36:8). */
669 | padding: 0 4px 0 4px;
670 | }
671 |
672 | /* Display a hand cursor for words that can be looked up. */
673 | .linked a {
674 | cursor: pointer;
675 | }
676 |
677 | .scrolls {
678 | cursor: auto;
679 | }
680 |
681 | /* Hide the cursor when hovering between words on the page (.hidden_cursor), hovering over words on the page (.hidden_cursor a), and hovering over a search verse (.hidden_cursor .search_verse span). */
682 | /* These all must be specified specifically because they may have a hand cursor style that needs to be directly overwitten. */
683 | .hidden_cursor, .hidden_cursor a, .hidden_cursor .search_verse span {
684 | cursor: none!important;
685 | }
686 |
687 |
688 | .loaders {
689 | height: 50px;
690 | width: 100%;
691 | background: no-repeat center url(data:image/gif;base64,R0lGODlhIAAgAPMLAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBP///wAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQECgD/ACwAAAAAIAAgAAAE53DJSelQo+rNZ1JJZRydJgSVolKAIJTUkSQFpSrT4SIwNScvyW2CcBl6k8CMMBkuDDskhTBDLZwuAUkqEfxIQ6gAQBFvFwICITMpVDW6XNE4GagJhSAgwe60smQUBXd4Rz1ZAghnFAKDd0hihh12BEE9kjAHVlycXIg7BwADAaSlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YHvpJivxNaGmLHT0VnOgKYf0dZXS7APdpB309RnHOG5gvqXGLDaC457D1zZ/V/nmOM82XiHQ7YKhKP1oZmADdEAAAh+QQFCgALACwAAAAAGAAXAAAEcnDJSWsSNetJEqnBsIlUYlKEomjEV57SoCZsi0wmLSVqoA2tAg4WmG0WhRYptzCoFKRNy8UsqFzNQOCGwlJkgAlCqzVIDATMkSIghw7rjcHti2/GgbD9qN774wcIAoOEfwuChIV/gYmDho+QkZKTR3p7EQAh+QQFCgALACwBAAAAHQAOAAAEcnDJSacgNeu9CimZwE0GUhEoVSTJKAWBOKGYJLD1CAfGnEoElkuC2PlyuKFkADMtaIsDKyGbHDYG4zMVYIEmAYVicBAgehNmTNNaJsQKnmCOuEYDgBGAAFfUAHNzeUp9VBQHCIFOLmFxWHNoQwWRWEocEQAh+QQFCgALACwHAAAAGQARAAAEaXDJuUAANOs9wsjfthxGFpwZQYiCgE1nQKni0goHjEqFGmqGFkInWwxUhdoC0SotYhLVSnm4SaALWiaREFAATY2A4BxzE2JnrXBOJJWb9pTihRu5dnggl+/7NQqBggk/fYKHCn8LiAqEEQAh+QQFCgALACwOAAAAEgAYAAAEZtAMs6q9WAy8EOXLIF5DEIDhWBnmCYpb1SIoXCEtmsbt944CU6wyIBBQgMDBUjAShiBD06mzOAkFWrVihG6/4G9iTD5WyejEOU0QhMMB3zegULi+9XrCCwIQ8gpmWwMJeXdbdApuEQAh+QQFCgALACwOAAAAEgAeAAAEgPCgs6q9GAmEAb5CCA7DV4XCRaYmagmk14oLQJbm4i53foq2AauCCAQMJsPQYDRyfIdBM4DzTY8+C8CZxQy74CxhTC58P+Q0QawuhN8WynuQSMDrdcI5WcAn3CYBCjICBHgmBQoKaxeGJgeKClVdggp2bwmKAW8CkXAEinJhVCYRACH5BAUKAAsALA8AAQARAB8AAAR8cMm5zKEYAyGyPxziZQhnjJQRohQnXGzFASkHU/dylCa7uTSUS4DIeVSCU0yiXDo9gah0EIRKr6hrlPrsOgUEwsAZDheeZcJokKAUymNKIJE4TwZhiWIvoSc6HnsKE3RqgXwSBHQjghR+h4MTBYsZjRiAGAkKbU4DCnFLEQAh+QQFCgALACwIAA4AGAASAAAEbHDJSesSOKNj+8wg4nkgto1oigoqCgSB2FpwbczUMdTBMAuE28LAky0AikCHQKggYMIFQaEoLBJYCbM5GlAVHGxCMmBaPQmq8pqVFJg+GnUsEVO2nbQizqZPmB1UXHVtE3wVOxUECYM4H34qEQAh+QQFCgALACwCABIAHQAOAAAEeHDJSatd5lJTtDWCkF2BogQehYQCclBCYpopBbACIBGzQugeQOC1OKxChpIpMZAYmBZBINCcGFaHgQk1KSQSKIJYMg2MLMRJ7LsbLxDl2oTAbhMmgylCvvje7VZxNXQJAnNuEnlcKV8dh38TCGcehhUFBI58cpA1EQAh+QQFCgALACwAAA8AGQARAAAEZ3AkReu6OOtbu9pgJnlfaJ7oeQQpmiRDCxLvK2dFnRSoIWw1wu8i3PgEgIzApiEQLoHoRUA9oJzPRZS1OCJOBWdMK70gqIbQwMmDlhcH6nCWdXMvAGrIqdlqDFZqGgMBYzcaAAFJGxEAIfkEBQoACwAsAQAIABEAGAAABF1QKBWWvfiGqdLI4EJwCgGE2JCQaLZRbWZUcW3feK7v6EAQNkTh96sRCQVDy/crXA6BE+j3uQwCAcFCwEXNsBauNoQNIMJdEKJ8EZOxSvTYlcW4QYa5BSE43w4IBxEAIfkEBQoACwAsAAACAA4AHQAABHJwyblGoHgqRTLeiuBNwZaMU7Jd6AAaaUcRW5EmCSEugMJKBRyuAPMICMITaoEbLBeH51JQIFivmatWRqFuudLwDoUIBAAjg3ntsawHUUzZPEBLBPGFOoCgAAQCRR4HgGMeCICCGQaAfWSAeUYCdigHihEAOw==);
692 | visibility: hidden;
693 | opacity: .5;
694 | }
695 |
696 |
697 | /* Make labels have a hand cursor. */
698 | label {
699 | cursor: pointer;
700 | }
701 |
702 | .callout {
703 | border-radius: 9px;
704 | box-shadow: 3px 3px 6px rgba(0,0,0,.2), inset -1px 0 2px rgba(0,0,0,.2);
705 |
706 | background: #FFF;
707 | /* Transparent background for Firefox 3.5- */
708 | background: rgba(255, 255, 255, .95);
709 | /* Chrome/Safari 5.1+ */
710 | background: -webkit-linear-gradient(bottom, rgba(240,240,240,.97), rgba(255,255,255,.97) 60px);
711 | /* IE 10, Firefox 16+, Opera 12.10+ */
712 | background: linear-gradient(to top, rgba(240,240,240,.97), rgba(255,255,255,.97) 60px);
713 |
714 | position: absolute;
715 | border: #ccc solid 1px;
716 | /* There is no padding on the left so that the lexicon title's diacritics have enough room to be displayed. */
717 | padding: 4px 4px 4px 0;
718 | font-size: 16px;
719 | min-width: 46px;
720 | max-width: 95%;
721 | max-height: 90%;
722 | width: 300px;
723 | height: 125px;
724 | }
725 |
726 | .callout .inside {
727 | overflow-x: hidden;
728 | height: 100%;
729 | }
730 | .pointer-down {
731 | box-shadow: 6px 4px 6px -3px rgba(0,0,0,.2);
732 | transform: rotate(45deg);
733 | -moz-transform: rotate(45deg);
734 | -webkit-transform: rotate(45deg);
735 | position: absolute;
736 | width: 20px;
737 | height: 20px;
738 | border-bottom: #ccc solid 1px;
739 | border-right: #ccc solid 1px;
740 |
741 | /* Chrome/Safari 5.1+ */
742 | background: -webkit-linear-gradient(left top, rgba(255,255,255,0) 41%, rgba(240,240,240,.97) 41%);
743 | /* IE 10, Firefox 16+, Opera 12.10+ */
744 | background: linear-gradient(to right bottom, rgba(255,255,255,0) 41%, rgba(240,240,240,.97) 41%);
745 | bottom: -11px;
746 | }
747 | .pointer-up {
748 | box-shadow: -1px -3px 6px -3px rgba(0,0,0,.2);
749 | transform: rotate(45deg);
750 | -moz-transform: rotate(45deg);
751 | -webkit-transform: rotate(45deg);
752 |
753 | position: absolute;
754 | top: -11px;
755 |
756 | width: 20px;
757 | height: 20px;
758 |
759 | border-top: #ccc solid 1px;
760 | border-left: #ccc solid 1px;
761 |
762 | /* Chrome/Safari 5.1+ */
763 | background: -webkit-linear-gradient(right bottom, rgba(255,255,255,0) 45%, rgba(255,255,255,.95) 45%);
764 | /* IE 10, Firefox 16+, Opera 12.10+ */
765 | background: linear-gradient(to left top, rgba(255,255,255,0) 45%, rgba(255,255,255,.95) 45%);
766 | }
767 |
768 | /* Hide certain callout content when showing details (such as the "more" button). */
769 | .detailed_callout .simple_only {
770 | display: none;
771 | }
772 |
773 | /* Hide detailed callout content by default. */
774 | .detailed_only {
775 | display: none;
776 | }
777 |
778 | /* Display detailed callout content when showing details. */
779 | .detailed_callout .detailed_only {
780 | display: block;
781 | }
782 |
783 |
784 | .lex-title {
785 | /* Push the text over a little so that diacritics are not cut off. */
786 | margin-left: 7px;
787 | }
788 |
789 | .lex-orig_word {
790 | /* Palatino Linotype (or similar fonts) are used for Greek and BF_Hebrew for Hebrew. */
791 | /* If both fonts are installed on the computer, the Hebrew letters will fall back to BF_Hebrew, */
792 | /* but if they are not installed, this will not happen; therefore, a separate class is needed for the Hebrew text. */
793 | font-family: 'Palatino Linotype', Palatino, TexGyrePagella, 'TeX Gyre Pagella', 'TeX Gyre Pagella Webfont', 'URWPalladioL', 'Book Antiqua', 'Times new Roman', Times, serif;
794 | font-size: 30px;
795 | /* The vertical alignment is required simply to match up with .lex-pronun. */
796 | vertical-align: super;
797 |
798 | /* Transition the font size when expanding the callout for more details. */
799 | /* Transition for Mozilla (Firefox 4.0+) */
800 | -moz-transition: font-size 150ms ease 150ms;
801 | /* Transition for WebKit */
802 | /* NOTE: In the WebKit specific CSS below, the line-height property is required to fix some issues and this property also must transition with the font size. */
803 | -webkit-transition: font-size 150ms ease 150ms, line-height 150ms ease 150ms;
804 | /* Transition for Opera 10.5+ */
805 | transition: font-size 150ms ease 150ms;
806 | }
807 |
808 | /* The first letter of Psalm 119 titles is a Hebrew letter. */
809 | .hebrew, .psalm_119_heading:first-letter {
810 | font-family: "Ezra SIL SR", BF_Hebrew;
811 | }
812 |
813 | /* Zoom the font in when transitioning to show more details. */
814 | .large_callout .lex-orig_word {
815 | font-size: 55px;
816 | }
817 |
818 |
819 | .lex-pronun {
820 | font-family: Verdana, 'Bitstream Vera Sans', 'Bitstream Vera Sans Webfont', 'DejaVu Sans', sans-serif;
821 | font-size: 14px;
822 | white-space: nowrap;
823 | /* Because this element can wrap if the word is long and the line height is rather large due to the large original word one the line above, */
824 | /* it needs to be centered above the baseline to look good. */
825 | vertical-align: super;
826 | /* This pushes down the context menu and drop down arrow slightly. */
827 | padding-bottom: 3px;
828 |
829 | /* Transition the font size when expanding the callout for more details. */
830 | /* Transition for Mozilla (Firefox 4.0+) */
831 | -moz-transition: font-size 200ms ease 10ms;
832 | /* Transition for WebKit */
833 | -webkit-transition: font-size 200ms ease 10ms;
834 | /* Transition for Opera 10.5+ */
835 | transition: font-size 200ms ease 10ms;
836 | }
837 |
838 | /* Zoom the font in when transitioning to show more details. */
839 | .large_callout .lex-pronun {
840 | font-size: 16px;
841 | }
842 |
843 | .expandable {
844 | margin-top: 7px;
845 | /* Without this, the before pseudo element does not stay with the parent element when scrolling in WebKit. */
846 | /* In Mozilla, the pseudo element sometimes stays with the element, but there is a long delay before it is redrawn. */
847 | position: relative;
848 | }
849 |
850 | .expandable_summary {
851 | font-weight: bold;
852 | /* Push the text over to make way for the arrow. */
853 | padding-left: 10px;
854 | }
855 |
856 | .dropdown, .lang:after, .expandable_summary:before {
857 | /* The drop down arrow (triangle) */
858 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAQAAAC/O6xzAAAAXklEQVQI12Ng4GJIZXjP8B8JTmYQY2BgY9BmWMjwFS54lMEHqJSBCUgYMSyBShxnCGTgZ2BmYABJcAMlFjN8AaoMZBBgYGGAApAOPYYSBnegSrggRIINKMQB0c7AAAAMyxgJkjHDSAAAAABJRU5ErkJggg==);
859 | background-repeat: no-repeat;
860 | }
861 |
862 | .expandable_summary:before {
863 | /* This is necessary (for some reason) to make the element visible. */
864 | content: "";
865 |
866 | /* This is needed to be able to change the width and height. */
867 | position: absolute;
868 |
869 | width: 11px;
870 | height: 6px;
871 |
872 | /* Reposition the arrow to align with the left. */
873 | margin-top: 8px;
874 | margin-left: -12px;
875 |
876 | /* The default position for the arrow is rotated to the right. */
877 | transform: rotate(-90deg);
878 | -moz-transform: rotate(-90deg);
879 | -webkit-transform: rotate(-90deg);
880 | /* This is used to center the rotation of the arrow to make it rotate properly. */
881 | transform-origin: 6px 1px;
882 | -moz-transform-origin: 6px 1px;
883 | -webkit-transform-origin: 6px 1px;
884 |
885 | transition: transform 250ms;
886 | -moz-transition: -moz-transform 250ms;
887 | -webkit-transition: -webkit-transition 250ms;
888 | }
889 |
890 | /* The expander's summary's pseudo element when clicked. */
891 | .expandable_summary.expanded:before {
892 | /* Rotate the arrow to point down. */
893 | /*NOTE: If 0 degrees is used, it will visually jump; therefore, 1 degree is used. */
894 | transform: rotate(-1deg);
895 | -moz-transform: rotate(-1deg);
896 | -webkit-transform: rotate(-1deg);
897 | }
898 |
899 | /* Set the number styles for list elements (specifically for long definitions). */
900 | ol {
901 | margin: 0;
902 | list-style-type: decimal;
903 | padding-left: 2em;
904 | }
905 |
906 | ol ol {
907 | list-style-type: lower-latin;
908 | }
909 |
910 | ol ol ol {
911 | list-style-type: lower-roman;
912 | }
913 |
914 | ol ol ol ol {
915 | list-style-type: upper-latin;
916 | }
917 |
918 | ol ol ol ol ol {
919 | list-style-type: upper-roman;
920 | }
921 |
922 | ol ol ol ol ol ol {
923 | list-style-type: decimal;
924 | }
925 |
926 |
927 | .dropdown {
928 | /* Move the arrow all the way to the right, and center it vertically. */
929 | background-position: 100%;
930 | /* Add some extra space to the right for the drop down arrow. */
931 | padding-right: 12px;
932 | cursor: pointer;
933 | }
934 |
935 | .lex-body {
936 | /* Push the text over a little to make it line up (more or less) with the title. */
937 | margin-left: 4px;
938 | }
939 |
940 | /* The element that causes the text to fade out when a large callout is shown. */
941 | .transparent_el {
942 | background: #FFF;
943 | }
944 |
945 | /*NOTE See above for .more-button-buffer:hover and .more-button-buffer:active. */
946 | .more-button {
947 | position: absolute;
948 | bottom: 0;
949 | right: 0;
950 |
951 | border-top: 1px solid rgba(0,0,0,.1);
952 | border-left: 1px solid rgba(0,0,0,.1);
953 | border-radius: 9px 0;
954 |
955 | padding: 4px;
956 |
957 | font-size: 12px;
958 | /* This is the same as .lang. */
959 | background: rgba(0,0,200,.05);
960 | color: rgba(0,0,0,.2);
961 |
962 | cursor: pointer;
963 |
964 | /* Prevent the user from selecting the text (though only when directly clicking it). */
965 | /* This also prevent users from copying and text in Mozilla. */
966 | user-select: none;
967 | -webkit-touch-callout: none;
968 | -webkit-user-select: none;
969 | -moz-user-select: none;
970 | -ms-user-select: none;
971 |
972 | /* Apply transitions to all changes. */
973 | /* Transition for Mozilla (Firefox 4.0+) */
974 | -moz-transition: background .2s, color .2s;
975 | /* Transition for WebKit */
976 | -webkit-transition: background .2s, color .2s;
977 | /* Transition for Opera 10.5+ */
978 | transition: background .2s, color .2s;
979 | }
980 |
981 | .more-button:active {
982 | /* Emulate button pressing */
983 | /*NOTE: There is another .more-button:active above. */
984 | padding: 5px 3px 3px 5px;
985 | /* The box shadow gives some depth to the button when it is pressed. */
986 | box-shadow: inset 2px 2px 3px rgba(0,0,0,.2);
987 | }
988 |
989 | /* This is an invisible element that is used to prevent real text from going over the more button. */
990 | .more-button-buffer {
991 | position: static;
992 | visibility: hidden;
993 | float: right;
994 | /* NOTE: Because this element is not positioned absolutely, it is moved over to the left slightly (due to padding in the parent element), */
995 | /* but this extra padding adds extra buffer between the button and the text, so the padding attribute does not need to be changed here. */
996 | }
997 |
998 |
999 | /* The class allows for simple fade ins and outs with JavaScript. */
1000 | .fade {
1001 | /* Fade out slightly when the mouse cursor moves off. */
1002 | /* Transition for Mozilla (Firefox 4.0+) */
1003 | -moz-transition: opacity .3s ease;
1004 | /* Transition for WebKit */
1005 | -webkit-transition: opacity .3s ease;
1006 | /* Transition for Opera 10.5+ */
1007 | transition: opacity .3s ease;
1008 | }
1009 |
1010 |
1011 | /* Verses created from a search. */
1012 | .search_verse {
1013 | text-indent: 2em;
1014 | width: 100%;
1015 | }
1016 |
1017 | .search_verse span {
1018 | cursor: pointer;
1019 | }
1020 |
1021 | /* Verses created from a verse lookup. */
1022 | .verse {
1023 | text-indent: 1em;
1024 | width: 100%;
1025 | }
1026 |
1027 | /* Make all lines of text the same height. */
1028 | .scrolls .verse, .scrolls .first_verse, .psalm_title, .search_verse, .paragraph, .subscription {
1029 | line-height: 19px;
1030 | }
1031 |
1032 | /***************************************/
1033 | /* Verses created from a verse lookup. */
1034 | /***************************************/
1035 |
1036 | .psalm_title {
1037 | /* Adding padding to the sides and bottom to distinguish the Psalm title from the rest of the text. */
1038 | /*NOTE: Padding should always be used for verses instead of margin because it can interfere with determining the verse range. */
1039 | padding: 0 2em 19px 2em;
1040 | }
1041 |
1042 | /* Subscriptions added to the end of Pauline Epistles. */
1043 | .subscription {
1044 | /* Add indentation on both sides and insert a single blank line between the text and the subscription. */
1045 | /*NOTE: Padding should always be used for verses instead of margin because it can interfere with determining the verse range. */
1046 | /*FIXME: Because of the top padding, when looking up a subscription, the text is scrolled to the blank line above the text. */
1047 | padding: 19px 2em 0 2em;
1048 | }
1049 |
1050 | /* Drop caps for the first letter of a chapter */
1051 | .first_verse:first-letter, .first_paragraph:first-letter {
1052 | /*FIXME: Capital J's can cover up part of characters in the third line of the first verse. (See 2 Kings 22:1 in a narrow window.) */
1053 | font-size: 51px;
1054 | float: left;
1055 |
1056 | /*NOTE: Bold makes some letters better, especially P's. */
1057 | /* This fixes the spacing problem with some letters, like W, but not quite capital J's (see Psalm 43). */
1058 | margin-bottom: -9px;
1059 |
1060 | /* Ensure that the verse letter has the correct line height even if it is a divine word. (See Psalm 3.) */
1061 | line-height: 19px!important;
1062 | }
1063 |
1064 | .first_verse {
1065 | /* This fixes a bug where the last word of the first verse sometimes wraps. */
1066 | width: 100%;
1067 | /* This makes sure that the first line is at least two lines long to give the drop caps just enough space. */
1068 | min-height: 38px;
1069 | }
1070 |
1071 |
1072 | .paragraph {
1073 | text-indent: 2em;
1074 | }
1075 |
1076 | .paragraph .first_verse {
1077 | display: inline;
1078 | }
1079 |
1080 | .first_paragraph {
1081 | text-indent: 0;
1082 | }
1083 |
1084 | .paragraph .verse {
1085 | display: inline;
1086 | }
1087 |
1088 | /* Verse numbers in paragraph form */
1089 | .paragraph span {
1090 | /* Shrink the verse number. */
1091 | font-size: 75%;
1092 | /* Raise it slightly. */
1093 | vertical-align: 4px;
1094 | /* Remove extra line height created by raising the numbers. */
1095 | line-height: 1px!important;
1096 | /* Move the verse numbers over a little closer to the text. */
1097 | margin-right: -2px;
1098 | }
1099 |
1100 | /* Serif fonts */
1101 | .book, .chapter, .short_book, .first_verse:first-letter, .first_paragraph:first-letter, .d, .psalm_119_heading {
1102 | font-family: 'TeX Gyre Pagella Webfont', 'Palatino Linotype', Palatino, TexGyrePagella, 'TeX Gyre Pagella', 'URWPalladioL', 'Book Antiqua', 'Times new Roman', Times, serif;
1103 | }
1104 |
1105 | .book, .chapter, .short_book {
1106 | text-align: center;
1107 | width: 100%;
1108 | font-size: 23px;
1109 | }
1110 |
1111 | .chapter, .short_book, .psalm_119_heading {
1112 | padding: 13px 0 13px 0;
1113 | margin: 0;
1114 | line-height: 31px;
1115 | }
1116 |
1117 | /* Long book titles */
1118 | div.book {
1119 | padding: 57px 0 19px 0;
1120 | }
1121 |
1122 |
1123 | /* Main name for long book titles */
1124 | h1 {
1125 | margin: 0;
1126 | font-size: 69px;
1127 | display: block;
1128 | font-weight: bold;
1129 | line-height: 95px;
1130 | }
1131 |
1132 | /* Pre- and post-titles for long book titles */
1133 | h2 {
1134 | font-size: 23px;
1135 | display: block;
1136 | font-weight: normal;
1137 | margin: 0;
1138 | line-height: 19px;
1139 | }
1140 |
1141 |
1142 | /* The divine name of God */
1143 | .d {
1144 | /* Firefox 4.0+ and recent version of Chrome also support font-feature-settings:"smcp" which turns on small caps as well. */
1145 | font-variant: small-caps;
1146 | font-size: 16px;
1147 | /* Different fonts have different line-heights. This eliminates the inconsistency. */
1148 | line-height: 1px!important;
1149 | }
1150 |
1151 | /* Added (implied) words */
1152 | .a {
1153 | font-style: italic;
1154 | }
1155 |
1156 | /* Quotations of Jesus */
1157 | .q {
1158 | -webkit-transition: color 1s;
1159 | transition: color 1s;
1160 | color: #D00;
1161 | }
1162 | /* Override for black letters. */
1163 | .black_letter .q {
1164 | color: #000;
1165 | }
1166 |
1167 |
1168 | .no_results {
1169 | margin-top: 10px;
1170 | }
1171 |
1172 |
1173 | .psalm_119_heading {
1174 | text-align: center;
1175 | font-size: 23px;
1176 | }
1177 |
1178 | .psalm_119_heading:first-letter {
1179 | /* For some reason, the Hebrew letter creates a line height issues that can be solved by completely removing the line height from it. */
1180 | line-height: 0;
1181 | }
1182 |
1183 |
1184 | /* Style the previous and next links for the non-JavaScript version. */
1185 | .static_link {
1186 | text-decoration: none;
1187 | color: #000;
1188 | opacity: .5;
1189 | margin: 10px 0;
1190 | }
1191 | .static_link:hover {
1192 | opacity: 1;
1193 | }
1194 |
1195 | .prev {
1196 | float: left;
1197 | }
1198 |
1199 | .next {
1200 | float: right;
1201 | }
1202 |
1203 | .unsupported_warning {
1204 | display: block;
1205 | text-align: center;
1206 | padding: 10px;
1207 | }
1208 |
1209 | /* This is used in the non-JS version. */
1210 | .footer {
1211 | padding: 20px;
1212 | max-width: 800px;
1213 | margin: 0 auto;
1214 | }
1215 |
1216 | /* Highlighted found words from searches */
1217 | .f1 {
1218 | background: #FF6;
1219 | }
1220 | .f2 {
1221 | background: #AFF;
1222 | }
1223 | .f3 {
1224 | background: #9F9;
1225 | }
1226 | .f4 {
1227 | background: #F99;
1228 | }
1229 | .f5 {
1230 | background: #F6F;
1231 | }
1232 | .f6 {
1233 | background: #800;
1234 | }
1235 | .f7 {
1236 | background: #0A0;
1237 | }
1238 | .f8 {
1239 | background: #860;
1240 | }
1241 | .f9 {
1242 | background: #049;
1243 | }
1244 | .f10 {
1245 | background: #909;
1246 | }
1247 | .f11 {
1248 | background: #EE0;
1249 | }
1250 | .f12 {
1251 | background: #0FF;
1252 | }
1253 | .f13 {
1254 | background: #A0A;
1255 | }
1256 | .f14 {
1257 | background: #7F0;
1258 | }
1259 | .f15 {
1260 | background: #19f;
1261 | }
1262 | .f16 {
1263 | background: #F00;
1264 | }
1265 | .f17 {
1266 | background: #F70;
1267 | }
1268 | .f18 {
1269 | background: #CF0;
1270 | }
1271 | .f19 {
1272 | background: #AA9;
1273 | }
1274 | .f20 {
1275 | background: #469;
1276 | }
1277 |
1278 | /* Fonts for Mozilla/Opera/WebKit/IE9+ */
1279 | /*NOTE: If no local() fonts are supplied, Mozilla will always download the web font if it detects an element with this CSS. */
1280 | @font-face {
1281 | font-family: 'TeX Gyre Pagella Webfont';
1282 | src: local('Palatino Linotype'),
1283 | local('PalatinoLinotype-Roman'),
1284 | local('Palatino'),
1285 | local('TeX Gyre Pagella'),
1286 | local('TeXGyrePagella-Regular'),
1287 | url('/fonts/TeXGyrePagella-Regular.woff') format('woff');
1288 | }
1289 | @font-face {
1290 | font-family: 'TeX Gyre Pagella Webfont';
1291 | src: local('Palatino Linotype Italic'),
1292 | local('PalatinoLinotype-Italic'),
1293 | local('Palatino Italic'),
1294 | local('Palatino-Italic'),
1295 | local('TeXGyrePagella-Italic'),
1296 | url('/fonts/TeXGyrePagella-Italic.woff') format('woff');
1297 | font-style: italic;
1298 | }
1299 | @font-face {
1300 | font-family: 'TeX Gyre Pagella Webfont';
1301 | src: local('Palatino Linotype Bold'),
1302 | local('PalatinoLinotype-Bold'),
1303 | local('Palatino Linotype, Bold'),
1304 | local('Palatino Bold'),
1305 | local('Palatino-Bold'),
1306 | local('TeXGyrePagella-Bold'),
1307 | url('/fonts/TeXGyrePagella-Bold.woff') format('woff');
1308 | font-weight: bold;
1309 | }
1310 | @font-face {
1311 | font-family: 'TeX Gyre Pagella Webfont';
1312 | src: local('Palatino Linotype Bold Italic'),
1313 | local('PalatinoLinotype-BoldItalic'),
1314 | local('Palatino-BoldItalic'),
1315 | local('TeXGyrePagella-BoldItalic'),
1316 | url('/fonts/TeXGyrePagella-BoldItalic.woff') format('woff');
1317 | font-weight: bold;
1318 | font-style: italic;
1319 | }
1320 |
1321 | @font-face {
1322 | font-family: 'Bitstream Vera Sans Webfont';
1323 | src: local('Verdana'),
1324 | local('Bitstream Vera Sans'),
1325 | local('BitstreamVeraSans-Roman'),
1326 | url('/fonts/BitstreamVeraSans-Roman.woff') format('woff');
1327 | }
1328 | @font-face {
1329 | font-family: 'Bitstream Vera Sans Webfont';
1330 | src: local('Verdana Italic'),
1331 | local('Verdana-Italic'),
1332 | local('Bitstream Vera Sans Oblique'),
1333 | local('BitstreamVeraSans-Oblique'),
1334 | url('/fonts/BitstreamVeraSans-Oblique.woff') format('woff');
1335 | font-style: italic;
1336 | }
1337 | @font-face {
1338 | font-family: 'Bitstream Vera Sans Webfont';
1339 | src: local('Verdana Bold'),
1340 | local('Verdana-Bold'),
1341 | local('Bitstream Vera Sans Bold'),
1342 | local('BitstreamVeraSans-Bold'),
1343 | url('/fonts/BitstreamVeraSans-Bold.woff') format('woff');
1344 | font-weight: bold;
1345 | }
1346 | @font-face {
1347 | font-family: 'Bitstream Vera Sans Webfont';
1348 | src: local('Verdana Bold Italic'),
1349 | local('Verdana-BoldItalic'),
1350 | local('Bitstream Vera Sans Bold Oblique'),
1351 | local('BitstreamVeraSans-BoldOblique'),
1352 | url('/fonts/BitstreamVeraSans-BoldOblique.woff') format('woff');
1353 | font-weight: bold;
1354 | font-style: italic;
1355 | }
1356 |
1357 | @font-face {
1358 | font-family: 'BF_Hebrew';
1359 | src: local('Ezra SIL SR'),
1360 | url('/fonts/BF_Hebrew.woff') format('woff');
1361 | }
1362 |
1363 |
1364 |
1365 | /* HTML scrollbar styles (WebKit only) */
1366 | /* NOTE: If ::-webkit-scrollbar is not present, the other styles will not work. */
1367 | html > ::-webkit-scrollbar {
1368 | width: 8px;
1369 | background: #FFF;
1370 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
1371 | }
1372 |
1373 | html > ::-webkit-scrollbar-thumb {
1374 | border-radius: 4px;
1375 | background: rgba(130,130,130,.3);
1376 | /* Do not let the thumb get too small. */
1377 | min-height: 35px;
1378 | }
1379 |
1380 | html > ::-webkit-scrollbar-thumb:hover {
1381 | background: rgba(130,130,130,.5);
1382 | }
1383 |
1384 | /*
scrollbars (WebKit only) */
1385 | /* NOTE: If ::-webkit-scrollbar is not present, the other styles will not work. */
1386 | div ::-webkit-scrollbar {
1387 | width: 8px;
1388 | background: transparent;
1389 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
1390 | border-radius: 4px;
1391 | }
1392 |
1393 | div ::-webkit-scrollbar-thumb {
1394 | border-radius: 4px;
1395 | background: rgba(130,130,130,.3);
1396 | /* Do not let the thumb get too small. */
1397 | min-height: 25px;
1398 | }
1399 |
1400 | div ::-webkit-scrollbar-thumb:hover {
1401 | background: rgba(130,130,130,.5);
1402 | }
1403 |
1404 |
1405 | /* Set viewport width for modile devices. */
1406 | /*NOTE: This needs to be tested on more devices and may need multiple media queries to display properly across devices. */
1407 | @-o-viewport {
1408 | width: 67%;
1409 | user-zoom: fixed;
1410 | }
1411 | @-ms-viewport {
1412 | width: 67%;
1413 | user-zoom: fixed;
1414 | }
1415 | @-moz-viewport {
1416 | width: 67%;
1417 | user-zoom: fixed;
1418 | }
1419 | @-webkit-viewport {
1420 | width: 67%;
1421 | user-zoom: fixed;
1422 | }
1423 | @viewport {
1424 | width: 67%;
1425 | user-zoom: fixed;
1426 | }
1427 |
1428 |
1429 | /*******************************/
1430 | /* WebKit & Opera specific CSS */
1431 | /*******************************/
1432 |
1433 | /* Make the drop caps aligned with the second line of text. */
1434 | .webkit .first_verse:first-letter, .webkit .first_paragraph:first-letter, .opera .first_verse:first-letter, .opera .first_paragraph:first-letter {
1435 | margin-bottom: 0;
1436 | margin-top: 5px;
1437 | padding: 1px;
1438 | }
1439 |
1440 | /****************************/
1441 | /* Just WebKit specific CSS */
1442 | /****************************/
1443 |
1444 | .webkit .lex-orig_word {
1445 | /* NOTE: Because the font size changes via a CSS transition, the line-height also must transition as well. See above for the declaration. */
1446 | line-height: 150%;
1447 | }
1448 |
1449 | /***************************/
1450 | /* Just Opera specific CSS */
1451 | /***************************/
1452 |
1453 | .opera .first_verse:first-letter, .opera .first_paragraph:first-letter {
1454 | margin-bottom: 0;
1455 | margin-top: 13px;
1456 | padding: 1px;
1457 | }
1458 |
1459 | /* Because Opera raises these elements regardless of whether they are on the same line or not, the alignment must be reverted, */
1460 | /* and thereby causing the .lex-pronun element to be improperly aligned if it wraps. */
1461 | .opera .lex-orig_word, .opera .lex-pronun {
1462 | vertical-align: baseline;
1463 | }
1464 |
1465 |
1466 | /*******************/
1467 | /* IE specific CSS */
1468 | /*******************/
1469 |
1470 | /* Make the inputIcon appear next to the queryInput. */
1471 | /*NOTE: The query box is slightly off centered in IE with this CSS. */
1472 | .ie .inputIcon {
1473 | position: relative;
1474 | }
1475 |
1476 | /* Make the drop caps aligned with the second line of text. */
1477 | .ie .first_verse:first-letter, .ie .first_paragraph:first-letter {
1478 | margin-top: 5px;
1479 | margin-bottom: 0;
1480 | }
1481 |
1482 |
1483 | /************************/
1484 | /* Mozilla specific CSS */
1485 | /************************/
1486 |
1487 | /* Center the crown of thorns loader. */
1488 | .moz .crown_loader {
1489 | -moz-transform-origin: 50.5%;
1490 | position: relative;
1491 | top: -1px;
1492 | }
1493 |
--------------------------------------------------------------------------------
/server/bibleforge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * BibleForge
3 | *
4 | * @date 05-15-12
5 | * @version alpha (α)
6 | * @link http://BibleForge.com
7 | * @license The MIT License (MIT)
8 | */
9 |
10 | /*!
11 | * The BibleForge motto:
12 | *
13 | * "all things whatsoever ye would that men should do to you, do ye even so to them."
14 | * —Jesus (Matthew 7:12)
15 | */
16 |
17 | /*!
18 | * Copyright (C) 2022
19 | *
20 | * Permission is hereby granted, free of charge, to any person obtaining
21 | * a copy of this software and associated documentation files (the
22 | * “Software”), to deal in the Software without restriction, including
23 | * without limitation the rights to use, copy, modify, merge, publish,
24 | * distribute, sublicense, and/or sell copies of the Software, and to
25 | * permit persons to whom the Software is furnished to do so.
26 | *
27 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | */
35 |
36 | /// Set JSHint options.
37 | // jshint bitwise:true, curly:true, eqeqeq:true, forin:true, immed:true, latedef:true, newcap:true, noarg:true, noempty:true, nonew:true, onevar:true, plusplus:true, quotmark:double, strict:true, undef:true, unused:strict, node:true
38 |
39 | "use strict";
40 |
41 | /// Create the BibleForge global object to which everything else attaches.
42 | var BF = {};
43 |
44 | /// ********************
45 | /// * Create constants *
46 | /// ********************
47 |
48 | ///TODO: Link this with the client-side code, perhaps using the Forge.
49 | BF.consts = {
50 | /// Query type "constants"
51 | verse_lookup: 1,
52 | mixed_search: 2,
53 | standard_search: 3,
54 | grammatical_search: 4,
55 | lexical_lookup: 5,
56 |
57 | /// Direction "constants"
58 | additional: 1,
59 | previous: 2
60 | };
61 |
62 | /// ****************
63 | /// * Load modules *
64 | /// ****************
65 |
66 | BF.config = require("./config.js").config;
67 |
68 | /// Attach the database object.
69 | BF.db = require("./modules/db.js").db(BF.config.db);
70 |
71 | /// Attach the email sender module.
72 | BF.email = require("./modules/email.js").init(BF.config.smtp);
73 |
74 | /// ***************************
75 | /// * Create helper functions *
76 | /// ***************************
77 |
78 | /**
79 | * Safely parse JSON.
80 | *
81 | * @param str (string) The JSON encoded string to parse.
82 | * @return The parsed JSON or NULL if the JSON is invalid.
83 | * @todo Load this code from the client side (or copy it via the Forge).
84 | */
85 | BF.parse_json = function (str)
86 | {
87 | try {
88 | return JSON.parse(str);
89 | } catch (e) {}
90 | };
91 |
92 | /**
93 | * Escape a string to be safely added inside HTML.
94 | *
95 | * @example BF.escape_html('This is a "harmless" comment '); /// Returns "This is a "harmless" comment <script>...</script>"
96 | * @param str (string) The string to be escaped
97 | * @note This code only escapes the few dangerous symbols, not all of them.
98 | */
99 | BF.escape_html = function (str)
100 | {
101 | ///NOTE: It must first replace ampersands (&); otherwise, the other entities would be escaped twice.
102 | return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(//g, ">");
103 | };
104 |
105 | /**
106 | * Get the book, chapter, and verse numbers from a verse ID.
107 | *
108 | * @example BF.get_b_c_v(1002003); /// Returns {b: 1, c: 2, v: 3}
109 | * @param verseID (number || string) The verse ID to convert.
110 | * @return An object containing the book, chapter, and verse numbers: {b: book, c: chapter, v: verse}
111 | * @todo Load this code from the client side (or copy it via the Forge).
112 | */
113 | BF.get_b_c_v = function (verseID)
114 | {
115 | var c,
116 | v = verseID % 1000;
117 |
118 | c = ((verseID - v) % 1000000) / 1000;
119 |
120 | return {
121 | b: (verseID - v - c * 1000) / 1000000,
122 | c: c,
123 | v: v
124 | };
125 | };
126 |
127 | /**
128 | * Insert data into a string.
129 | *
130 | * @example BF.insert({"a": "text", num: 10}, "This is some {a}; {num} is a number. Here's more {a}.") /// Returns "This is some text; 10 is a number. Here's more text."
131 | * @param obj (object) An object representing the data to insert.
132 | * @param template (string) The template string using curly brackets with a name to indicate placeholders.
133 | */
134 | BF.insert = function (obj, template)
135 | {
136 | /// Match all matching curly brackets, and send them to the function.
137 | return template.replace(/{([^{}]+)}/g, function (whole, inside)
138 | {
139 | var data = obj[inside];
140 | return typeof data !== "undefined" ? data : whole;
141 | });
142 | };
143 |
144 |
145 | /**
146 | * Tries to detect bots based on their user-agent.
147 | *
148 | * @param agent (string) The user-agent to examine.
149 | * @return A boolean indicating whether or not the client appears to be a bot.
150 | * @note User-agents can be easily spoofed.
151 | */
152 | BF.is_bot = function (agent)
153 | {
154 | ///NOTE: Using parentheses prevents the regex from possibly (however unlikely) from looking like the divion sign.
155 | return (/google(?:bot|\/)|yahoo\!|bingbot|baiduspider|iaskspider|ia_archiver|yandex/i).test(agent);
156 | };
157 |
158 | /// **************************
159 | /// * Create query functions *
160 | /// **************************
161 |
162 | /**
163 | * Retrieve verses from the database.
164 | *
165 | * @example BF.verse_lookup({q: "1001001"}, function (data) {}); /// Look up verses starting with Genesis 1:1 in English.
166 | * @example BF.verse_lookup({q: "19119160", f: "1"}, function (data) {}); /// Look up verses starting with Psalm 119:160 in English.
167 | * @example BF.verse_lookup({q: "19119152", d: "2"}, function (data) {}); /// Look up previous verses starting with Psalm 119:152 in English.
168 | * @example BF.verse_lookup({q: "66022021", f: "1", l: "zh_t"}, function (data) {}); /// Look up verses starting with Revelation 22:21 in Traditional Chinese.
169 | * @param data (object) An object containing the query and query options.
170 | * Object structure:
171 | * q: "The verse ID",
172 | * d: "The direction (BF.consts.additional OR BF.consts.previous)" (optional) (default: BF.consts.additional)
173 | * f: "Whether or not to find the start of a paragraph" (optional) (default: FALSE)
174 | * l: "The language ID" (optional) (default: "en")
175 | * p: "Whether or not to return verses in groups of paragraphs" (optional) (default: TRUE)
176 | * @param callback (function) The function to send the results to.
177 | * @return An object containing the results (if any).
178 | * Object structure:
179 | * n: (array) An array of verse IDs for each verse returned
180 | * v: (array) An array of strings containing the HTML of the verses
181 | * p: (array) An array of numbers either 1 (indicating a paragraph break at that verse) or 0 (no paragraph break)
182 | * t: (number) The total number of verses
183 | */
184 | BF.verse_lookup = function (data, callback)
185 | {
186 | var extra_fields,
187 | direction = data.d ? Number(data.d) : BF.consts.additional,
188 | find_paragraph_start = data.f === "1",
189 | in_paragraphs = typeof data.p === "undefined" ? true : data.p === "1",
190 | /// Select the language object specified by the query or use the default.
191 | lang = BF.langs[data.l] || BF.langs.en,
192 | limit,
193 | operator,
194 | order_by,
195 | verse_id = Number(data.q);
196 |
197 | if (in_paragraphs && lang.no_paragraphs) {
198 | in_paragraphs = false;
199 | }
200 |
201 | /**
202 | * Send the query to the database.
203 | *
204 | * @note This is a separate query because it can be called at two different times (and one is from an asynchronous callback).
205 | */
206 | function run_query(starting_verse)
207 | {
208 | BF.db.query("SELECT id, words" + extra_fields + " FROM `bible_" + lang.id + "_html` WHERE id " + operator + starting_verse + order_by + " LIMIT " + limit, function (verses)
209 | {
210 | var i,
211 | len,
212 | res = {
213 | n: [],
214 | v: []
215 | };
216 |
217 | /// Was there no response from the database? This could mean the database crashed.
218 | if (!verses) {
219 | /// Send an empty response, and exit.
220 | callback({});
221 | return;
222 | }
223 |
224 | if (in_paragraphs) {
225 | res.p = [];
226 |
227 | /// Determine the actual number of verses that should be returned (starting from the end).
228 | ///NOTE: Because the last verse cannot be in the middle of a paragraph break, it has to trim off the last partial paragraph from the database results.
229 | len = verses.length;
230 | /// Did it return the expected number of verses?
231 | /// If not, then it must have reached the end of the Bible, in which case it has also reached the end of a paragraph.
232 | if (len === limit) {
233 | /// Start at the end of the dataset, and look for the last (i.e., first in reverse order) paragraph marker.
234 | /// Once found, trim off the last, incomplete paragraph (if any).
235 | ///NOTE: When preforming previous lookups, there might not be anything to trim off, but additional lookups must at least trim off one verse
236 | /// because it must stop before the last paragraph marker.
237 | while (true) {
238 | /// Is it at a paragraph break?
239 | ///NOTE: Since the database may return "0" or "1" as strings, it is necessary to convert them to JavaScript Numbers so that 0 will be falesy.
240 | if (Number(verses[len - 1].paragraph)) {
241 | /// The first verse should be at a paragraph beginning, and the last verse
242 | /// should be just before one. Therefore, when looking up previous verses,
243 | /// we must get this verse (because previous lookups are in reverse).
244 | /// So, previous lookups should stop now because this verse is at the
245 | /// beginning of a paragraph, but additional lookups need to get the verse before.
246 | if (direction === BF.consts.previous) {
247 | break;
248 | }
249 | /// Move back one to get the verse before the paragraph break.
250 | len -= 1;
251 | break;
252 | }
253 | len -= 1;
254 | }
255 | }
256 | } else {
257 | /// When not breaking at paragraphs, just send back all of the verses retrieved from the database.
258 | len = verses.length;
259 | }
260 |
261 | /// Loop through the verses and fill in the results object.
262 | for (i = 0; i < len; i += 1) {
263 | res.n[i] = Number(verses[i].id);
264 | res.v[i] = verses[i].words;
265 | if (in_paragraphs) {
266 | res.p[i] = Number(verses[i].paragraph);
267 | }
268 | }
269 |
270 | /// Is the query looking up previous verses?
271 | if (direction === BF.consts.previous) {
272 | /// Because the database returns the verses in reverse order when preforming a previous lookup, they need to be reordered.
273 | ///NOTE: Because in paragraph mode, there is no way to know how many verses will be returned, it cannot simply put the verses in the array in reverse order above.
274 | res.n.reverse();
275 | res.v.reverse();
276 | if (in_paragraphs) {
277 | res.p.reverse();
278 | }
279 | }
280 |
281 | /// Add the total number of verses being sent back to the client.
282 | res.t = res.n.length;
283 |
284 | callback(res);
285 | });
286 | }
287 |
288 | /// If verse_id is not a number, send an empty response, and exit.
289 | if (isNaN(verse_id)) {
290 | callback({});
291 | return;
292 | }
293 |
294 | if (verse_id < 1001001) {
295 | /// Default to Genesis 1:1 if the verse_id is too small.
296 | verse_id = 1001001;
297 | } else if (verse_id > 66022021) {
298 | /// If the user is looking for a verse past the end, default to Revelation 22:21.
299 | ///NOTE: 66022021 may need to be language dependent because different languages have different verse breaks.
300 | verse_id = 66022021;
301 | /// If returning paragraphs, make sure to find the beginning of the last paragraph.
302 | if (in_paragraphs) {
303 | find_paragraph_start = true;
304 | }
305 | }
306 |
307 | if (direction === BF.consts.additional) {
308 | operator = ">=";
309 | order_by = "";
310 | } else {
311 | ///NOTE: To get the right verses in a previous verse lookup, we need to sort the database by id in reverse order because
312 | /// chapter and book boundaries are not predictable (i.e., we can't just say "WHERE id >= id - LIMIT").
313 | operator = "<=";
314 | ///NOTE: Leading space is needed in case the preceding variable does end with whitespace.
315 | order_by = " ORDER BY id DESC";
316 | }
317 |
318 | if (in_paragraphs) {
319 | /// The limit must be larger than the minimum length of the longest paragraph because paragraphs cannot be split.
320 | limit = lang.paragraph_limit;
321 | extra_fields = ", paragraph";
322 | } else {
323 | limit = lang.minimum_desired_verses;
324 | extra_fields = "";
325 | }
326 |
327 | /// If this is the first query and the query does not begin at an obvious paragraph break (e.g., the beginning of a chapter), we must first determine the where the paragraph begins.
328 | ///NOTE: For example, if the query is for Deuteronomy 6:4 (in paragraphs), the query cannot begin at Deuteronomy 6:4 because that is (or at least could be) the middle of a paragraph.
329 | /// So, we must first use another query to determine the first paragraph break before (or at) Deuteronomy 6:4. Currently, in the English version, it is Deuteronomy 6:3, so that will be used for starting_verse.
330 | if (find_paragraph_start) {
331 | /// Look up the nearest verse that is at a paragraph break, and then run the query.
332 | ///NOTE: This is much faster than adding a subquery to the main query.
333 | ///NOTE: Currently, find_paragraph_start is never true when direction === BF.consts.previous because previous lookups always start at a paragraph break.
334 | /// In order to find the correct starting verse when looking up in reverse, the comparison operator (<=) would need to be greater than or equal to (>=),
335 | /// and 1 would need to be subtracted from the found starting id.
336 | BF.db.query("SELECT id FROM `bible_" + lang.id + "_html` WHERE id <= " + verse_id + " AND paragraph = 1 ORDER BY id DESC LIMIT 1", function (start_id)
337 | {
338 | /// Was there no response from the database? This could mean the database crashed.
339 | if (!start_id || !start_id[0]) {
340 | /// Send an empty response, and exit.
341 | callback({});
342 | return;
343 | }
344 |
345 | run_query(start_id[0].id);
346 | });
347 | } else {
348 | /// Since if not grouping the verses in paragraphs, it does not matter where the query starts, so just start with the verse being queried.
349 | run_query(verse_id);
350 | }
351 | };
352 |
353 |
354 | /**
355 | * Preform a standard search of the Bible.
356 | *
357 | * @example BF.standard_search({q: "love"}, function (data) {}); /// Preform a search for the word "love."
358 | * @example BF.standard_search({q: "love", s: "5033004"}, function (data) {}); /// Preform a search for the word "love" starting from Deuteronomy 33:4.
359 | * @param data (object) An object containing the query and query options.
360 | * Object structure:
361 | * q: "The search query",
362 | * l: "The language ID" (optional) (default: "en")
363 | * s: "The verse ID from which to start the search" (optional) (default: 0)
364 | * @param callback (function) The function to send the results to.
365 | * @return An object containing the results (if any).
366 | * Object structure:
367 | * n: (array) An array of verse IDs for each verse returned
368 | * v: (array) An array of strings containing the HTML of the verses
369 | * t: (number) The total number of verses found in the search, not the total sent back, only present on intial queries
370 | */
371 | BF.standard_search = function (data, callback)
372 | {
373 | var db_client,
374 | html_table,
375 | initial,
376 | /// Select the language object specified by the query or use the default.
377 | lang = BF.langs[data.l] || BF.langs.en,
378 | query,
379 | start_at = data.s ? Number(data.s) : 0,
380 | terms = String(data.q),
381 | verse_table;
382 |
383 | html_table = "`bible_" + lang.id + "_html`";
384 | verse_table = "`verse_text_" + lang.id + "`";
385 |
386 | ///NOTE: Currently, the first query does not specifiy a verse.
387 | initial = !Boolean(start_at);
388 |
389 | ///TODO: Requery if no client is returned.
390 | db_client = BF.db.request_a_client();
391 |
392 | /// Create the first part of the SQL/SphinxQL query.
393 | query = "SELECT " + verse_table + ".id, " + html_table + ".words FROM " + verse_table + ", " + html_table + " WHERE " + html_table + ".id = " + verse_table + ".id AND " + verse_table + ".query = \"" + db_client.escape_sphinx(terms) + ";limit=" + lang.minimum_desired_verses + ";ranker=none";
394 |
395 | /// Should the query start somewhere in the middle of the Bible?
396 | if (start_at) {
397 | ///NOTE: By keeping all of the settings in the Sphinx query, Sphinx can preform the best optimizations.
398 | /// Another, less optimized, approach would be to use the database itself to filter the results like this:
399 | /// ...WHERE id >= start_at AND query="...;limit=9999999" LIMIT lang.minimum_desired_verses
400 | query += ";minid=" + start_at;
401 | }
402 |
403 | /// Determine the search mode.
404 | /// Default is SPH_MATCH_ALL (i.e., all words are required: word1 & word2).
405 | /// SPH_MATCH_ALL should be the fastest and needs no sorting.
406 |
407 | /// Is there more than one word?
408 | if (terms.indexOf(" ") >= 0) {
409 | /// Are there more than 10 search terms in the query, or does the query contains double quotes (")?
410 | ///NOTE: Could use the more accurate (/([a-z-]+[^a-z-]+){11}/.test(terms)) to find word count, but it is slower.
411 | if (terms.indexOf("\"") >= 0 || terms.split(" ").length > 9) {
412 | /// By default, other modes stop at 10, but SPH_MATCH_EXTENDED does more (256?).
413 | /// Phrases (words in quotes) require SPH_MATCH_EXTENDED mode.
414 | ///NOTE: SPH_MATCH_BOOLEAN is supposed to find more than 10 words too but doesn't seem to.
415 | /// mode=extended is the most complex (and slowest?).
416 | /// Since we want the verses in canonical order, we need to sort the results by id, not based on weight.
417 | query += ";mode=extended;sort=extended:@id asc";
418 | /// Are boolean operators present?
419 | ///NOTE: This detects all ampersands (&), all pipes (|), and hyphens (-) only at the beginning of the string (e.g., "-word1 word2") or after a space (e.g., "word1 -word2").
420 | /// The reason why only some hyphens are detected is that hyphens are only special symbols in certain positions. If a hyphen separates two words (e.g., " Baal-peor"), it is not a special symbol.
421 | } else if (/(?:(?:^| )-|&|\|)/.test(terms)) {
422 | /// Set mode to boolean and order by id.
423 | query += ";mode=boolean;sort=extended:@id asc";
424 | /// Multiple words are being searched for but nothing else special.
425 | } else {
426 | /// Just order by id.
427 | query += ";sort=extended:@id asc";
428 | }
429 | }
430 |
431 | if (initial) {
432 | /// Initial queries need to calculate the total verse.
433 | ///NOTE: SphinxSE does not return statistics by default, but we can retrieve them by running another query immediately after the first
434 | /// on the INFORMATION_SCHEMA.SESSION_STATUS table and UNION'ing it to the first.
435 | /// The only draw back to this is that both queries must have the same number of columns.
436 | /// Other ways to get the statistics is with the the following queries:
437 | /// SHOW ENGINE SPHINX STATUS;
438 | /// +--------+-------+-------------------------------------------------+
439 | /// | Type | Name | Status |
440 | /// +--------+-------+-------------------------------------------------+
441 | /// | SPHINX | stats | total: 421, total found: 421, time: 1, words: 1 |
442 | /// | SPHINX | words | love:421:498 |
443 | /// +--------+-------+-------------------------------------------------+
444 | ///
445 | /// SHOW STATUS LIKE 'sphinx_%';
446 | /// +--------------------------------+--------------+
447 | /// | Variable_name | Value |
448 | /// +--------------------------------+--------------+
449 | /// | sphinx_error_commits | 0 |
450 | /// | sphinx_error_group_commits | 0 |
451 | /// | sphinx_error_snapshot_file | |
452 | /// | sphinx_error_snapshot_position | 0 |
453 | /// | sphinx_time | 1 |
454 | /// | sphinx_total | 421 |
455 | /// | sphinx_total_found | 421 |
456 | /// | sphinx_word_count | 1 |
457 | /// | sphinx_words | love:421:498 |
458 | /// +--------------------------------+--------------+
459 | ///
460 | /// However, because these queries are SHOW queries and not SELECT queries, they must be executed after the initial SELECT query.
461 | ///
462 | ///NOTE: The first column is currently ignored.
463 | query += "\" UNION SELECT 0, VARIABLE_VALUE FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME = 'sphinx_total_found'";
464 | } else {
465 | query += "\"";
466 | }
467 |
468 | /// Run the Sphinx search and return both the verse IDs and the HTML.
469 | db_client.query(query, function (data, err)
470 | {
471 | var i,
472 | len,
473 | res = {
474 | n: [],
475 | v: []
476 | };
477 |
478 | /// Was there no response (or an invalid response) from the database? This could mean the database or Sphinx crashed.
479 | ///NOTE: If merely the length is 0, that means there were no results for that query, and we need to send an empty result with the proper response and not attempt to log it as an error.
480 | if (!data) {
481 | ///TODO: Do better logging,
482 | if (err) {
483 | console.log(err);
484 | }
485 | /// Send an empty response, and exit.
486 | callback({});
487 | return;
488 | }
489 |
490 | if (initial) {
491 | if (data.length > 0) {
492 | /// Because all of the columns share the same name when using UNION, the total verses found statistic is in the "words" column.
493 | res.t = Number(data.pop().words);
494 | } else {
495 | res.t = 0;
496 | }
497 | }
498 |
499 | len = data.length;
500 |
501 | for (i = 0; i < len; i += 1) {
502 | res.n[i] = Number(data[i].id);
503 | res.v[i] = data[i].words;
504 | }
505 |
506 | callback(res);
507 | });
508 | };
509 |
510 | /**
511 | * Preform a grammatical (morphological) search of the Bible.
512 | *
513 | * @example BF.standard_search({q: '["love",[[3,1]],[0]]'}, function (data) {}); /// Preform a search for the word "love" when spoken by Jesus (i.e., red letter).
514 | * @example BF.standard_search({q: '["love",[[3,1]],[0]]', s: "704772"}, function (data) {}); /// Preform a search for the word "love" when spoken by Jesus (i.e., red letter) starting from the 704772th word (in English).
515 | * @example BF.standard_search({q: '["love",[[3,1],[6,3],[5,2]],[1,0,0]]'}, function (data) {}); /// Preform a search for the word "love" when not spoken by Jesus (i.e., black letter) and that is in the third person plural.
516 | * @param data (object) An object containing the query and query options.
517 | * Object structure:
518 | * q: "A JSON encoed string defining an array of search terms and grammatical properties.",
519 | * JSON structure:
520 | * ["WORD", [[(number) GRAMMATICAL_CATEGORY, (number) ATTRIBUTE], ...],[(boolean) EXCLUDE, ...]]
521 | * l: "The language ID" (optional) (default: "en")
522 | * s: "The word ID (in the translated language) from which to start the search" (optional) (default: 0)
523 | * @param callback (function) The function to send the results to.
524 | * @return An object containing the results (if any).
525 | * Object structure:
526 | * i: (array) An array of word IDs for each highlighted word
527 | * n: (array) An array of verse IDs for each verse returned
528 | * v: (array) An array of strings containing the HTML of the verses
529 | * t: (number) The total number of verses found in the search, not the total sent back, only present on intial queries
530 | * @note Grammatical searches examine the morphology of the original languages, not translations.
531 | * @note The data.s property refers to the word ID, not the verse ID.
532 | */
533 | BF.grammatical_search = function (data, callback)
534 | {
535 | var db_client,
536 | html_table,
537 | i,
538 | initial,
539 | /// Select the language object specified by the query or use the default.
540 | lang = BF.langs[data.l] || BF.langs.en,
541 | morphological_table,
542 | query,
543 | start_at = data.s ? Number(data.s) : 0,
544 | ///TODO: Make this an object instead.
545 | query_arr = BF.parse_json(data.q);
546 |
547 | /// If query_arr is not an array, return an empty result.
548 | /// If the first element is not a string or the second or third element is not an array, return an empty result.
549 | ///NOTE: This assumes only one grammatical word being searched for, which is currently the limit.
550 | if (!(query_arr instanceof Array) || typeof query_arr[0] !== "string" || !(query_arr[1] instanceof Array) || !(query_arr[2] instanceof Array)) {
551 | callback({});
552 | return;
553 | }
554 |
555 | ///TODO: Requery if no client is returned.
556 | db_client = BF.db.request_a_client();
557 |
558 | html_table = "`bible_" + lang.id + "_html`";
559 | morphological_table = "`morphological_" + lang.id + "`";
560 | ///NOTE: Currently, the first query does not specifiy a verse.
561 | initial = !Boolean(start_at);
562 |
563 | /// Create the first part of the SQL/SphinxQL query.
564 | query = "SELECT " + morphological_table + ".id, " + morphological_table + ".verseID, " + html_table + ".words FROM " + morphological_table + ", " + html_table + " WHERE " + html_table + ".id = " + morphological_table + ".verseID AND " + morphological_table + ".query = \"" + db_client.escape_sphinx(query_arr[0]) + ";limit=" + lang.minimum_desired_verses + ";ranker=none";
565 |
566 | /// Should the query start somewhere in the middle of the Bible?
567 | if (start_at) {
568 | ///NOTE: By keeping all of the settings in the Sphinx query, Sphinx can preform the best optimizations.
569 | /// Another less optimized approach would be to use the database itself to filter the results like this:
570 | /// ...WHERE id >= start_at AND query="...;limit=9999999" LIMIT lang.minimum_desired_verses
571 | query += ";minid=" + start_at;
572 | }
573 |
574 | /// Create the filter from the query.
575 | /// Examples:
576 | /// ... AS RED => ["...", [[3, 1]], [0]] => "...;filter=red,1"
577 | /// ... AS NOT RED => ["...", [[3, 1]], [1]] => "...;!filter=red,1"
578 | /// ... AS NOT RED, THIRD_PERSON, PLURAL => ["...", [[3, 1], [6, 3], [5, 2]],[1, 0, 0]] => "...;filter=number,2;filter=person,3;!filter=red,1"
579 | ///NOTE: The filters are added in reverse order simply to make the loop simpler.
580 | for (i = query_arr[1].length - 1; i >= 0; i -= 1) {
581 | query += ";" + (query_arr[2][i] ? "!" : "") + "filter=" + lang.grammar_categories[query_arr[1][i][0]] + "," + query_arr[1][i][1];
582 | }
583 |
584 | if (initial) {
585 | /// Initial queries need to calculate the total verse.
586 | ///NOTE: SphinxSE does not return statistics by default, but we can retrieve them by running another query immediately after the first
587 | /// on the INFORMATION_SCHEMA.SESSION_STATUS table and UNION'ing it to the first.
588 | /// The only draw back to this is that both queries must have the same number of columns.
589 | /// Other ways to get the statistics is with the the following queries:
590 | /// SHOW ENGINE SPHINX STATUS;
591 | /// +--------+-------+-------------------------------------------------+
592 | /// | Type | Name | Status |
593 | /// +--------+-------+-------------------------------------------------+
594 | /// | SPHINX | stats | total: 421, total found: 421, time: 1, words: 1 |
595 | /// | SPHINX | words | love:421:498 |
596 | /// +--------+-------+-------------------------------------------------+
597 | ///
598 | /// SHOW STATUS LIKE 'sphinx_%';
599 | /// +--------------------------------+--------------+
600 | /// | Variable_name | Value |
601 | /// +--------------------------------+--------------+
602 | /// | sphinx_error_commits | 0 |
603 | /// | sphinx_error_group_commits | 0 |
604 | /// | sphinx_error_snapshot_file | |
605 | /// | sphinx_error_snapshot_position | 0 |
606 | /// | sphinx_time | 1 |
607 | /// | sphinx_total | 421 |
608 | /// | sphinx_total_found | 421 |
609 | /// | sphinx_word_count | 1 |
610 | /// | sphinx_words | love:421:498 |
611 | /// +--------------------------------+--------------+
612 | ///
613 | /// However, because these queries are SHOW queries and not SELECT queries, they must be executed after the initial SELECT query.
614 | ///
615 | ///NOTE: The first two columns are currently ignored.
616 | query += "\" UNION SELECT 0, 0, VARIABLE_VALUE FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME = 'sphinx_total_found'";
617 | } else {
618 | query += "\"";
619 | }
620 |
621 | /// Run the Sphinx search, and return both the verse IDs and the HTML.
622 | db_client.query(query, function (data)
623 | {
624 | var i,
625 | len,
626 | res = {
627 | i: [],
628 | n: [],
629 | v: []
630 | },
631 | verse_count = 0;
632 |
633 | /// Was there no response from the database? This could mean the database or Sphinx crashed.
634 | if (!data) {
635 | /// Send an empty response, and exit.
636 | callback({});
637 | return;
638 | }
639 |
640 | if (initial) {
641 | /// Because all of the columns share the same name when using UNION, the total verses found statistic is in the "words" column.
642 | res.t = Number(data.pop().words);
643 | }
644 |
645 | len = data.length;
646 |
647 | for (i = 0; i < len; i += 1) {
648 | res.i[i] = Number(data[i].id);
649 | /// Because Sphinx is searching at the word level, it might return multiple verses, so only add non-duplicate verses.
650 | if (res.n[verse_count - 1] !== Number(data[i].verseID)) {
651 | res.n[verse_count] = Number(data[i].verseID);
652 | res.v[verse_count] = data[i].words;
653 | verse_count += 1;
654 | }
655 | }
656 |
657 | callback(res);
658 | });
659 | };
660 |
661 | /**
662 | * Retrieve lexical data from the database.
663 | *
664 | * @example BF.standard_search({q: "1"}, function (data) {}); /// Get the lexical data for the first word in the english Bible.
665 | * @param data (object) An object containing the query and query options.
666 | * Object structure:
667 | * q: "The word ID (in the translated language)",
668 | * l: "The language ID" (optional) (default: "en")
669 | * @param callback (function) The function to send the results to.
670 | * @return An object containing the results (if any).
671 | * Object structure:
672 | * word (string) The original Greek, Hebrew, or Aramaic word, in Unicode.
673 | * pronun (string) A JSON string containing the pronunciation of the word (same as data.pronun below except for the actual word, not the base form).
674 | * strongs (integer) The designated Strong's number for that word.
675 | * base_word (string) The original Greek, Hebrew, or Aramaic base form of the word, in Unicode.
676 | * data (string) A JSON object containing the lexical information about the word.
677 | * Object structure:
678 | * def: {lit: "The literal definition of a word (especially a name)",
679 | * long: ["A multi-dimensional array of various possible definitions"],
680 | * short: "A short and simple definition"}
681 | * deriv: "Information about words this word is derived from",
682 | * pronun: {ipa: "IPA Biblical reconstructed pronunciation (base form)",
683 | * ipa_mod: "IPA modern pronunciation (base form)",
684 | * dic: "Dictionary Biblical reconstructed pronunciation (base form)",
685 | * dic_mod: "Dictionary modern pronunciation (base form)",
686 | * sbl: "The Society of Biblical Literature's phonemic transliteration"}
687 | * see: ["An array of Strong's numbers identifying additional words of interest."]
688 | * comment: "A string containing additional useful information"
689 | * usage (string) A list of ways the word is translated.
690 | * @note The usage data is planned to be completely redone and expanded.
691 | * @note Also, any number of the following:
692 | * part_of_speech, declinability, case_5, number, gender, degree, tense, voice, mood, person, middle, transitivity, miscellaneous, noun_type, numerical, form, dialect, type, pronoun_type
693 | */
694 | BF.lexical_lookup = function (data, callback)
695 | {
696 | /// Select the language object specified by the query or use the default.
697 | var bible_table,
698 | lang = BF.langs[data.l] || BF.langs.en,
699 | query,
700 | word_id = Number(data.q);
701 |
702 | bible_table = "`bible_" + lang.id + "`";
703 |
704 | /// Is it an Old Testament word?
705 | if (word_id < lang.divisions.nt) {
706 | query = "SELECT `bible_original`.word, `bible_original`.pronun, `lexicon_hebrew`.strongs, `lexicon_hebrew`.base_word, `lexicon_hebrew`.data, `lexicon_hebrew`.usage FROM " + bible_table + ", `bible_original`, `lexicon_hebrew`, `morphology` WHERE " + bible_table + ".id = " + word_id + " AND `bible_original`.id = " + bible_table + ".orig_id AND lexicon_hebrew.strongs = `bible_original`.strongs LIMIT 1";
707 | } else {
708 | query = "SELECT `bible_original`.word, `bible_original`.pronun, `lexicon_greek`.strongs, `lexicon_greek`.base_word, `lexicon_greek`.data, `lexicon_greek`.usage, `morphology`.part_of_speech, `morphology`.declinability, `morphology`.case_5, `morphology`.number, `morphology`.gender, `morphology`.degree, `morphology`.tense, `morphology`.voice, `morphology`.mood, `morphology`.person, `morphology`.middle, `morphology`.transitivity, `morphology`.miscellaneous, `morphology`.noun_type, `morphology`.numerical, `morphology`.form, `morphology`.dialect, `morphology`.type, `morphology`.pronoun_type FROM " + bible_table + ", `bible_original`, `lexicon_greek`, `morphology` WHERE " + bible_table + ".id = " + word_id + " AND `bible_original`.id = " + bible_table + ".orig_id AND lexicon_greek.strongs = `bible_original`.strongs AND `morphology`.id = `bible_original`.id LIMIT 1";
709 | }
710 |
711 | ///FIXME: Currently, BibleForge links words to the lexicon by Strong's numbers; however, this is too simplistic because some Strong's numbers have multiple entries.
712 | /// So, there needs to be another identifier.
713 | BF.db.query(query, function (data)
714 | {
715 | /// Was there no response from the database? This could mean the database crashed.
716 | if (!data || !data.length) {
717 | /// Send an empty response, and exit.
718 | callback({});
719 | return;
720 | }
721 |
722 | ///NOTE: Currently, only one results is requested, so it can simply send data[0].
723 | /// In the future, it should return multiple results for some words (e.g., hyphenated words, phrases translated as one word).
724 |
725 | callback(data[0]);
726 | });
727 | };
728 |
729 |
730 | /// *******************************
731 | /// * Prepare to start the server *
732 | /// *******************************
733 |
734 | /**
735 | * Load the language specific files and start the server.
736 | */
737 | (function ()
738 | {
739 | ///NOTE: Since the server cannot start until this is done, async only slows things down.
740 | var files = require("fs").readdirSync(BF.config.static_path + "js/lang"),
741 | i,
742 | id,
743 | lang;
744 |
745 | /// Pepare the langs object for the languages to attach to.
746 | BF.langs = {};
747 |
748 | for (i = files.length - 1; i >= 0; i -= 1) {
749 | lang = require(BF.config.static_path + "js/lang/" + files[i]).BF.langs;
750 | ///NOTE: Object.keys() ignores prototypes, so there is no need for hasOwnProperty().
751 | id = Object.keys(lang)[0];
752 | BF.langs[id] = lang[id];
753 | }
754 |
755 | /**
756 | * Listen for HTTP forwarded requests.
757 | */
758 | (function start_server()
759 | {
760 | /**
761 | * Create a closure to handle queries from the client.
762 | *
763 | * @return A function to handle queries.
764 | */
765 | var handle_query = (function ()
766 | {
767 | /**
768 | * Create a closure to house the code to produce the non-JavaScript version.
769 | *
770 | * @return A function to create the HTML for the non-JavaScript version.
771 | */
772 | var create_simple_page = (function ()
773 | {
774 | /**
775 | * Create a closure to get the base HTML of the non-JavaScript code.
776 | *
777 | * @return A function to get the HTML for the non-JavaScript version.
778 | */
779 | var get_simple_html = (function ()
780 | {
781 | /// Prepare a variable in the closure to cache the results.
782 | var html;
783 |
784 | /**
785 | * Get the base HTML of the non-JavaScript code.
786 | *
787 | * @param callback (function) The function to send the HTML back to.
788 | * @return NULL
789 | * @note The callback function could be called synchronously or asynchronously.
790 | */
791 | return function get_simple_html(callback)
792 | {
793 | /// Has the HTML already been cached?
794 | if (html) {
795 | callback(html);
796 | } else {
797 | /// Asynchronously read the file.
798 | require("fs").readFile(__dirname + "/index_non-js.html", "utf8", function (err, data)
799 | {
800 | if (err) {
801 | ///TODO: Log errors.
802 | console.error(err);
803 | }
804 | /// Is BibleForge configued to cache the results?
805 | ///NOTE: Production servers should use the cache.
806 | if (BF.config.cache_simple_html) {
807 | /// Optionally, cache the HTML in the closure.
808 | html = data;
809 | }
810 |
811 | callback(data);
812 | });
813 | }
814 | };
815 | }());
816 |
817 | /**
818 | * Create the non-JavaScript version and send the results to the client.
819 | *
820 | * @param url (string) The URL from which to create the query.
821 | * @param data (object) The GET/POST data as an object.
822 | * @param connection (object) The connection object though which data may be sent back to the client.
823 | */
824 | return function create_simple_page(url, data, connection, info)
825 | {
826 | /// Because the URI starts with a slash (/), the first array element is empty.
827 | var full_featured_uri,
828 | lang,
829 | query,
830 | /// Separate the URL to possibly obtain the language and query.
831 | ///NOTE: Three matches are possibly returned because the leading slash (/) counts as one black result.
832 | /// I.e., "/en/love/".split("/", 3) returns ["", "en", "love"].
833 | query_arr = url.path.split("/", 3);
834 |
835 | /// First, parse the query array for valid langauges and searches.
836 | /// Example queries:
837 | /// /!
838 | /// /en/!
839 | /// /en/love/!
840 | /// /en_em/Romans 3:16/!
841 | /// /love/!
842 | /// /Romans 3:16/!
843 |
844 | /// Is the first parameter a valid language ID?
845 | if (BF.langs[query_arr[1]]) {
846 | /// Example queries:
847 | /// /en/!
848 | /// /en/love/!
849 | lang = BF.langs[query_arr[1]];
850 | /// Is the second parameter a query?
851 | ///NOTE: If the last parameter is simply a question mark, it is not a valid query and indicates the switch to the non-JavaScript version.
852 | if (query_arr[2] && query_arr[2] !== "!") {
853 | /// Example query: /en/love/!
854 | query = query_arr[2];
855 | }
856 | } else {
857 | /// Since there was no language specified, use the default language.
858 | ///TODO: Determine how to determine the default language.
859 | lang = BF.langs.en;
860 | /// Since the first parameter was not a language ID, the first parameter should be the query (if present).
861 | /// Is the first parameter a query?
862 | ///NOTE: If the first parameter not a valid language ID, the first parameter is treated as the query and any other parameters are discarded.
863 | ///NOTE: If the last parameter is simply a question mark, it is not a valid query and indicates the switch to the non-JavaScript version.
864 | if (query_arr[1] && query_arr[1] !== "!") {
865 | /// Example query: /love/!
866 | query = query_arr[1];
867 | }
868 | }
869 |
870 | /// Was there no query specified in the URL?
871 | /// Example queries:
872 | /// /!
873 | /// /en/!
874 | if (query === undefined || query === "") {
875 | /// Get the queried language.
876 | if (data && data.l && BF.langs[data.l]) {
877 | lang = BF.langs[data.l];
878 | }
879 | /// Was there a query specified in the GET data?
880 | ///NOTE: For example, this will occur when submitting a query from the query box in the non-JavaScript version.
881 | /// Example query: /en/!?q=love
882 | if (data && data.q) {
883 | query = data.q;
884 | } else {
885 | /// If there is no query present, then preform a verse lookup starting at the beginning of the Bible (e.g., Genesis 1:1).
886 | query = lang.books_short[1] + " 1:1";
887 | }
888 | } else {
889 | /// Convert special symbols to normal ones (e.g., "%26" becomes "&").
890 | query = global.decodeURIComponent(query);
891 | }
892 |
893 | /// Create the URL to the full-featured page, used to possibly redirect proper browsers to.
894 | ///NOTE: A scenario where this could be used is if someone using the non-JavaScript version sends a link to someone with a browser capable of handing the full-featured page.
895 | ///NOTE: Both the leading and trailing slashes (/) are necessary.
896 | full_featured_uri = "/" + lang.id + "/" + global.encodeURIComponent(query) + "/";
897 |
898 | /// If a query string is present, redirect it to the correct URL and cloes the connection.
899 | ///TODO: Check for the presence of both the exclamation point (!) and _escaped_fragment_ and redirect to a page without the exclamation point.
900 | ///TODO: Retrieve any query in the _escaped_fragment_ variable.
901 | if (data && (data.q || data.l)) {
902 | connection.writeHead(301, {"Location": "http" + (BF.config.use_ssl ? "s" : "") + "://" + url.host + (Number(url.port) !== 80 ? ":" + url.port : "") + full_featured_uri + "!"});
903 | connection.end();
904 | return;
905 | }
906 |
907 | /// Now that we know the request will not be redirected, send the OK status code and appropriate header.
908 | connection.writeHead(200, {"Content-Type": "text/html"});
909 |
910 | /**
911 | * Create the page based on the retrieved HTML and send it to the client.
912 | *
913 | * @param html (string) The HTML of the non-JavaScript version.
914 | * @note The callback function could be called synchronously or asynchronously.
915 | */
916 | get_simple_html(function (html)
917 | {
918 | var b,
919 | c,
920 | content = {},
921 | lang_css_html = "", /// If there is no language specific CSS, a blank string is needed.
922 | lang_select = "