├── .gitignore
├── .nojekyll
├── CNAME
├── LICENSE.md
├── README.md
├── bundle.js
├── css
├── font.css
└── kjs.css
├── favicon.png
├── font
├── courgette-v18-latin-regular.woff2
├── inconsolata-v36-latin-regular.woff2
├── merienda-v21-latin-regular.woff2
├── merriweather-v32-latin-regular.woff2
├── noto-serif-hebrew-v29-latin-regular.woff2
├── open-sans-v43-latin-regular.woff2
├── roboto-mono-v30-latin-regular.woff2
├── roboto-slab-v35-latin-regular.woff2
└── roboto-v48-latin-regular.woff2
├── help
├── about.html
├── bookmark.html
├── clipboard-mode.html
├── help.html
├── name-mode.html
├── navigator.html
├── overview.html
├── read.html
├── search.html
├── setting.html
└── strong.html
├── icons.svg
├── index.html
├── js
├── CommandQueue.js
├── Controller
│ ├── BookmarkController.js
│ ├── HelpController.js
│ ├── NavigatorController.js
│ ├── ReadController.js
│ ├── SearchController.js
│ ├── SettingController.js
│ └── StrongController.js
├── Model
│ ├── BookmarkModel.js
│ ├── DbModel.js
│ ├── HelpModel.js
│ ├── NavigatorModel.js
│ ├── ReadModel.js
│ ├── SearchModel.js
│ ├── SettingModel.js
│ └── StrongModel.js
├── SearchEngine.js
├── View
│ ├── BookmarkExportView.js
│ ├── BookmarkFolderAddView.js
│ ├── BookmarkFolderDeleteView.js
│ ├── BookmarkFolderRenameView.js
│ ├── BookmarkFolderView.js
│ ├── BookmarkImportView.js
│ ├── BookmarkListView.js
│ ├── BookmarkMoveCopyView.js
│ ├── HelpReadView.js
│ ├── HelpTopicView.js
│ ├── NavigatorBookView.js
│ ├── NavigatorChapterView.js
│ ├── ReadView.js
│ ├── SearchFilterView.js
│ ├── SearchHistoryView.js
│ ├── SearchLookupView.js
│ ├── SearchResultView.js
│ ├── SettingView.js
│ ├── StrongDefView.js
│ ├── StrongFilterView.js
│ ├── StrongHistoryView.js
│ ├── StrongLookupView.js
│ ├── StrongResultView.js
│ └── StrongVerseView.js
├── app.js
├── data
│ ├── binIdx.js
│ ├── dbUtil.js
│ ├── kjvNameDb.js
│ ├── kjvPureDb.js
│ ├── strongDictDb.js
│ ├── strongIdx.js
│ ├── strongNameDb.js
│ ├── strongPureDb.js
│ ├── tomeIdx.js
│ └── tomeLists.js
├── lib
│ └── dexie.v4.0.4.min.mjs
├── load.js
├── progress.js
├── template.js
└── util.js
├── json
├── kjv_lists.json
├── kjv_name.json
├── kjv_pure.json
├── strong_dict.json
├── strong_name.json
└── strong_pure.json
├── manifest.json
├── png
├── icon-032.png
├── icon-192.png
├── icon-512.png
├── maskable-icon-192.png
└── maskable-icon-512.png
├── robots.txt
├── rollup.config.mjs
├── sw.js
└── webp
├── android.webp
└── desktop.webp
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gitignore
2 | /.nojekyll
3 | /.vscode
4 | /ref
5 | /seTest
6 | /eslint.config.js
7 |
8 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/.nojekyll
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | kjs.1-john-419.org
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | Copyright (c) 2017-2022 Clayton Carney
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
6 |
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8 |
9 | You may obtain a copy of the GNU General Public License from .
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bible App
2 |
3 | KJS - King James with Strong
4 |
5 | ## Features:
6 |
7 | * Free, open source, offline-first, and mobile-first.
8 | * Once downloaded, an internet connection is not required.
9 | * Supports the latest version of a Chromium-based browser on Android, Linux, or Windows devices.
10 | * Supports the latest version of the Safari browser on Mac/iOS/iPhone devices.
11 | * Easy Navigation
12 | * Bookmarks:
13 | * Organize by folders
14 | * Import and export
15 | * Search:
16 | * Word or phrase queries
17 | * Wildcards and case sensitivity
18 | * Filter by book/chapter
19 | * Strong:
20 | * Hebrew and Greek dictionaries
21 | * Word summaries
22 | * Customizable Interface:
23 | * Font face/size
24 | * Light and dark app themes
25 | * Help System
26 | * Same User Interface:
27 | * Desktop
28 | * Laptop
29 | * Tablet
30 | * Phone
31 |
32 | ## Download
33 |
34 | *[KJS](https://kjs.1-john-419.org/)*
35 |
36 | ## Contact
37 |
38 | email *[1John419](mailto:github.1john419@gmail.com)*
39 |
40 | ## Website
41 |
42 | *[1-john419.org](https://1-john-419.org/)*
43 |
--------------------------------------------------------------------------------
/css/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-display: swap;
3 | font-family: 'Roboto';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: url('/font/roboto-v48-latin-regular.woff2') format('woff2');
7 | }
8 |
9 | .font--roboto {
10 | font-family: 'Roboto', sans-serif;
11 | }
12 |
13 | @font-face {
14 | font-display: swap;
15 | font-family: 'Open Sans';
16 | font-style: normal;
17 | font-weight: 400;
18 | src: url('/font/open-sans-v43-latin-regular.woff2') format('woff2');
19 | }
20 |
21 | .font--open-sans {
22 | font-family: 'Open Sans', sans-serif;
23 | }
24 |
25 | @font-face {
26 | font-display: swap;
27 | font-family: 'Roboto Slab';
28 | font-style: normal;
29 | font-weight: 400;
30 | src: url('/font/roboto-slab-v35-latin-regular.woff2') format('woff2');
31 | }
32 |
33 | .font--roboto-slab {
34 | font-family: 'Roboto Slab', serif;
35 | }
36 |
37 | @font-face {
38 | font-display: swap;
39 | font-family: 'Merriweather';
40 | font-style: normal;
41 | font-weight: 400;
42 | src: url('/font/merriweather-v32-latin-regular.woff2') format('woff2');
43 | }
44 |
45 | .font--merriweather {
46 | font-family: 'Merriweather', serif;
47 | }
48 |
49 | @font-face {
50 | font-display: swap;
51 | font-family: 'Courgette';
52 | font-style: normal;
53 | font-weight: 400;
54 | src: url('/font/courgette-v18-latin-regular.woff2') format('woff2');
55 | }
56 |
57 | .font--courgette {
58 | font-family: 'Courgette', cursive;
59 | }
60 |
61 | @font-face {
62 | font-display: swap;
63 | font-family: 'Merienda';
64 | font-style: normal;
65 | font-weight: 400;
66 | src: url('/font/merienda-v21-latin-regular.woff2') format('woff2');
67 | }
68 |
69 | .font--merienda {
70 | font-family: 'Mereanda', cursive;
71 | }
72 |
73 | @font-face {
74 | font-display: swap;
75 | font-family: 'Roboto Mono';
76 | font-style: normal;
77 | font-weight: 400;
78 | src: url('/font/roboto-mono-v30-latin-regular.woff2') format('woff2');
79 | }
80 |
81 | .font--roboto-mono {
82 | font-family: 'Roboto Mono', monospace;
83 | }
84 |
85 | @font-face {
86 | font-display: swap;
87 | font-family: 'Inconsolata';
88 | font-style: normal;
89 | font-weight: 400;
90 | src: url('/font/inconsolata-v36-latin-regular.woff2') format('woff2');
91 | }
92 |
93 | .font--inconsolata {
94 | font-family: 'Inconsolata', monospace;
95 | }
96 |
97 | @font-face {
98 | font-display: swap;
99 | font-family: 'Noto Serif Hebrew';
100 | font-style: normal;
101 | font-weight: 400;
102 | src: url('/font/noto-serif-hebrew-v29-latin-regular.woff2') format('woff2');
103 | }
104 |
105 | .font--hebrew {
106 | font-family: 'Noto Serif Hebrew', serif;
107 | font-size: 1.5em;
108 | }
109 |
110 | .font-size--s {
111 | font-size: 0.8rem;
112 | }
113 |
114 | .font-size--m {
115 | font-size: 1.0rem;
116 | }
117 |
118 | .font-size--l {
119 | font-size: 1.2rem;
120 | }
121 |
122 | .font-size--xl {
123 | font-size: 1.4rem;
124 | }
125 |
126 | .font-size--xxl {
127 | font-size: 1.6rem;
128 | }
129 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/favicon.png
--------------------------------------------------------------------------------
/font/courgette-v18-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/courgette-v18-latin-regular.woff2
--------------------------------------------------------------------------------
/font/inconsolata-v36-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/inconsolata-v36-latin-regular.woff2
--------------------------------------------------------------------------------
/font/merienda-v21-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/merienda-v21-latin-regular.woff2
--------------------------------------------------------------------------------
/font/merriweather-v32-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/merriweather-v32-latin-regular.woff2
--------------------------------------------------------------------------------
/font/noto-serif-hebrew-v29-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/noto-serif-hebrew-v29-latin-regular.woff2
--------------------------------------------------------------------------------
/font/open-sans-v43-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/open-sans-v43-latin-regular.woff2
--------------------------------------------------------------------------------
/font/roboto-mono-v30-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/roboto-mono-v30-latin-regular.woff2
--------------------------------------------------------------------------------
/font/roboto-slab-v35-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/roboto-slab-v35-latin-regular.woff2
--------------------------------------------------------------------------------
/font/roboto-v48-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/font/roboto-v48-latin-regular.woff2
--------------------------------------------------------------------------------
/help/about.html:
--------------------------------------------------------------------------------
1 |
2 |
1 John 4:19
3 |
4 |
We love him,
5 |
because he first loved us.
6 |
7 |
Dedicated to our precious
8 |
Lord and Saviour,
9 |
Jesus Christ of Nazareth.
10 |
11 |
Bible App
12 |
KJS - King James with Strong
13 |
Release
14 |
2025.07.10
15 |
Contact
16 |
github.1john419@gmail.com
17 |
Source Code
18 |
github.com/1John419/kjs
19 |
Website
20 |
1-john-419.org
21 |
22 |
--------------------------------------------------------------------------------
/help/bookmark.html:
--------------------------------------------------------------------------------
1 |
2 |
Folders
3 |
Bookmarks may be organized into folders. An initial folder, named Default, is provided.
4 |
Folders allow you to save and organize verses from sermons, Bible studies, etc. Bookmarks may be moved and copied between folders.
5 |
Bookmarks may be imported and exported in text format (a Bookmark Package). This facilitates data backup and restore operations. It also allows sharing between different devices and users.
6 |
Bookmark Window
7 |
The Bookmark window is used to manage folders and bookmarks.
8 |
The Bookmark window has two task panes: List and Folder.
9 |
List Pane
10 |
The List pane displays the bookmark list for the Active Folder.
11 |
The upper toolbar displays the name of the Active Folder.
12 |
Tap a list entry to open the Read window at the selected verse.
13 |
The list is managed via Action Menus. Tap the Menu button to open the Action Menu for a bookmark. You may reorder, move/copy, or delete entries as desired.
14 |
Sorting
15 |
These buttons in the lower toolbar sort all bookmarks in the Active Folder:
16 |
17 |
Sort Ascending
18 |
19 |
20 | Sort Ascending
21 |
22 |
23 |
24 |
25 |
26 |
Sort Invert
27 |
28 |
29 | Sort Invert
30 |
31 |
32 |
33 |
34 |
Expand Mode
35 |
This toggle button in the lower toolbar sets the Expand Mode:
36 |
37 |
Expand Mode
38 |
39 |
40 | Expand Mode
41 |
42 |
43 |
44 |
45 |
46 |
When Expand Mode is active, verse text for each bookmark is displayed
47 |
Strong Mode
48 |
This toggle button in the lower toolbar sets the Strong Mode:
49 |
50 |
Strong Mode
51 |
52 |
53 | Strong Mode
54 |
55 |
56 |
57 |
58 |
59 |
When Strong Mode is on, tapping a list entry will open the Strong Verse pane for that verse.
60 |
Folder Pane
61 |
The Folder pane displays the bookmark folder list.
62 |
Tap a folder name to set the Active Folder and open the List pane.
63 |
The list is managed via Action Menus. Tap the Menu button to open the Action Menu for a folder. You may reorder, rename, or delete entries as desired.
64 |
65 |
The following auxiliary panes are available via the lower toolbar
66 |
67 |
Add Pane
68 |
Enter a folder name and tap the Save button, or press Enter, to add a new folder. The Active Folder is set to the new folder and the List pane is opened.
69 |
Import Pane
70 |
Copy and Paste the text from a Bookmark Package into the text area. Tap the Import button. Success or error is reported below.
71 |
Folders and bookmarks from the package are merged into existing bookmarks. Any duplicates are ignored.
72 |
Export Pane
73 |
A Bookmark Package is written to the text area. Select All and Copy the text.
74 |
The Bookmark Package may be pasted and saved in a text editor. Or it might be pasted into an email.
75 |
76 |
The following task panes are available via the Action Menu.
77 |
78 |
Rename Pane
79 |
Edit the folder name and tap the Save button, or press Enter, to rename the folder and return to the Folder pane.
80 |
Delete Pane
81 |
Tap the Delete button to confirm folder deletion and return to the Folder pane. The Active Folder is reset to the next available folder.
82 |
Move/Copy Pane
83 |
The Active Bookmark is displayed in the upper toolbar.
84 |
The Move/Copy pane displays a list of folders which do not contain the Active Bookmark.
85 |
Move/Copy operations for each folder are managed via Action Menus. Tap the Menu button to open the Action Menu. You may move or copy the bookmark to the selected folder.
86 |
87 |
--------------------------------------------------------------------------------
/help/clipboard-mode.html:
--------------------------------------------------------------------------------
1 |
2 |
Clipboard Mode
3 |
The Clipboard Mode can be activated in the Read, Bookmark List, Search Result, and Strong Result panes.
4 |
When Clipboard Mode is active, tapping a verse in the associated pane will copy the verse text to the system clipboard.
5 |
To activate Clipboard Mode, tap the pane banner in the upper toolbar. The banner is highlighted, indicating Clipboard Mode is active.
6 |
To deactivate Clipboard Mode, tap the banner in the upper toolbar again. The banner is dehighlighted, indicating Clipboard Mode is inactive.
7 |
Note: In the Bookmark List pane, Clipboard Mode is available only when the Expand Mode is active (see Bookmark help topic).
8 |
9 |
--------------------------------------------------------------------------------
/help/help.html:
--------------------------------------------------------------------------------
1 |
2 |
Help Window
3 |
The Help window is used to access the help system.
4 |
The Help window has two task panes: Read and Topic.
5 |
Read Pane
6 |
The Help pane displays the contents of the selected help topic.
7 |
Topic Pane
8 |
The Topic pane displays a list of available help topics.
9 |
Tap a name to select a topic and open the Read pane.
10 |
11 |
--------------------------------------------------------------------------------
/help/name-mode.html:
--------------------------------------------------------------------------------
1 |
2 |
Name Mode
3 |
This toggle button in the Read window lower toolbar sets the Name Mode:
4 |
5 |
Name Mode
6 |
7 |
8 | Name Mode
9 |
10 |
11 |
12 |
13 |
Purpose
14 |
When Name Mode is active, transliterations for the names/epithets of God in the Tanakh (Hebrew Text) are substituted for the original KJV text.
15 |
Scope
16 |
The substitutions provided in Name Mode are as follows:
17 |
18 |
19 |
Strong's Number
20 |
Transliteration
21 |
22 |
23 |
H136
24 |
Adonai
25 |
26 |
30 |
34 |
35 |
H430
36 |
Elohim
37 |
38 |
39 |
H433
40 |
Eloha
41 |
42 |
46 |
47 |
H3068 and H3069
48 |
Yahweh
49 |
50 |
51 |
H5945 and H5946
52 |
Elyon
53 |
54 |
55 |
H6635
56 |
Tzevaot
57 |
58 |
59 |
H7706
60 |
Shaddai
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/help/navigator.html:
--------------------------------------------------------------------------------
1 |
2 |
Navigator Window
3 |
The Navigator window is the primary means to set the Active Chapter.
4 |
The Navigator Window has two task panes: Book and Chapter.
5 |
Book Pane
6 |
The Book pane displays the KJV book list.
7 |
Books are divided into two groups: those given before the Cross of Jesus Christ (the Hebrew Texts) and those given after the Cross of Jesus Christ (the Greek Texts).
8 |
Book names are abbreviated to provide a compact format. The first three characters (excluding spaces) of the book name are used with following exceptions: JDG for Judges, SOL for Song of Solomon, and PHM for Philemon.
9 |
Tap a book name to set the Active Book. The Active Book is highlighted.
10 |
If the book has only one chapter, that chapter is selected automatically.
11 |
On smaller devices, the Chapter pane is opened to allow chapter selection.
12 |
On larger devices, the Chapter pane is opened in the Sidebar, and the first chapter is selected automatically. Select another chapter as desired.
13 |
Chapter Pane
14 |
The Chapter pane displays the chapter list for the Active Book.
15 |
Tap a chapter number to set the Active Chapter. The Active Chapter is highlighted. The Read pane is opened at the Active Chapter.
16 |
17 |
--------------------------------------------------------------------------------
/help/read.html:
--------------------------------------------------------------------------------
1 |
2 |
Read Window
3 |
The Read window is used to read the chapters of the KJV.
4 |
The Read window has a single task pane: Read.
5 |
Read Pane
6 |
The Read pane displays the verses of the Active Chapter.
7 |
The Active Chapter is set via the Read pane Previous/Next Chapter buttons or the Navigator, Bookmark, Search, or Strong Window.
8 |
Chapter Selection
9 |
These buttons in the upper toolbar provide rapid selection of nearby chapters:
10 |
11 |
Previous Chapter
12 |
13 |
14 | Previous Chapter
15 |
16 |
17 |
18 |
19 |
20 |
Next Chapter
21 |
22 |
23 | Next Chapter
24 |
25 |
26 |
27 |
28 |
29 |
For general chapter selection, use the Navigator Window.
30 |
Column Mode
31 |
Depending on Read window size, this toggle button in the lower toolbar sets the Column Mode:
32 |
33 |
Column Mode
34 |
35 |
36 | Column Mode
37 |
38 |
39 |
40 |
41 |
42 |
When Column Mode is on, verses are displayed in contiguous columns, similar to a printed Bible. Note that the page scrolls horizontally in Column Mode.
43 |
Bookmarks
44 |
Bookmarks in the Active Folder are highlighted on the Read pane.
45 |
The Active Folder is set via the Bookmark Window.
46 |
Tapping a verse will add/remove a bookmark in the Active Folder.
47 |
Strong Mode
48 |
This toggle button in the lower toolbar sets the Strong Mode:
49 |
50 |
Strong Mode
51 |
52 |
53 | Strong Mode
54 |
55 |
56 |
57 |
58 |
59 |
When Strong Mode is on, tapping a verse will open the Strong Verse pane for that verse.
60 |
61 |
--------------------------------------------------------------------------------
/help/search.html:
--------------------------------------------------------------------------------
1 |
2 |
Queries
3 |
Queries allow you to search the KJV. Queries are performed on individual verses.
4 |
Two query types are available: Word Query or Phrase Query.
5 |
Word Queries are specified by a single word or a comma separated list of words. Query results will list all verses which contain all those words.
6 |
For example, "forgive,sin" will find all verses that contain both "forgive" and "sin".
7 |
Phrase Queries are specified by a word phrase. Query results will list all verses contain that phrase.
8 |
For example, "son of man" will find all verses that contain the phrase "son of man".
9 |
Wildcards
10 |
Individual words in both Word and Phrase Queries may contain an optional wildcard character (*) to find variants of a word.
11 |
For example, "lord*" will find all occurrences of "lord", "lords", "lord's", "lordly", or "lordship".
12 |
Case Sensitivity
13 |
To perform a case sensitive search, prepend an at sign (@) to the Query.
14 |
For example, "@LORD*" will find all occurrences of "LORD" and "LORD'S", but not "lord", "lords", "lord's", "lordly", "lordship", "Lord", or "Lord's".
15 |
Search Window
16 |
The Search window is used perform queries and analyze results.
17 |
The Search window has two task panes: Lookup and Result.
18 |
Lookup Pane
19 |
The Lookup pane is used perform queries.
20 |
Enter a query in the input field. Tap the Search button, or press Enter, to set the Active Query.
21 |
The Active Filter is reset to Tome and the Result pane is opened.
22 |
Result Pane
23 |
The Result pane displays the verse list matching the Active Query and Active Filter.
24 |
The upper toolbar displays the Active Filter and Active Query.
25 |
Tap a verse to open the Read pane at that verse.
26 |
Verses are displayed in groups of fifty. Tap the Load More button to display the next group. Use the Filter pane to quickly reduce result set size.
27 |
Strong Mode
28 |
This toggle button sets the Strong Mode:
29 |
30 |
Strong Mode
31 |
32 |
33 | Strong Mode
34 |
35 |
36 |
37 |
38 |
39 |
When Strong Mode is on, tapping a verse will open the Strong Verse pane for that verse.
40 |
41 |
The following auxiliary panes are available via the lower toolbar
42 |
43 |
Filter Pane
44 |
The Filter pane displays a list of filters grouped by Tome, Book, or Chapter.
45 |
Summary information is presented in the form: tome/book/chapter (word count/verse count).
46 |
The upper toolbar displays the Active Query.
47 |
These buttons expand/collapse book entries:
48 |
49 |
Expand
50 |
51 |
52 | Expand
53 |
54 |
55 |
56 |
57 |
58 |
Collapse
59 |
60 |
61 | Collapse
62 |
63 |
64 |
65 |
66 |
67 |
Tap a filter entry to set the Active Filter and open the Result pane.
68 |
History Pane
69 |
The History pane displays a list of previous queries.
70 |
Tap an history entry to set the Active Query.
71 |
The Active Filter is reset to Tome and the Result pane is opened.
72 |
To remove an history entry, tap the Delete button:
73 |
74 |
Delete
75 |
76 |
77 | Delete
78 |
79 |
80 |
81 |
82 |
83 |
To remove all history entries, tap the Clear History button in the lower toolbar:
84 |
85 |
Clear History
86 |
87 |
88 | Clear History
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/help/setting.html:
--------------------------------------------------------------------------------
1 |
2 |
Setting Window
3 |
The Setting window is used select preferred application options.
4 |
The Setting window has one task pane: Setting
5 |
Setting Pane
6 |
The Setting pane displays available application options.
7 |
A sample of scripture text is displayed to reflect options selected.
8 |
These buttons allow selection of options:
9 |
10 |
Previous
11 |
12 |
13 | Previous
14 |
15 |
16 |
17 |
18 |
19 |
Next
20 |
21 |
22 | Next
23 |
24 |
25 |
26 |
27 |
33 |
34 |
Font settings apply only to the Read, Bookmark List (expanded mode), and Search Result panes. All other panes use the default system font settings.
35 |
Font
36 |
Several fonts are available for verse text. Tap the Previous/Next button to scroll through available fonts. The selected font name is displayed.
37 |
Font Size
38 |
Several font sizes are available for verse text. Tap a Font Size button to select the preferred size.
39 |
Font Variant
40 |
Tap a Font Variant button to select Normal or SmallCaps font style.
41 |
42 |
Theme
43 |
Several application color themes are available. Tap the Previous/Next button to scroll through available theme palettes. The selected theme name is displayed. Tap the Dark/Light button to change the theme type.
44 |
Read Options
45 |
Several options are available to control display of supplemental information in the Read pane. Tap a checkbox to toogle display.
46 |
47 |
--------------------------------------------------------------------------------
/help/strong.html:
--------------------------------------------------------------------------------
1 |
2 |
Strong Window
3 |
The Strong Window is used to perform word studies with the Strong dictionaries.
4 |
The Strong Window has three task panes: Verse, Definition, and Result.
5 |
Verse Pane
6 |
The Verse pane presents a tabular display of verse text and the corresponding Strong Numbers.
7 |
The upper toolbar displays the verse citation.
8 |
Tap a Strong Number to open the Definition pane for that number.
9 |
Definition Pane
10 |
The Definition pane presents the Dictionary Entry for the selected Strong Number. A Strong Word summary is also provided.
11 |
The upper toolbar displays the selected Strong Number.
12 |
Dictionary Entry
13 |
The dictionary entry is presented in six parts:
14 |
15 | Lemma: the citation form of the word in the original language
16 | Transliteration: mapping of original language graphemes to English
17 | Pronunciation: Phonetic guide to word pronunciation
18 | Etymology (▽): the derivation of the word
19 | Explanation (▷): the meaning of the word (or phrase), including variations and idioms
20 | Renderings (◁): the different renderings of the word in the KJV.
21 |
22 |
The definition statement may contain references to related Strong Numbers. Tap on a number to open the Definition pane for that number.
23 |
Strong Chain
24 |
Selecting related Strong Numbers creates a definition chain. This button in the lower toolbar traverses back up the chain:
25 |
26 |
Previous
27 |
28 |
29 | Previous
30 |
31 |
32 |
33 |
34 |
Strong Word Summary
35 |
The summary presents a list of the different translated words (Strong Words) found in the KJV.
36 |
The word summary is represented in the form: word (word count/verse count).
37 |
Two special word symbols are used as appropriate:
38 |
39 | The symbol '*' is used for all translated words combined
40 | The symbol '+' is used for Hebrew/Greek phrases represented by two or more Strong Numbers
41 |
42 |
Tap on a word to set the Strong Word. The Strong Word is highlighted. The Result pane is filtered to display only that word.
43 |
When a new Strong Number is selected, the Strong Word is reset to '*' (all) or to the unique word, as appropriate.
44 |
45 |
The following auxiliary panes are available via the lower toolbar
46 |
47 |
Lookup Pane
48 |
The Lookup pane is used query the dictionaries by Strong Number.
49 |
Enter a Strong Number in the input field. Tap the Search button, or press Enter, to set the Active Strong Number.
50 |
History Pane
51 |
The History pane displays a list of previous Strong Number queries.
52 |
Tap an history entry to set the Strong Number.
53 |
The Active Filter is reset to Tome, the Strong Word is reset to '*' (all) or unique, and the Result pane is opened.
54 |
To remove an history entry, tap the Delete button:
55 |
56 |
Delete
57 |
58 |
59 | Delete
60 |
61 |
62 |
63 |
64 |
65 |
To remove all history entries, tap the Clear History button in the lower toolbar:
66 |
67 |
Clear History
68 |
69 |
70 | Clear History
71 |
72 |
73 |
74 |
75 |
Result Pane
76 |
The Result pane displays the verse list matching the Strong Number, Strong Word, and Active Filter.
77 |
The upper toolbar displays the Active Filter, Strong Number, and Strong Word.
78 |
Tap a verse to open the Read pane at that verse.
79 |
Verses are displayed in groups of fifty. Tap the Load More button to display the next group. Use the Filter pane to quickly reduce result set size.
80 |
Strong Mode
81 |
This toggle button in the lower toolbar sets the Strong Mode:
82 |
83 |
Strong Mode
84 |
85 |
86 | Strong Mode
87 |
88 |
89 |
90 |
91 |
92 |
When Strong Mode is on, tapping a verse will open the Strong Verse pane for that verse.
93 |
94 |
The following auxiliary pane is available via the lower toolbar
95 |
96 |
Filter Pane
97 |
The Filter pane displays a list of filters grouped by Tome, Book, or Chapter.
98 |
Summary information is presented in the form: tome/book/chapter (word count/verse count).
99 |
The upper toolbar displays the Strong Number and Strong Word.
100 |
These buttons expand/collapse book entries:
101 |
102 |
Expand
103 |
104 |
105 | Expand
106 |
107 |
108 |
109 |
110 |
111 |
Collapse
112 |
113 |
114 | Collapse
115 |
116 |
117 |
118 |
119 |
120 |
Tap a filter entry to set the Active Filter and open the Result pane.
121 |
Reference
122 |
Dictionaries of Hebrew and Greek Words taken from Strong's Exhaustive Concordance by James Strong, S.T.D., LL.D.
123 |
Published in 1890; public domain.
124 |
Signs Employed
125 |
✚ (addition) denotes a rendering in the Authorized Version of one or more Hebrew/Greek words in connection with the one under consideration.
126 |
✖ (multiplication) denotes a rendering in the Authorized Version that results from an idiom peculiar to the Hebrew/Greek.
127 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KJS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/js/CommandQueue.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | class CommandQueue {
4 |
5 | constructor() {
6 | this.queue = [];
7 | this.queueRunning = false;
8 | this.commands = {};
9 | }
10 |
11 | publish(command, data) {
12 | // console.log(command);
13 | if (this.commands[command] && this.commands[command].length >= 1) {
14 | for (const listener of this.commands[command]) {
15 | this.queue.push({listener, data});
16 | }
17 | if (!this.queueRunning) {
18 | this.runQueue();
19 | }
20 | }
21 | }
22 |
23 | runQueue() {
24 | this.queueRunning = true;
25 | while (this.queue.length) {
26 | const task = this.queue.shift();
27 | task.listener(task.data);
28 | }
29 | this.queueRunning = false;
30 | }
31 |
32 | subscribe(command, listener) {
33 | if (!this.commands[command]) {
34 | this.commands[command] = [];
35 | }
36 | this.commands[command].push(listener);
37 | }
38 |
39 | }
40 |
41 | export const queue = new CommandQueue();
42 |
--------------------------------------------------------------------------------
/js/Controller/HelpController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 |
5 | class HelpController {
6 |
7 | constructor() {
8 | this.initialize();
9 | }
10 |
11 | back() {
12 | queue.publish('sidebar.change', 'none');
13 | }
14 |
15 | hide() {
16 | queue.publish(`${this.helpTask}.hide`, null);
17 | }
18 |
19 | initialize() {
20 | this.subscribe();
21 | }
22 |
23 | readPane() {
24 | queue.publish('help.task.change', 'help-read');
25 | }
26 |
27 | show() {
28 | queue.publish(`${this.helpTask}.show`, null);
29 | }
30 |
31 | sidebarUpdate(sidebar) {
32 | this.sidebar = sidebar;
33 | }
34 |
35 | subscribe() {
36 | queue.subscribe('help-read', () => {
37 | this.readPane();
38 | });
39 |
40 | queue.subscribe('help-topic', (helpTopic) => {
41 | this.topicPane(helpTopic);
42 | });
43 | queue.subscribe('help-topic.select', (helpTopic) => {
44 | this.topicSelect(helpTopic);
45 | });
46 |
47 | queue.subscribe('help.back', () => {
48 | this.back();
49 | });
50 | queue.subscribe('help.hide', () => {
51 | this.hide();
52 | });
53 | queue.subscribe('help.show', () => {
54 | this.show();
55 | });
56 | queue.subscribe('help.task.update', (helpTask) => {
57 | this.taskUpdate(helpTask);
58 | });
59 | queue.subscribe('help.topic.update', () => {
60 | this.topicUpdate();
61 | });
62 |
63 | queue.subscribe('sidebar.update', (sidebar) => {
64 | this.sidebarUpdate(sidebar);
65 | });
66 | }
67 |
68 | taskUpdate(helpTask) {
69 | if (this.sidebar === 'help') {
70 | if (this.helpTask !== helpTask) {
71 | queue.publish(`${this.helpTask}.hide`, null);
72 | this.helpTask = helpTask;
73 | queue.publish(`${this.helpTask}.show`, null);
74 | }
75 | } else {
76 | this.helpTask = helpTask;
77 | }
78 | }
79 |
80 | topicPane() {
81 | queue.publish('help.task.change', 'help-topic');
82 | }
83 |
84 | topicSelect(helpTopic) {
85 | this.topicSelectPending = true;
86 | queue.publish('help.topic.change', helpTopic);
87 | }
88 |
89 | topicUpdate() {
90 | if (this.topicSelectPending) {
91 | this.topicSelectPending = false;
92 | queue.publish('help.task.change', 'help-read');
93 | }
94 | }
95 |
96 | }
97 |
98 | export { HelpController };
99 |
--------------------------------------------------------------------------------
/js/Controller/NavigatorController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { tomeIdx } from '../data/tomeIdx.js';
5 | import { tomeLists } from '../data/tomeLists.js';
6 |
7 | class NavigatorController {
8 |
9 | constructor() {
10 | this.initialize();
11 | }
12 |
13 | back() {
14 | queue.publish('sidebar.change', 'none');
15 | }
16 |
17 | bookIdxUpdate(bookIdx) {
18 | this.lastBookIdx = bookIdx;
19 | if (this.bookSelectPending) {
20 | this.bookSelectPending = false;
21 | const book = tomeLists.books[bookIdx];
22 | this.chapterCount = book[tomeIdx.book.lastChapterIdx] -
23 | book[tomeIdx.book.firstChapterIdx] + 1;
24 | if (this.panes > 1 || this.chapterCount === 1) {
25 | const chapterIdx =
26 | tomeLists.books[bookIdx][tomeIdx.book.firstChapterIdx];
27 | queue.publish('chapterIdx.change', chapterIdx);
28 | } else {
29 | queue.publish('navigator.task.change', 'navigator-chapter');
30 | }
31 | }
32 | }
33 |
34 | bookPane() {
35 | queue.publish('navigator.task.change', 'navigator-book');
36 | }
37 |
38 | bookSelect(bookIdx) {
39 | if (bookIdx !== this.lastBookIdx) {
40 | this.bookSelectPending = true;
41 | queue.publish('bookIdx.change', bookIdx);
42 | }
43 | }
44 |
45 | chapterIdxUpdate(chapterIdx) {
46 | this.chapterIdx = chapterIdx;
47 | if (this.sidebar === 'navigator') {
48 | if (this.panes === 1) {
49 | queue.publish('sidebar.change', 'none');
50 | } else if (this.navigatorTask !== 'navigator-chapter') {
51 | queue.publish('navigator.task.change', 'navigator-chapter');
52 | }
53 | }
54 | }
55 |
56 | chapterPane() {
57 | queue.publish('navigator.task.change', 'navigator-chapter');
58 | }
59 |
60 | chapterSelect(chapterIdx) {
61 | queue.publish('chapterIdx.change', chapterIdx);
62 | }
63 |
64 | hide() {
65 | queue.publish(`${this.navigatorTask}.hide`, null);
66 | }
67 |
68 | initialize() {
69 | this.subscribe();
70 | this.lastBookIdx = null;
71 | }
72 |
73 | panesUpdate(panes) {
74 | this.panes = panes;
75 | }
76 |
77 | show() {
78 | queue.publish(`${this.navigatorTask}.show`, null);
79 | }
80 |
81 | sidebarUpdate(sidebar) {
82 | this.sidebar = sidebar;
83 | }
84 |
85 | subscribe() {
86 | queue.subscribe('bookIdx.update', (bookIdx) => {
87 | this.bookIdxUpdate(bookIdx);
88 | });
89 |
90 | queue.subscribe('chapterIdx.update', (chapterIdx) => {
91 | this.chapterIdxUpdate(chapterIdx);
92 | });
93 |
94 | queue.subscribe('navigator-book', () => {
95 | this.bookPane();
96 | });
97 | queue.subscribe('navigator-book.select', (bookIdx) => {
98 | this.bookSelect(bookIdx);
99 | });
100 |
101 | queue.subscribe('navigator-chapter', () => {
102 | this.chapterPane();
103 | });
104 | queue.subscribe('navigator-chapter.select', (chapterIdx) => {
105 | this.chapterSelect(chapterIdx);
106 | });
107 |
108 | queue.subscribe('navigator.back', () => {
109 | this.back();
110 | });
111 | queue.subscribe('navigator.hide', () => {
112 | this.hide();
113 | });
114 | queue.subscribe('navigator.show', () => {
115 | this.show();
116 | });
117 | queue.subscribe('navigator.task.update', (navigatorTask) => {
118 | this.taskUpdate(navigatorTask);
119 | });
120 |
121 | queue.subscribe('panes.update', (panes) => {
122 | this.panesUpdate(panes);
123 | });
124 |
125 | queue.subscribe('sidebar.update', (sidebar) => {
126 | this.sidebarUpdate(sidebar);
127 | });
128 | }
129 |
130 | taskUpdate(navigatorTask) {
131 | if (this.sidebar === 'navigator') {
132 | if (this.navigatorTask !== navigatorTask) {
133 | queue.publish(`${this.navigatorTask}.hide`, null);
134 | this.navigatorTask = navigatorTask;
135 | queue.publish(`${this.navigatorTask}.show`, null);
136 | }
137 | } else {
138 | this.navigatorTask = navigatorTask;
139 | }
140 | }
141 |
142 | }
143 |
144 | export { NavigatorController };
145 |
--------------------------------------------------------------------------------
/js/Controller/ReadController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 |
5 | const SIDEBAR_WIDTH = 320;
6 |
7 | const mqlOnePane = window.matchMedia('screen and (max-width: 639px)');
8 | const mqlTwoPanes =
9 | window.matchMedia('screen and (min-width: 640px) and (max-width: 959px)');
10 | const mqlThreePanes = window.matchMedia('screen and (min-width: 960px)');
11 |
12 | class ReadController {
13 |
14 | constructor() {
15 | this.initialize();
16 | }
17 |
18 | bookmarkAdd(verseIdx) {
19 | queue.publish('bookmark.add', verseIdx);
20 | }
21 |
22 | bookmarkDelete(verseIdx) {
23 | queue.publish('bookmark.delete', verseIdx);
24 | }
25 |
26 | columnModeToggle() {
27 | queue.publish('read.column-mode.toggle', null);
28 | }
29 |
30 | columnModeUpdate(columnMode) {
31 | this.columnMode = columnMode;
32 | }
33 |
34 | decreasePanes() {
35 | if (this.panes === 1) {
36 | this.lastSidebar = this.sidebar;
37 | queue.publish('sidebar.change', 'none');
38 | }
39 | if (this.columnMode && this.panes < 3) {
40 | queue.publish('read.column-mode.toggle', null);
41 | }
42 | }
43 |
44 | increasePanes() {
45 | if (this.currentPanes === 1) {
46 | if (this.sidebar !== 'none') {
47 | queue.publish('read.show', null);
48 | } else if (this.lastSidebar === null) {
49 | queue.publish('sidebar.change', 'navigator');
50 | } else {
51 | queue.publish('sidebar.change', this.lastSidebar);
52 | }
53 | }
54 | }
55 |
56 | initialize() {
57 | this.subscribe();
58 | this.sidebar = null;
59 | this.lastSidebar = null;
60 | this.panes = null;
61 | this.currentPanes = null;
62 | this.PaneListeners();
63 | }
64 |
65 | initializeApp() {
66 | this.setPanes();
67 | this.currentPanes = this.panes;
68 | queue.publish('db.restore', null);
69 | queue.publish('bookmark.restore', null);
70 | queue.publish('navigator.restore', null);
71 | queue.publish('search.restore', null);
72 | queue.publish('strong.restore', null);
73 | queue.publish('setting.restore', null);
74 | queue.publish('help.restore', null);
75 | queue.publish('read.restore', null);
76 | queue.publish('set.name-mode-btn', null);
77 | }
78 |
79 | nameModeChange() {
80 | queue.publish('name-mode.change', null);
81 | }
82 |
83 | nextChapter() {
84 | queue.publish('chapter.next', null);
85 | }
86 |
87 | PaneListeners() {
88 | mqlOnePane.addEventListener('change', (event) => {
89 | if (event.matches) {
90 | this.updatePanes();
91 | }
92 | });
93 | mqlTwoPanes.addEventListener('change', (event) => {
94 | if (event.matches) {
95 | this.updatePanes();
96 | }
97 | });
98 | mqlThreePanes.addEventListener('change', (event) => {
99 | if (event.matches) {
100 | this.updatePanes();
101 | }
102 | });
103 | }
104 |
105 | prevChapter() {
106 | queue.publish('chapter.prev', null);
107 | }
108 |
109 | setPanes() {
110 | if (mqlOnePane.matches) {
111 | this.panes = 1;
112 | } else if (mqlTwoPanes.matches) {
113 | this.panes = 2;
114 | } else if (mqlThreePanes.matches) {
115 | this.panes = 3;
116 | }
117 | queue.publish('panes.change', this.panes);
118 | }
119 |
120 | sidebarSelect(sidebar) {
121 | queue.publish('sidebar.change', sidebar);
122 | }
123 |
124 | sidebarUpdate(sidebar) {
125 | if (sidebar !== this.sidebar) {
126 | if (sidebar === 'none') {
127 | this.lastSidebar = this.sidebar;
128 | queue.publish(`${this.sidebar}.hide`, null);
129 | this.sidebar = sidebar;
130 | queue.publish('read.show', null);
131 | } else if (this.panes === 1) {
132 | if (this.sidebar === 'none') {
133 | queue.publish('read.hide', null);
134 | } else {
135 | queue.publish(`${this.sidebar}.hide`, null);
136 | }
137 | this.sidebar = sidebar;
138 | queue.publish(`${this.sidebar}.show`, null);
139 | } else {
140 | if (this.sidebar && this.sidebar !== 'none') {
141 | queue.publish(`${this.sidebar}.hide`, null);
142 | }
143 | this.sidebar = sidebar;
144 | queue.publish(`${this.sidebar}.show`, null);
145 | }
146 | }
147 | }
148 |
149 | strongModeToggle() {
150 | queue.publish('read.strong-mode.toggle', null);
151 | }
152 |
153 | strongSelect(verseIdx) {
154 | this.strongSelectPending = true;
155 | queue.publish('strong.verse.change', verseIdx);
156 | }
157 |
158 | strongVerseUpdate() {
159 | if (this.strongSelectPending) {
160 | this.strongSelectPending = false;
161 | queue.publish('sidebar.change', 'strong');
162 | }
163 | }
164 |
165 | subscribe() {
166 | queue.subscribe('read.bookmark.add', (verseIdx) => {
167 | this.bookmarkAdd(verseIdx);
168 | });
169 | queue.subscribe('read.bookmark.delete', (verseIdx) => {
170 | this.bookmarkDelete(verseIdx);
171 | });
172 |
173 | queue.subscribe('read.column-mode.click', () => {
174 | this.columnModeToggle();
175 | });
176 | queue.subscribe('read.column-mode.update', (columnMode) => {
177 | this.columnModeUpdate(columnMode);
178 | });
179 |
180 | queue.subscribe('read.name-mode.click', () => {
181 | this.nameModeChange();
182 | });
183 |
184 | queue.subscribe('read.next.chapter', () => {
185 | this.nextChapter();
186 | });
187 |
188 | queue.subscribe('read.prev.chapter', () => {
189 | this.prevChapter();
190 | });
191 |
192 | queue.subscribe('read.strong-mode.click', () => {
193 | this.strongModeToggle();
194 | });
195 |
196 | queue.subscribe('read.strong.select', (verseIdx) => {
197 | this.strongSelect(verseIdx);
198 | });
199 |
200 | queue.subscribe('sidebar.select', (sidebar) => {
201 | this.sidebarSelect(sidebar);
202 | });
203 | queue.subscribe('sidebar.update', (sidebar) => {
204 | this.sidebarUpdate(sidebar);
205 | });
206 |
207 | queue.subscribe('strong.verse.update', () => {
208 | this.strongVerseUpdate();
209 | });
210 |
211 | }
212 |
213 | updatePanes() {
214 | this.setPanes();
215 | if (this.currentPanes !== this.panes) {
216 | if (this.currentPanes > this.panes) {
217 | this.decreasePanes();
218 | } else {
219 | this.increasePanes();
220 | }
221 | this.currentPanes = this.panes;
222 | }
223 | }
224 |
225 | }
226 |
227 | export { ReadController };
228 |
--------------------------------------------------------------------------------
/js/Controller/SearchController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { chapterIdxByVerseIdx } from '../data/tomeLists.js';
5 |
6 | class SearchController {
7 |
8 | constructor() {
9 | this.initialize();
10 | }
11 |
12 | back() {
13 | queue.publish('sidebar.change', 'none');
14 | }
15 |
16 | chapterIdxUpdate() {
17 | if (this.selectVerseIdx) {
18 | if (this.panes === 1 && this.sidebar !== 'none') {
19 | queue.publish('sidebar.select', 'none');
20 | }
21 | queue.publish('read.scroll-verse-idx', this.selectVerseIdx);
22 | this.selectVerseIdx = null;
23 | }
24 | }
25 |
26 | filterPane() {
27 | queue.publish('search.task.change', 'search-filter');
28 | }
29 |
30 | filterSelect(searchFilter) {
31 | this.filterSelectPending = true;
32 | queue.publish('search.filter.change', searchFilter);
33 | }
34 |
35 | filterUpdate() {
36 | if (this.filterSelectPending) {
37 | this.filterSelectPending = false;
38 | queue.publish('search.task.change', 'search-result');
39 | }
40 | }
41 |
42 | hide() {
43 | queue.publish(`${this.searchTask}.hide`, null);
44 | }
45 |
46 | historyClear() {
47 | queue.publish('search.history.clear', null);
48 | }
49 |
50 | historyDelete(query) {
51 | queue.publish('search.history.delete', query);
52 | }
53 |
54 | historyPane() {
55 | queue.publish('search.task.change', 'search-history');
56 | }
57 |
58 | historySelect(query) {
59 | this.historySelectPending = true;
60 | queue.publish('search.query.change', query);
61 | }
62 |
63 | historyUpdate() {
64 | if (this.historySelectPending) {
65 | this.historySelectPending = false;
66 | queue.publish('search.task.change', 'search-result');
67 | }
68 | }
69 |
70 | initialize() {
71 | this.subscribe();
72 | }
73 |
74 | lookupPane() {
75 | queue.publish('search.task.change', 'search-lookup');
76 | }
77 |
78 | lookupSearch(query) {
79 | queue.publish('search.query.change', query);
80 | }
81 |
82 | modeToggle() {
83 | queue.publish('search.strong-mode.toggle', null);
84 | }
85 |
86 | panesUpdate(panes) {
87 | this.panes = panes;
88 | }
89 |
90 | queryChange() {
91 | this.queryChangePending = true;
92 | }
93 |
94 | queryUpdate() {
95 | if (this.queryChangePending) {
96 | queue.publish('search.task.change', 'search-result');
97 | }
98 | }
99 |
100 | readSelect(verseIdx) {
101 | this.selectVerseIdx = verseIdx;
102 | const chapterIdx = chapterIdxByVerseIdx(verseIdx);
103 | queue.publish('chapterIdx.change', chapterIdx);
104 | }
105 |
106 | resultPane() {
107 | queue.publish('search.task.change', 'search-result');
108 | }
109 |
110 | show() {
111 | queue.publish(`${this.searchTask}.show`, null);
112 | }
113 |
114 | sidebarUpdate(sidebar) {
115 | this.sidebar = sidebar;
116 | }
117 |
118 | strongSelect(verseIdx) {
119 | this.strongSelectPending = true;
120 | queue.publish('strong.verse.change', verseIdx);
121 | }
122 |
123 | strongVerseUpdate() {
124 | if (this.strongSelectPending) {
125 | this.strongSelectPending = false;
126 | queue.publish('sidebar.change', 'strong');
127 | }
128 | }
129 |
130 | subscribe() {
131 | queue.subscribe('chapterIdx.update', () => {
132 | this.chapterIdxUpdate();
133 | });
134 |
135 | queue.subscribe('panes.update', (panes) => {
136 | this.panesUpdate(panes);
137 | });
138 |
139 | queue.subscribe('search-filter', () => {
140 | this.filterPane();
141 | });
142 | queue.subscribe('search-filter.select', (searchFilter) => {
143 | this.filterSelect(searchFilter);
144 | });
145 |
146 | queue.subscribe('search-history', () => {
147 | this.historyPane();
148 | });
149 | queue.subscribe('search-history.clear', () => {
150 | this.historyClear();
151 | });
152 | queue.subscribe('search-history.delete', (query) => {
153 | this.historyDelete(query);
154 | });
155 | queue.subscribe('search-history.select', (query) => {
156 | this.historySelect(query);
157 | });
158 |
159 | queue.subscribe('search-lookup', () => {
160 | this.lookupPane();
161 | });
162 | queue.subscribe('search-lookup.search', (query) => {
163 | this.lookupSearch(query);
164 | });
165 |
166 | queue.subscribe('search-result', () => {
167 | this.resultPane();
168 | });
169 | queue.subscribe('search-result.read-select', (verseIdx) => {
170 | this.readSelect(verseIdx);
171 | });
172 | queue.subscribe('search-result.strong-select', (verseIdx) => {
173 | this.strongSelect(verseIdx);
174 | });
175 |
176 | queue.subscribe('search.back', () => {
177 | this.back();
178 | });
179 | queue.subscribe('search.filter.update', () => {
180 | this.filterUpdate();
181 | });
182 | queue.subscribe('search.hide', () => {
183 | this.hide();
184 | });
185 | queue.subscribe('search.history.update', () => {
186 | this.historyUpdate();
187 | });
188 | queue.subscribe('search.query.change', () => {
189 | this.queryChange();
190 | });
191 | queue.subscribe('search.query.update', () => {
192 | this.queryUpdate();
193 | });
194 | queue.subscribe('search.show', () => {
195 | this.show();
196 | });
197 | queue.subscribe('search.strong-mode.click', () => {
198 | this.modeToggle();
199 | });
200 | queue.subscribe('search.task.update', (searchTask) => {
201 | this.taskUpdate(searchTask);
202 | });
203 |
204 | queue.subscribe('sidebar.update', (sidebar) => {
205 | this.sidebarUpdate(sidebar);
206 | });
207 |
208 | queue.subscribe('strong.verse.update', () => {
209 | this.strongVerseUpdate();
210 | });
211 | }
212 |
213 | taskUpdate(searchTask) {
214 | if (this.sidebar === 'search') {
215 | if (this.searchTask !== searchTask) {
216 | queue.publish(`${this.searchTask}.hide`, null);
217 | this.searchTask = searchTask;
218 | queue.publish(`${this.searchTask}.show`, null);
219 | }
220 | } else {
221 | this.searchTask = searchTask;
222 | }
223 | }
224 |
225 | }
226 |
227 | export { SearchController };
228 |
--------------------------------------------------------------------------------
/js/Controller/SettingController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 |
5 | class SettingController {
6 |
7 | constructor() {
8 | this.initialize();
9 | }
10 |
11 | back() {
12 | queue.publish('sidebar.change', 'none');
13 | }
14 |
15 | fontNext() {
16 | this.getNextFontIdx();
17 | queue.publish('font.change', this.fonts[this.fontIdx]);
18 | }
19 |
20 | fontPrev() {
21 | this.getPrevFontIdx();
22 | queue.publish('font.change', this.fonts[this.fontIdx]);
23 | }
24 |
25 | fontSize(fontSize) {
26 | queue.publish('font-size.change', fontSize);
27 | }
28 |
29 | fontUpdate(font) {
30 | this.font = font;
31 | if (!this.fontIdx) {
32 | this.fontIdx = this.fonts.findIndex((font) => {
33 | return font.fontName === this.font.fontName;
34 | });
35 | }
36 | }
37 |
38 | fontVariant(fontVariant) {
39 | queue.publish('font-variant.change', fontVariant);
40 | }
41 |
42 | fontsUpdate(fonts) {
43 | this.fonts = fonts;
44 | this.maxFontIdx = this.fonts.length - 1;
45 | }
46 |
47 | getNextFontIdx() {
48 | this.fontIdx = this.fontIdx === this.maxFontIdx ? 0 : this.fontIdx += 1;
49 | }
50 |
51 | getPrevFontIdx() {
52 | this.fontIdx = this.fontIdx === 0 ? this.maxFontIdx : this.fontIdx -= 1;
53 | }
54 |
55 | getDarkThemeIdx() {
56 | if (this.themes[this.themeIdx] === 'dark') {
57 | return this.themeIdx;
58 | }
59 | this.themeIdx = this.themes.findIndex((theme) => {
60 | return theme.themeType === 'dark' &&
61 | theme.themeName === this.theme.themeName;
62 | });
63 | }
64 |
65 | getLightThemeIdx() {
66 | if (this.themes[this.themeIdx] === 'light') {
67 | return this.themeIdx;
68 | }
69 | this.themeIdx = this.themes.findIndex((theme) => {
70 | return theme.themeType === 'light' &&
71 | theme.themeName === this.theme.themeName;
72 | });
73 | }
74 |
75 | getNextThemeIdx() {
76 | let nameIdx = this.themeNames.findIndex(x => x === this.theme.themeName);
77 | const nextNameIdx = nameIdx === this.maxThemeNamesIdx ? 0 : nameIdx += 1;
78 | this.themeIdx = this.themes.findIndex((theme) => {
79 | return theme.themeType === this.theme.themeType &&
80 | theme.themeName === this.themeNames[nextNameIdx];
81 | });
82 | }
83 |
84 | getPrevThemeIdx() {
85 | let nameIdx = this.themeNames.findIndex(x => x === this.theme.themeName);
86 | const nextNameIdx = nameIdx === 0 ? this.maxThemeNamesIdx : nameIdx -= 1;
87 | this.themeIdx = this.themes.findIndex((theme) => {
88 | return theme.themeType === this.theme.themeType &&
89 | theme.themeName === this.themeNames[nextNameIdx];
90 | });
91 | }
92 |
93 | initialize() {
94 | this.subscribe();
95 | }
96 |
97 | subscribe() {
98 | queue.subscribe('font.update', (font) => {
99 | this.fontUpdate(font);
100 | });
101 |
102 | queue.subscribe('fonts.update', (fonts) => {
103 | this.fontsUpdate(fonts);
104 | });
105 |
106 | queue.subscribe('setting.back', () => {
107 | this.back();
108 | });
109 | queue.subscribe('setting.font-next', () => {
110 | this.fontNext();
111 | });
112 | queue.subscribe('setting.font-prev', () => {
113 | this.fontPrev();
114 | });
115 | queue.subscribe('setting.font-size', (fontSize) => {
116 | this.fontSize(fontSize);
117 | });
118 | queue.subscribe('setting.font-variant', (fontVariant) => {
119 | this.fontVariant(fontVariant);
120 | });
121 |
122 | queue.subscribe('setting.theme-next', () => {
123 | this.themeNext();
124 | });
125 | queue.subscribe('setting.theme-prev', () => {
126 | this.themePrev();
127 | });
128 |
129 | queue.subscribe('setting.theme-dark', () => {
130 | this.themeDark();
131 | });
132 | queue.subscribe('setting.theme-light', () => {
133 | this.themeLight();
134 | });
135 |
136 | queue.subscribe('theme.update', (theme) => {
137 | this.themeUpdate(theme);
138 | });
139 |
140 | queue.subscribe('themes.update', (themes) => {
141 | this.themesUpdate(themes);
142 | });
143 | }
144 |
145 | themeDark() {
146 | const idxNow = this.themeIdx;
147 | this.getDarkThemeIdx();
148 | if (idxNow === this.themeIdx) {
149 | return;
150 | }
151 | queue.publish('theme.change', this.themes[this.themeIdx]);
152 | }
153 |
154 | themeLight() {
155 | const idxNow = this.themeIdx;
156 | this.getLightThemeIdx();
157 | if (idxNow === this.themeIdx) {
158 | return;
159 | }
160 | queue.publish('theme.change', this.themes[this.themeIdx]);
161 | }
162 |
163 | themeNext() {
164 | this.getNextThemeIdx();
165 | queue.publish('theme.change', this.themes[this.themeIdx]);
166 | }
167 |
168 | themePrev() {
169 | this.getPrevThemeIdx();
170 | queue.publish('theme.change', this.themes[this.themeIdx]);
171 | }
172 |
173 | themeUpdate(theme) {
174 | this.theme = theme;
175 | if (!this.themeIdx) {
176 | this.themeIdx = this.themes.findIndex((theme) => {
177 | return theme.themeType === this.theme.themeType &&
178 | theme.themeName === this.theme.themeName;
179 | });
180 | }
181 | if (!this.themeNameIdx) {
182 | this.themeNameIdx = this.themeNames.findIndex((theme) => {
183 | return theme.themeName === this.theme.themeName;
184 | });
185 | }
186 | }
187 |
188 | themesUpdate(themes) {
189 | this.themes = themes;
190 | this.maxThemesIdx = this.themes.length - 1;
191 | this.themeNames = [...new Set(this.themes.map(x => x.themeName))];
192 | this.maxThemeNamesIdx = this.themeNames.length - 1;
193 | }
194 |
195 | }
196 |
197 | export { SettingController };
198 |
--------------------------------------------------------------------------------
/js/Controller/StrongController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { chapterIdxByVerseIdx } from '../data/tomeLists.js';
5 |
6 | class StrongController {
7 |
8 | constructor() {
9 | this.initialize();
10 | }
11 |
12 | back() {
13 | queue.publish('sidebar.change', 'none');
14 | }
15 |
16 | chapterIdxUpdate() {
17 | if (this.selectVerseIdx) {
18 | if (this.panes === 1 && this.sidebar !== 'none') {
19 | queue.publish('sidebar.select', 'none');
20 | }
21 | queue.publish('read.scroll-verse-idx', this.selectVerseIdx);
22 | this.selectVerseIdx = null;
23 | }
24 | }
25 |
26 | defChange() {
27 | this.defChangePending = true;
28 | }
29 |
30 | defPane() {
31 | queue.publish('strong.task.change', 'strong-def');
32 | }
33 |
34 | defSelect(strongDef) {
35 | queue.publish('strong.chain.add', null);
36 | queue.publish('strong.def.change', strongDef);
37 | }
38 |
39 | defUpdate() {
40 | if (this.defChangePending) {
41 | this.defChangePending = false;
42 | queue.publish('strong.task.change', 'strong-def');
43 | }
44 | }
45 |
46 | filterPane() {
47 | queue.publish('strong.task.change', 'strong-filter');
48 | }
49 |
50 | filterSelect(strongFilter) {
51 | this.filterSelectPending = true;
52 | queue.publish('strong.filter.change', strongFilter);
53 | }
54 |
55 | filterUpdate() {
56 | if (this.filterSelectPending) {
57 | this.filterSelectPending = false;
58 | queue.publish('strong.task.change', 'strong-result');
59 | }
60 | }
61 |
62 | hide() {
63 | queue.publish(`${this.strongTask}.hide`, null);
64 | }
65 |
66 | historyClear() {
67 | queue.publish('strong.history.clear', null);
68 | }
69 |
70 | historyDelete(strongDef) {
71 | queue.publish('strong.history.delete', strongDef);
72 | }
73 |
74 | historyPane() {
75 | queue.publish('strong.task.change', 'strong-history');
76 | }
77 |
78 | historySelect(strongDef) {
79 | queue.publish('strong.def.change', strongDef);
80 | }
81 |
82 | initialize() {
83 | this.subscribe();
84 | }
85 |
86 | lookupFind(strongNum) {
87 | queue.publish('strong.chain.clear', null);
88 | queue.publish('strong.def.change', strongNum);
89 | }
90 |
91 | lookupPane() {
92 | queue.publish('strong.task.change', 'strong-lookup');
93 | }
94 |
95 | modeToggle() {
96 | queue.publish('strong.strong-mode.toggle', null);
97 | }
98 |
99 | panesUpdate(panes) {
100 | this.panes = panes;
101 | }
102 |
103 | prev() {
104 | queue.publish('strong.chain.prev', null);
105 | }
106 |
107 | readSelect(verseIdx) {
108 | this.selectVerseIdx = verseIdx;
109 | const chapterIdx = chapterIdxByVerseIdx(verseIdx);
110 | queue.publish('chapterIdx.change', chapterIdx);
111 | }
112 |
113 | resultPane() {
114 | queue.publish('strong.task.change', 'strong-result');
115 | }
116 |
117 | show() {
118 | queue.publish(`${this.strongTask}.show`, null);
119 | }
120 |
121 | sidebarUpdate(sidebar) {
122 | this.sidebar = sidebar;
123 | }
124 |
125 | strongSelect(verseIdx) {
126 | queue.publish('strong.verse.change', verseIdx);
127 | }
128 |
129 | subscribe() {
130 | queue.subscribe('chapterIdx.update', () => {
131 | this.chapterIdxUpdate();
132 | });
133 |
134 | queue.subscribe('panes.update', (panes) => {
135 | this.panesUpdate(panes);
136 | });
137 |
138 | queue.subscribe('sidebar.update', (sidebar) => {
139 | this.sidebarUpdate(sidebar);
140 | });
141 |
142 | queue.subscribe('strong-def', () => {
143 | this.defPane();
144 | });
145 | queue.subscribe('strong-def.select', (strongDef) => {
146 | this.defSelect(strongDef);
147 | });
148 | queue.subscribe('strong-def.word.select', (strongWord) => {
149 | this.wordSelect(strongWord);
150 | });
151 |
152 | queue.subscribe('strong-filter', () => {
153 | this.filterPane();
154 | });
155 | queue.subscribe('strong-filter.select', (strongFilter) => {
156 | this.filterSelect(strongFilter);
157 | });
158 |
159 | queue.subscribe('strong-history', () => {
160 | this.historyPane();
161 | });
162 | queue.subscribe('strong-history.clear', () => {
163 | this.historyClear();
164 | });
165 | queue.subscribe('strong-history.delete', (strongDef) => {
166 | this.historyDelete(strongDef);
167 | });
168 | queue.subscribe('strong-history.select', (strongDef) => {
169 | this.historySelect(strongDef);
170 | });
171 |
172 | queue.subscribe('strong-lookup', () => {
173 | this.lookupPane();
174 | });
175 | queue.subscribe('strong-lookup.find', (strongNum) => {
176 | this.lookupFind(strongNum);
177 | });
178 |
179 | queue.subscribe('strong-result', () => {
180 | this.resultPane();
181 | });
182 | queue.subscribe('strong-result.read-select', (verseIdx) => {
183 | this.readSelect(verseIdx);
184 | });
185 | queue.subscribe('strong-result.strong-select', (verseIdx) => {
186 | this.strongSelect(verseIdx);
187 | });
188 |
189 | queue.subscribe('strong-verse', () => {
190 | this.versePane();
191 | });
192 | queue.subscribe('strong-verse.select', (strongDef) => {
193 | this.verseSelect(strongDef);
194 | });
195 |
196 | queue.subscribe('strong.back', () => {
197 | this.back();
198 | });
199 | queue.subscribe('strong.def.change', () => {
200 | this.defChange();
201 | });
202 | queue.subscribe('strong.def.update', () => {
203 | this.defUpdate();
204 | });
205 | queue.subscribe('strong.filter.update', () => {
206 | this.filterUpdate();
207 | });
208 | queue.subscribe('strong.hide', () => {
209 | this.hide();
210 | });
211 | queue.subscribe('strong.prev', () => {
212 | this.prev();
213 | });
214 | queue.subscribe('strong.show', () => {
215 | this.show();
216 | });
217 | queue.subscribe('strong.strong-mode.click', () => {
218 | this.modeToggle();
219 | });
220 | queue.subscribe('strong.task.update', (strongTask) => {
221 | this.taskUpdate(strongTask);
222 | });
223 | queue.subscribe('strong.verse.change', () => {
224 | this.verseChange();
225 | });
226 | queue.subscribe('strong.verse.update', () => {
227 | this.verseUpdate();
228 | });
229 | queue.subscribe('strong.word.update', () => {
230 | this.wordUpdate();
231 | });
232 | }
233 |
234 | taskUpdate(strongTask) {
235 | if (this.sidebar === 'strong') {
236 | if (this.strongTask !== strongTask) {
237 | queue.publish(`${this.strongTask}.hide`, null);
238 | this.strongTask = strongTask;
239 | queue.publish(`${this.strongTask}.show`, null);
240 | }
241 | } else {
242 | this.strongTask = strongTask;
243 | }
244 | }
245 |
246 | verseChange() {
247 | this.verseChangePending = true;
248 | }
249 |
250 | versePane() {
251 | queue.publish('strong.task.change', 'strong-verse');
252 | }
253 |
254 | verseSelect(strongDef) {
255 | queue.publish('strong.chain.clear', null);
256 | queue.publish('strong.def.change', strongDef);
257 | }
258 |
259 | verseUpdate() {
260 | if (this.verseChangePending) {
261 | this.verseChangePending = false;
262 | queue.publish('strong.task.change', 'strong-verse');
263 | }
264 | }
265 |
266 | wordSelect(strongWord) {
267 | this.wordSelectPending = true;
268 | queue.publish('strong.word.change', strongWord);
269 | }
270 |
271 | wordUpdate() {
272 | if (this.wordSelectPending) {
273 | this.wordSelectPending = false;
274 | queue.publish('strong.task.change', 'strong-result');
275 | }
276 | }
277 |
278 | }
279 |
280 | export { StrongController };
281 |
--------------------------------------------------------------------------------
/js/Model/DbModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { kjvPureDb, kjvPureVerseCount, kjvPureWords }
5 | from '../data/kjvPureDb.js';
6 | import { kjvNameDb, kjvNameVerseCount, kjvNameWords }
7 | from '../data/kjvNameDb.js';
8 | import { strongNameDb } from '../data/strongNameDb.js';
9 | import { strongPureDb } from '../data/strongPureDb.js';
10 |
11 | export let tomeDb = null;
12 | export let tomeVerseCount = null;
13 | export let tomeWords = null;
14 | export let dbNameMode = null;
15 | export let strongDb = null;
16 | export let strongName = null;
17 |
18 | class DbModel {
19 |
20 | constructor() {
21 | this.initialize();
22 | }
23 |
24 | initialize() {
25 | this.subscribe();
26 | }
27 |
28 | tomeDbChange() {
29 | if (this.nameMode === true) {
30 | tomeDb = kjvNameDb;
31 | tomeVerseCount = kjvNameVerseCount;
32 | tomeWords = kjvNameWords;
33 | } else {
34 | tomeDb = kjvPureDb;
35 | tomeVerseCount = kjvPureVerseCount;
36 | tomeWords = kjvPureWords;
37 | }
38 | }
39 |
40 | nameModeChange() {
41 | this.nameMode = !this.nameMode;
42 | this.saveNameMode();
43 | dbNameMode = this.nameMode;
44 | this.tomeDbChange();
45 | this.strongDbChange();
46 | queue.publish('name-mode.update', this.nameMode);
47 | }
48 |
49 | restore() {
50 | this.restoreNameMode();
51 | }
52 |
53 | restoreNameMode() {
54 | const defaultNameMode = false;
55 | let nameMode = localStorage.getItem('nameMode');
56 | if (!nameMode) {
57 | nameMode = defaultNameMode;
58 | } else {
59 | try {
60 | nameMode = JSON.parse(nameMode);
61 | } catch (error) {
62 | nameMode = defaultNameMode;
63 | }
64 | if (typeof nameMode !== 'boolean') {
65 | nameMode = defaultNameMode;
66 | }
67 | }
68 | this.nameMode = nameMode;
69 | dbNameMode = this.nameMode;
70 | this.tomeDbChange();
71 | this.strongDbChange();
72 | }
73 |
74 | saveNameMode() {
75 | localStorage.setItem('nameMode',
76 | JSON.stringify(this.nameMode));
77 | }
78 |
79 | strongDbChange() {
80 | if (this.nameMode === true) {
81 | strongDb = strongNameDb;
82 | strongName = 'strong_name';
83 | } else {
84 | strongDb = strongPureDb;
85 | strongName = 'strong_pure';
86 | }
87 | }
88 |
89 | subscribe() {
90 | queue.subscribe('db.restore', () => {
91 | this.restore();
92 | });
93 | queue.subscribe('name-mode.change', () => {
94 | this.nameModeChange();
95 | });
96 | }
97 |
98 | }
99 |
100 | export { DbModel };
101 |
--------------------------------------------------------------------------------
/js/Model/HelpModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 |
5 | const validTasks = [
6 | 'help-read', 'help-topic',
7 | ];
8 | const validTopics = [
9 | 'about', 'bookmark', 'clipboard-mode', 'help', 'name-mode', 'navigator',
10 | 'overview', 'read', 'search', 'setting', 'strong',
11 | ];
12 |
13 | class HelpModel {
14 |
15 | constructor() {
16 | this.initialize();
17 | }
18 |
19 | initialize() {
20 | this.subscribe();
21 | }
22 |
23 | restore() {
24 | this.restoreTask();
25 | this.restoreTopic();
26 | }
27 |
28 | restoreTask() {
29 | const defaultTask = 'help-read';
30 | let helpTask = localStorage.getItem('helpTask');
31 | if (!helpTask) {
32 | helpTask = defaultTask;
33 | } else {
34 | try {
35 | helpTask = JSON.parse(helpTask);
36 | } catch (error) {
37 | helpTask = defaultTask;
38 | }
39 | if (!validTasks.includes(helpTask)) {
40 | helpTask = defaultTask;
41 | }
42 | }
43 | this.taskChange(helpTask);
44 | }
45 |
46 | restoreTopic() {
47 | const defaultTopic = 'overview';
48 | let helpTopic = localStorage.getItem('helpTopic');
49 | if (!helpTopic) {
50 | helpTopic = defaultTopic;
51 | } else {
52 | try {
53 | helpTopic = JSON.parse(helpTopic);
54 | } catch (error) {
55 | helpTopic = defaultTopic;
56 | }
57 | if (!validTopics.includes(helpTopic)) {
58 | helpTopic = defaultTopic;
59 | }
60 | }
61 | this.topicChange(helpTopic);
62 | }
63 |
64 | saveHelpTask() {
65 | localStorage.setItem('helpTask', JSON.stringify(this.helpTask));
66 | }
67 |
68 | saveHelpTopic() {
69 | localStorage.setItem('helpTopic', JSON.stringify(this.helpTopic));
70 | }
71 |
72 | subscribe() {
73 | queue.subscribe('help.restore', () => {
74 | this.restore();
75 | });
76 | queue.subscribe('help.task.change', (helpTask) => {
77 | this.taskChange(helpTask);
78 | });
79 | queue.subscribe('help.topic.change', (helpTopic) => {
80 | this.topicChange(helpTopic);
81 | });
82 | }
83 |
84 | taskChange(helpTask) {
85 | this.helpTask = helpTask;
86 | this.saveHelpTask();
87 | queue.publish('help.task.update', this.helpTask);
88 | }
89 |
90 | topicChange(helpTopic) {
91 | this.helpTopic = helpTopic;
92 | this.saveHelpTopic();
93 | queue.publish('help.topic.update', this.helpTopic);
94 | }
95 |
96 | }
97 |
98 | export { HelpModel };
99 |
--------------------------------------------------------------------------------
/js/Model/NavigatorModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { tomeIdx } from '../data/tomeIdx.js';
5 | import { firstVerseIdxByChapterIdx, tomeLists } from '../data/tomeLists.js';
6 |
7 | const validTasks = [
8 | 'navigator-book', 'navigator-chapter',
9 | ];
10 |
11 | const CHAPTER_IDX_GENESIS_1 = 0;
12 |
13 | class NavigatorModel {
14 |
15 | constructor() {
16 | this.initialize();
17 | }
18 |
19 | bookIdxChange(bookIdx) {
20 | this.bookIdx = bookIdx;
21 | queue.publish('bookIdx.update', this.bookIdx);
22 | }
23 |
24 | chapterIdxChange(chapterIdx) {
25 | this.chapterIdx = chapterIdx;
26 | this.saveChapterIdx();
27 | const bookIdx =
28 | tomeLists.chapters[this.chapterIdx][tomeIdx.chapter.bookIdx];
29 | if (this.bookIdx !== bookIdx) {
30 | this.bookIdxChange(bookIdx);
31 | }
32 | queue.publish('chapterIdx.update', this.chapterIdx);
33 | const verseIdx = firstVerseIdxByChapterIdx(this.chapterIdx);
34 | queue.publish('read.scroll-verse-idx', verseIdx);
35 | }
36 |
37 | async chapterNext() {
38 | let nextChapterIdx = this.chapterIdx + 1;
39 | if (nextChapterIdx >= tomeLists.chapters.length) {
40 | nextChapterIdx = 0;
41 | }
42 | await this.chapterIdxChange(nextChapterIdx);
43 | }
44 |
45 | async chapterPrev() {
46 | let prevChapterIdx = this.chapterIdx - 1;
47 | if (prevChapterIdx < 0) {
48 | prevChapterIdx = tomeLists.chapters.length - 1;
49 | }
50 | await this.chapterIdxChange(prevChapterIdx);
51 | }
52 |
53 | initialize() {
54 | this.subscribe();
55 | }
56 |
57 | async restore() {
58 | this.restoreTask();
59 | await this.restoreChapterIdx();
60 | }
61 |
62 | async restoreChapterIdx() {
63 | const defaultIdx = CHAPTER_IDX_GENESIS_1;
64 | let chapterIdx = localStorage.getItem('chapterIdx');
65 | if (!chapterIdx) {
66 | chapterIdx = defaultIdx;
67 | } else {
68 | try {
69 | chapterIdx = JSON.parse(chapterIdx);
70 | } catch (error) {
71 | chapterIdx = defaultIdx;
72 | }
73 | if (!tomeLists.chapters[chapterIdx]) {
74 | chapterIdx = defaultIdx;
75 | }
76 | }
77 | await this.chapterIdxChange(chapterIdx);
78 | }
79 |
80 | restoreTask() {
81 | const defaultTask = 'navigator-book';
82 | let navigatorTask = localStorage.getItem('navigatorTask');
83 | if (!navigatorTask) {
84 | navigatorTask = defaultTask;
85 | } else {
86 | try {
87 | navigatorTask = JSON.parse(navigatorTask);
88 | } catch (error) {
89 | navigatorTask = defaultTask;
90 | }
91 | }
92 | if (!validTasks.includes(navigatorTask)) {
93 | navigatorTask = defaultTask;
94 | }
95 | this.taskChange(navigatorTask);
96 | }
97 |
98 | saveChapterIdx() {
99 | localStorage.setItem('chapterIdx',
100 | JSON.stringify(this.chapterIdx));
101 | }
102 |
103 | saveNavigatorTask() {
104 | localStorage.setItem('navigatorTask',
105 | JSON.stringify(this.navigatorTask));
106 | }
107 |
108 | subscribe() {
109 | queue.subscribe('bookIdx.change', (bookIdx) => {
110 | this.bookIdxChange(bookIdx);
111 | });
112 |
113 | queue.subscribe('chapter.next', async () => {
114 | await this.chapterNext();
115 | });
116 | queue.subscribe('chapter.prev', async () => {
117 | await this.chapterPrev();
118 | });
119 |
120 | queue.subscribe('chapterIdx.change', async (chapterIdx) => {
121 | await this.chapterIdxChange(chapterIdx);
122 | });
123 |
124 | queue.subscribe('navigator.restore', async () => {
125 | await this.restore();
126 | });
127 | queue.subscribe('navigator.task.change', (navigatorTask) => {
128 | this.taskChange(navigatorTask);
129 | });
130 | }
131 |
132 | taskChange(navigatorTask) {
133 | this.navigatorTask = navigatorTask;
134 | this.saveNavigatorTask();
135 | queue.publish('navigator.task.update', this.navigatorTask);
136 | }
137 |
138 | }
139 |
140 | export { NavigatorModel };
141 |
--------------------------------------------------------------------------------
/js/Model/ReadModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { util } from '../util.js';
5 | import { tomeIdx} from '../data/tomeIdx.js';
6 | import { tomeLists} from '../data/tomeLists.js';
7 | import { tomeDb } from '../Model/DbModel.js';
8 |
9 | class ReadModel {
10 |
11 | constructor() {
12 | this.initialize();
13 | }
14 |
15 | chapterIdxUpdate(chapterIdx) {
16 | this.chapterIdx = chapterIdx;
17 | this.updateReadVerseObjs();
18 | }
19 |
20 | columnModeChange(columnMode) {
21 | this.columnMode = columnMode;
22 | this.saveColumnMode();
23 | queue.publish('read.column-mode.update', this.columnMode);
24 | }
25 |
26 | columnModeToogle() {
27 | this.columnModeChange(!this.columnMode);
28 | }
29 |
30 | initialize() {
31 | this.subscribe();
32 | }
33 |
34 | nameModeChange() {
35 | this.updateReadVerseObjs();
36 | }
37 |
38 | panesChange(panes) {
39 | this.panes = panes;
40 | queue.publish('panes.update', this.panes);
41 | }
42 |
43 | restore() {
44 | this.restoreColumnMode();
45 | this.restoreStrongMode();
46 | this.restoreSidebar();
47 | }
48 |
49 | restoreColumnMode() {
50 | const defaultColumnMode = false;
51 | let columnMode = localStorage.getItem('columnMode');
52 | if (!columnMode) {
53 | columnMode = defaultColumnMode;
54 | } else {
55 | try {
56 | columnMode = JSON.parse(columnMode);
57 | } catch (error) {
58 | columnMode = defaultColumnMode;
59 | }
60 | if (typeof columnMode !== 'boolean') {
61 | columnMode = defaultColumnMode;
62 | }
63 | }
64 | this.columnModeChange(columnMode);
65 | }
66 |
67 | restoreSidebar() {
68 | const defaultSidebar = this.panes > 1 ? 'navigator' : 'none';
69 | let sidebar = localStorage.getItem('sidebar');
70 | if (!sidebar) {
71 | sidebar = defaultSidebar;
72 | } else {
73 | try {
74 | sidebar = JSON.parse(sidebar);
75 | } catch (error) {
76 | sidebar = defaultSidebar;
77 | }
78 | }
79 | if (this.panes > 1) {
80 | sidebar = sidebar === 'none' ? 'navigator' : sidebar;
81 | } else if (sidebar !== 'none') {
82 | sidebar = 'none';
83 | }
84 | this.sidebarChange(sidebar);
85 | }
86 |
87 | restoreStrongMode() {
88 | const defaultStrongMode = false;
89 | let strongMode = localStorage.getItem('readStrongMode');
90 | if (!strongMode) {
91 | strongMode = defaultStrongMode;
92 | } else {
93 | try {
94 | strongMode = JSON.parse(strongMode);
95 | } catch (error) {
96 | strongMode = defaultStrongMode;
97 | }
98 | if (typeof strongMode !== 'boolean') {
99 | strongMode = defaultStrongMode;
100 | }
101 | }
102 | this.strongModeChange(strongMode);
103 | }
104 |
105 | saveColumnMode() {
106 | localStorage.setItem('columnMode',
107 | JSON.stringify(this.columnMode));
108 | }
109 |
110 | saveStrongMode() {
111 | localStorage.setItem('readStrongMode',
112 | JSON.stringify(this.strongMode));
113 | }
114 |
115 | saveSidebar() {
116 | localStorage.setItem('sidebar', JSON.stringify(this.sidebar));
117 | }
118 |
119 | sidebarChange(sidebar) {
120 | this.sidebar = sidebar;
121 | this.saveSidebar();
122 | queue.publish('sidebar.update', this.sidebar);
123 | }
124 |
125 | strongModeChange(strongMode) {
126 | this.strongMode = strongMode;
127 | this.saveStrongMode();
128 | queue.publish('read.strong-mode.update', this.strongMode);
129 | }
130 |
131 | strongModeToogle() {
132 | this.strongModeChange(!this.strongMode);
133 | }
134 |
135 | subscribe() {
136 | queue.subscribe('chapterIdx.update', (chapterIdx) => {
137 | this.chapterIdxUpdate(chapterIdx);
138 | });
139 |
140 | queue.subscribe('name-mode.change', () => {
141 | this.nameModeChange();
142 | });
143 |
144 | queue.subscribe('panes.change', (panes) => {
145 | this.panesChange(panes);
146 | });
147 |
148 | queue.subscribe('read.column-mode.toggle', () => {
149 | this.columnModeToogle();
150 | });
151 | queue.subscribe('read.restore',
152 | () => { this.restore(); }
153 | );
154 | queue.subscribe('read.strong-mode.toggle', () => {
155 | this.strongModeToogle();
156 | });
157 |
158 | queue.subscribe('sidebar.change', (sidebar) => {
159 | this.sidebarChange(sidebar);
160 | });
161 | }
162 |
163 | async updateReadVerseObjs() {
164 | const chapter = tomeLists.chapters[this.chapterIdx];
165 | const keys = util.range(chapter[tomeIdx.chapter.firstVerseIdx],
166 | chapter[tomeIdx.chapter.lastVerseIdx] + 1);
167 | this.verseObjs = await tomeDb.verses.bulkGet(keys);
168 | queue.publish('read.verse-objs.update', this.verseObjs);
169 | }
170 | }
171 |
172 | export { ReadModel };
173 |
--------------------------------------------------------------------------------
/js/Model/SearchModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { SearchEngine } from '../SearchEngine.js';
5 | import { binIdx } from '../data/binIdx.js';
6 | import { tomeDb } from '../Model/DbModel.js';
7 |
8 | const searchResultReroute = [
9 | 'search-filter', 'search-history',
10 | ];
11 | const validTasks = [
12 | 'search-result', 'search-lookup', 'search-filter', 'search-history',
13 | ];
14 |
15 | const DEFAULT_QUERY = 'day of the lord';
16 |
17 | class SearchModel {
18 |
19 | constructor() {
20 | this.initialize();
21 | }
22 |
23 | addHistory() {
24 | if (this.searchHistory.indexOf(this.searchQuery) === -1) {
25 | this.searchHistory = [this.searchQuery, ...this.searchHistory];
26 | this.updateHistory();
27 | }
28 | }
29 |
30 | filterChange(searchFilter) {
31 | this.searchFilter = searchFilter;
32 | this.saveFilter();
33 | queue.publish('search.filter.update', this.searchFilter);
34 | }
35 |
36 | filterIsValid(searchFilter) {
37 | let result = false;
38 | if (typeof searchFilter === 'object') {
39 | if (searchFilter.bookIdx && searchFilter.chapterIdx) {
40 | result = true;
41 | }
42 | }
43 | return result;
44 | }
45 |
46 | historyChange(searchHistory) {
47 | this.searchHistory = searchHistory;
48 | this.saveHistory();
49 | queue.publish('search.history.update', this.searchHistory);
50 | }
51 |
52 | historyClear() {
53 | this.searchHistory = [];
54 | this.updateHistory();
55 | }
56 |
57 | historyDelete(str) {
58 | const index = this.searchHistory.indexOf(str);
59 | this.searchHistory.splice(index, 1);
60 | this.updateHistory();
61 | }
62 |
63 | historyIsValid(searchHistory) {
64 | return searchHistory.some((x) => {
65 | return typeof x === 'string';
66 | });
67 | }
68 |
69 | initialize() {
70 | this.engine = new SearchEngine();
71 | this.subscribe();
72 | }
73 |
74 | modeChange(strongMode) {
75 | this.strongMode = strongMode;
76 | this.saveStrongMode();
77 | queue.publish('search.strong-mode.update', this.strongMode);
78 | }
79 |
80 | modeToogle() {
81 | this.modeChange(!this.strongMode);
82 | }
83 |
84 | async queryChange(searchQuery) {
85 | const rig = await this.engine.performSearch(searchQuery);
86 | if (rig.state === 'ERROR') {
87 | let message;
88 | if (rig.type === 'EMPTY') {
89 | message = 'Enter a search expression.';
90 | } else if (rig.type === 'INVALID') {
91 | message = 'Invalid query expression.';
92 | } else if (rig.wordStatus !== 'OK') {
93 | message = rig.wordStatus;
94 | }
95 | this.searchQuery = '';
96 | this.rig = null;
97 | queue.publish('search.query.error', message);
98 | } else {
99 | this.rig = rig;
100 | this.searchQuery = searchQuery;
101 | this.saveQuery();
102 | this.addHistory();
103 | await this.updateSearchVerseObjs();
104 | queue.publish('rig.update', this.rig);
105 | this.resetFilter();
106 | queue.publish('search.query.update', this.searchQuery);
107 | }
108 | }
109 |
110 | resetFilter() {
111 | const filter = this.tomeFilter();
112 | this.filterChange(filter);
113 | }
114 |
115 | async restore() {
116 | this.restoreTask();
117 | this.restoreHistory();
118 | await this.restoreQuery();
119 | this.restoreFilter();
120 | this.restoreMode();
121 | }
122 |
123 | restoreFilter() {
124 | const defaultFilter = this.tomeFilter();
125 | let searchFilter = localStorage.getItem('searchFilter');
126 | if (!searchFilter) {
127 | searchFilter = defaultFilter;
128 | } else {
129 | try {
130 | searchFilter = JSON.parse(searchFilter);
131 | } catch (error) {
132 | searchFilter = defaultFilter;
133 | }
134 | if (!this.filterIsValid(searchFilter)) {
135 | searchFilter = defaultFilter;
136 | }
137 | }
138 | this.filterChange(searchFilter);
139 | }
140 |
141 | restoreHistory() {
142 | const defaultHistory = [];
143 | let searchHistory = localStorage.getItem('searchHistory');
144 | if (!searchHistory) {
145 | searchHistory = defaultHistory;
146 | } else {
147 | try {
148 | searchHistory = JSON.parse(searchHistory);
149 | } catch (error) {
150 | searchHistory = defaultHistory;
151 | }
152 | if (!Array.isArray(searchHistory)) {
153 | searchHistory = defaultHistory;
154 | } else {
155 | if (!this.historyIsValid(searchHistory)) {
156 | searchHistory = defaultHistory;
157 | }
158 | }
159 | }
160 | this.historyChange(searchHistory);
161 | }
162 |
163 | restoreMode() {
164 | const defaultMode = false;
165 | let strongMode = localStorage.getItem('searchStrongMode');
166 | if (!strongMode) {
167 | strongMode = defaultMode;
168 | } else {
169 | try {
170 | strongMode = JSON.parse(strongMode);
171 | } catch (error) {
172 | strongMode = defaultMode;
173 | }
174 | if (typeof strongMode !== 'boolean') {
175 | strongMode = defaultMode;
176 | }
177 | }
178 | this.modeChange(strongMode);
179 | }
180 |
181 | async restoreQuery() {
182 | const defaultQuery = DEFAULT_QUERY;
183 | let searchQuery = localStorage.getItem('searchQuery');
184 | if (!searchQuery) {
185 | searchQuery = defaultQuery;
186 | } else {
187 | try {
188 | searchQuery = JSON.parse(searchQuery);
189 | } catch (error) {
190 | searchQuery = defaultQuery;
191 | }
192 | if (typeof searchQuery !== 'string') {
193 | searchQuery = defaultQuery;
194 | }
195 | }
196 | await this.queryChange(searchQuery);
197 | }
198 |
199 | restoreTask() {
200 | const defaultTask = 'search-result';
201 | let searchTask = localStorage.getItem('searchTask');
202 | if (!searchTask) {
203 | searchTask = defaultTask;
204 | } else {
205 | searchTask = JSON.parse(searchTask);
206 | }
207 | if (searchResultReroute.includes(searchTask)) {
208 | searchTask = 'search-result';
209 | } else if (!validTasks.includes(searchTask)) {
210 | searchTask = defaultTask;
211 | }
212 | this.taskChange(searchTask);
213 | }
214 |
215 | saveFilter() {
216 | localStorage.setItem('searchFilter',
217 | JSON.stringify(this.searchFilter));
218 | }
219 |
220 | saveHistory() {
221 | localStorage.setItem('searchHistory',
222 | JSON.stringify(this.searchHistory));
223 | }
224 |
225 | saveStrongMode() {
226 | localStorage.setItem('searchStrongMode',
227 | JSON.stringify(this.strongMode));
228 | }
229 |
230 | saveQuery() {
231 | localStorage.setItem('searchQuery',
232 | JSON.stringify(this.searchQuery));
233 | }
234 |
235 | saveTask() {
236 | localStorage.setItem('searchTask',
237 | JSON.stringify(this.searchTask));
238 | }
239 |
240 | subscribe() {
241 | queue.subscribe('name-mode.update', () => {
242 | this.queryChange(this.searchQuery);
243 | });
244 |
245 | queue.subscribe('search.filter.change', (filter) => {
246 | this.filterChange(filter);
247 | });
248 |
249 | queue.subscribe('search.history.clear', () => {
250 | this.historyClear();
251 | });
252 | queue.subscribe('search.history.delete', (query) => {
253 | this.historyDelete(query);
254 | });
255 |
256 | queue.subscribe('search.query.change', async (query) => {
257 | await this.queryChange(query);
258 | });
259 |
260 | queue.subscribe('search.restore', async () => {
261 | await this.restore();
262 | });
263 | queue.subscribe('search.strong-mode.toggle', () => {
264 | this.modeToogle();
265 | });
266 | queue.subscribe('search.task.change', (searchTask) => {
267 | this.taskChange(searchTask);
268 | });
269 | }
270 |
271 | taskChange(searchTask) {
272 | this.searchTask = searchTask;
273 | this.saveTask();
274 | queue.publish('search.task.update', this.searchTask);
275 | }
276 |
277 | tomeFilter() {
278 | return {
279 | bookIdx: -1,
280 | chapterIdx: -1,
281 | };
282 | }
283 |
284 | updateHistory() {
285 | this.saveHistory();
286 | queue.publish('search.history.update', this.searchHistory);
287 | }
288 |
289 | async updateSearchVerseObjs() {
290 | if (this.rig === null) {
291 | return;
292 | }
293 | this.searchVerseObjs =
294 | await tomeDb.verses.bulkGet(this.rig.tomeBin[binIdx.tomeBinIdx.verses]);
295 | queue.publish('search.verse-objs.update', this.searchVerseObjs);
296 | }
297 |
298 | }
299 |
300 | export { SearchModel };
301 |
--------------------------------------------------------------------------------
/js/View/BookmarkExportView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { tomeLists } from '../data/tomeLists.js';
6 |
7 | const message = 'Select All and Copy the text below. ' +
8 | 'Then Paste in a text editor and save the file.';
9 |
10 | const dialogToolset = [
11 | { type: 'label', text: message },
12 | { type: 'textarea', ariaLabel: null, label: 'Bookmark Package' },
13 | ];
14 |
15 | const lowerToolSet = [
16 | { type: 'btn', icon: 'back', ariaLabel: null },
17 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
18 | ];
19 |
20 | const upperToolSet = [
21 | { type: 'banner', cssModifier: 'bookmark-export', text: 'Bookmark Export' },
22 | ];
23 |
24 | class BookmarkExportview {
25 |
26 | constructor() {
27 | this.initialize();
28 | }
29 |
30 | addListeners() {
31 | this.toolbarLower.addEventListener('click', (event) => {
32 | this.toolbarLowerClick(event);
33 | });
34 | }
35 |
36 | buildBookmarkPkg() {
37 | const bookmarkPkg = {};
38 | bookmarkPkg.tome = tomeLists.tomeName;
39 | bookmarkPkg.folders = [];
40 | for (const folder of this.folders) {
41 | const newFolder = {};
42 | newFolder.name = folder.name;
43 | newFolder.bookmarks = folder.bookmarks;
44 | bookmarkPkg.folders.push(newFolder);
45 | }
46 | return JSON.stringify(bookmarkPkg, null);
47 | }
48 |
49 | buildPage() {
50 | this.page = template.page('bookmark-export');
51 |
52 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
53 | this.page.appendChild(this.toolbarUpper);
54 |
55 | this.scroll = template.scroll('bookmark-export');
56 | this.dialog = template.divDialog('bookmark-export', dialogToolset);
57 | this.scroll.appendChild(this.dialog);
58 | this.page.appendChild(this.scroll);
59 |
60 | this.toolbarLower = template.toolbarLower(lowerToolSet);
61 | this.page.appendChild(this.toolbarLower);
62 |
63 | const container = document.querySelector('.container');
64 | container.appendChild(this.page);
65 | }
66 |
67 | foldersUpdate(folders) {
68 | this.folders = folders;
69 | }
70 |
71 | getElements() {
72 | this.textarea = this.scroll.querySelector('.dialog-textarea');
73 |
74 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
75 | this.btnBookmarkFolder = this.toolbarLower
76 | .querySelector('.btn-icon--bookmark-folder');
77 | }
78 |
79 | hide() {
80 | this.page.classList.add('page--hide');
81 | }
82 |
83 | initialize() {
84 | this.buildPage();
85 | this.getElements();
86 | this.addListeners();
87 | this.subscribe();
88 | }
89 |
90 | show() {
91 | this.page.classList.remove('page--hide');
92 | this.textarea.value = this.buildBookmarkPkg();
93 | }
94 |
95 | subscribe() {
96 | queue.subscribe('bookmark-export.hide', () => {
97 | this.hide();
98 | });
99 | queue.subscribe('bookmark-export.show', () => {
100 | this.show();
101 | });
102 |
103 | queue.subscribe('bookmark.folders.update', (folders) => {
104 | this.foldersUpdate(folders);
105 | });
106 | }
107 |
108 | toolbarLowerClick(event) {
109 | event.preventDefault();
110 | const btn = event.target.closest('div.btn-icon');
111 | if (btn) {
112 | if (btn === this.btnBack) {
113 | queue.publish('bookmark.back', null);
114 | } else if (btn === this.btnBookmarkFolder) {
115 | queue.publish('bookmark-folder', null);
116 | }
117 | }
118 | }
119 |
120 | }
121 |
122 | export { BookmarkExportview };
123 |
--------------------------------------------------------------------------------
/js/View/BookmarkFolderAddView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: 'Name' },
8 | { type: 'input', ariaLabel: 'Name' },
9 | { type: 'btn', cssModifier: 'save', ariaLabel: null, label: 'Save' },
10 | ];
11 |
12 | const lowerToolSet = [
13 | { type: 'btn', icon: 'back', ariaLabel: null },
14 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
15 | ];
16 |
17 | const upperToolSet = [
18 | { type: 'banner', cssModifier: 'bookmark-folder-add', text: 'Folder Add' },
19 | ];
20 |
21 | class BookmarkFolderAddView {
22 |
23 | constructor() {
24 | this.initialize();
25 | }
26 |
27 | addListeners() {
28 | this.dialogBtns.addEventListener('click', (event) => {
29 | this.dialogClick(event);
30 | });
31 | this.inputName.addEventListener('keydown', (event) => {
32 | this.inputKeyDown(event);
33 | });
34 | this.toolbarLower.addEventListener('click', (event) => {
35 | this.toolbarLowerClick(event);
36 | });
37 | }
38 |
39 | buildPage() {
40 | this.page = template.page('bookmark-folder-add');
41 |
42 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
43 | this.page.appendChild(this.toolbarUpper);
44 |
45 | this.scroll = template.scroll('bookmark-folder-add');
46 | this.dialog = template.divDialog('bookmark-folder-add', dialogToolset);
47 | this.scroll.appendChild(this.dialog);
48 |
49 | this.message = template.element('div', 'message', 'bookmark-folder-add',
50 | null, null);
51 | this.scroll.appendChild(this.message);
52 |
53 | this.page.appendChild(this.scroll);
54 |
55 | this.toolbarLower = template.toolbarLower(lowerToolSet);
56 | this.page.appendChild(this.toolbarLower);
57 |
58 | const container = document.querySelector('.container');
59 | container.appendChild(this.page);
60 | }
61 |
62 | dialogClick(event) {
63 | event.preventDefault();
64 | const btn = event.target.closest('div.btn-dialog');
65 | if (btn) {
66 | if (btn === this.btnSave) {
67 | this.saveClick();
68 | }
69 | }
70 | }
71 |
72 | error(message) {
73 | this.message.textContent = message;
74 | this.message.classList.remove('hide');
75 | }
76 |
77 | getElements() {
78 | this.inputName = this.dialog.querySelector('.dialog-input');
79 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
80 | this.btnSave = this.dialogBtns.querySelector('.btn-dialog--save');
81 |
82 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
83 | this.btnBookmarkFolder = this.toolbarLower
84 | .querySelector('.btn-icon--bookmark-folder');
85 | }
86 |
87 | hide() {
88 | this.page.classList.add('page--hide');
89 | }
90 |
91 | initialize() {
92 | this.buildPage();
93 | this.getElements();
94 | this.addListeners();
95 | this.subscribe();
96 | }
97 |
98 | inputKeyDown(event) {
99 | if (event.key === 'Enter') {
100 | this.inputName.blur();
101 | this.saveClick();
102 | }
103 | }
104 |
105 | saveClick() {
106 | const name = this.inputName.value;
107 | if (name) {
108 | queue.publish('bookmark-folder-add.save', name);
109 | }
110 | }
111 |
112 | show() {
113 | this.page.classList.remove('page--hide');
114 | this.message.classList.add('hide');
115 | this.inputName.value = '';
116 | this.inputName.focus();
117 | }
118 |
119 | subscribe() {
120 | queue.subscribe('bookmark-folder-add.hide', () => {
121 | this.hide();
122 | });
123 | queue.subscribe('bookmark-folder-add.show', () => {
124 | this.show();
125 | });
126 |
127 | queue.subscribe('bookmark.folder.add.error', (message) => {
128 | this.error(message);
129 | });
130 | }
131 |
132 | toolbarLowerClick(event) {
133 | event.preventDefault();
134 | const btn = event.target.closest('div.btn-icon');
135 | if (btn) {
136 | if (btn === this.btnBack) {
137 | queue.publish('bookmark.back', null);
138 | } else if (btn === this.btnBookmarkFolder) {
139 | queue.publish('bookmark-folder', null);
140 | }
141 | }
142 | }
143 |
144 | }
145 |
146 | export { BookmarkFolderAddView };
147 |
--------------------------------------------------------------------------------
/js/View/BookmarkFolderDeleteView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: null },
8 | { type: 'btn', cssModifier: 'delete', ariaLabel: null, label: 'Delete' },
9 | ];
10 |
11 | const lowerToolSet = [
12 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
13 | ];
14 |
15 | const upperToolSet = [{ type: 'banner', cssModifier: 'bookmark-folder-delete',
16 | text: 'Folder Delete' },];
17 |
18 | class BookmarkFolderDeleteView {
19 |
20 | constructor() {
21 | this.initialize();
22 | }
23 |
24 | addListeners() {
25 | this.dialogBtns.addEventListener('click', (event) => {
26 | this.dialogClick(event);
27 | });
28 | this.toolbarLower.addEventListener('click', (event) => {
29 | this.toolbarLowerClick(event);
30 | });
31 | }
32 |
33 | buildPage() {
34 | this.page = template.page('bookmark-folder-delete');
35 |
36 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
37 | this.page.appendChild(this.toolbarUpper);
38 |
39 | this.scroll = template.scroll('bookmark-folder-delete');
40 | this.dialog = template.divDialog('bookmark-folder-delete', dialogToolset);
41 | this.scroll.appendChild(this.dialog);
42 | this.page.appendChild(this.scroll);
43 |
44 | this.toolbarLower = template.toolbarLower(lowerToolSet);
45 | this.page.appendChild(this.toolbarLower);
46 |
47 | const container = document.querySelector('.container');
48 | container.appendChild(this.page);
49 | }
50 |
51 | deleteClick() {
52 | queue.publish('bookmark-folder-delete.confirm', this.folderName);
53 | }
54 |
55 | dialogClick(event) {
56 | event.preventDefault();
57 | const btn = event.target.closest('div.btn-dialog');
58 | if (btn) {
59 | if (btn === this.btnDelete) {
60 | this.deleteClick();
61 | }
62 | }
63 | }
64 |
65 | folderToDelete(folderName) {
66 | this.folderName = folderName;
67 | }
68 |
69 | getElements() {
70 | this.banner = this.toolbarUpper
71 | .querySelector('.banner--bookmark-folder-delete');
72 |
73 | this.label = this.dialog.querySelector('.dialog-label');
74 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
75 | this.btnDelete = this.dialogBtns.querySelector('.btn-dialog--delete');
76 |
77 | this.btnBookmarkFolder = this.toolbarLower
78 | .querySelector('.btn-icon--bookmark-folder');
79 | }
80 |
81 | hide() {
82 | this.page.classList.add('page--hide');
83 | }
84 |
85 | initialize() {
86 | this.buildPage();
87 | this.getElements();
88 | this.addListeners();
89 | this.subscribe();
90 | }
91 |
92 | show() {
93 | this.updateLabel();
94 | this.page.classList.remove('page--hide');
95 | }
96 |
97 | subscribe() {
98 | queue.subscribe('bookmark-folder-delete.hide', () => {
99 | this.hide();
100 | });
101 | queue.subscribe('bookmark-folder-delete.show', () => {
102 | this.show();
103 | });
104 |
105 | queue.subscribe('folder.to.delete', (folderName) => {
106 | this.folderToDelete(folderName);
107 | });
108 | }
109 |
110 | toolbarLowerClick(event) {
111 | event.preventDefault();
112 | const btn = event.target.closest('div.btn-icon');
113 | if (btn) {
114 | if (btn === this.btnBookmarkFolder) {
115 | queue.publish('bookmark-folder', null);
116 | }
117 | }
118 | }
119 |
120 | updateLabel() {
121 | this.label.innerHTML = `Delete Folder '${this.folderName}'?`;
122 | }
123 |
124 | }
125 |
126 | export { BookmarkFolderDeleteView };
127 |
--------------------------------------------------------------------------------
/js/View/BookmarkFolderRenameView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: 'Folder Name' },
8 | { type: 'input', ariaLabel: 'Name' },
9 | { type: 'btn', cssModifier: 'save', ariaLabel: null, label: 'Save' },
10 | ];
11 |
12 | const lowerToolSet = [
13 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
14 | ];
15 |
16 | const upperToolSet = [{type: 'banner', cssModifier: 'bookmark-folder-rename',
17 | text: 'Folder Rename'},];
18 |
19 | class BookmarkFolderRenameView {
20 |
21 | constructor() {
22 | this.initialize();
23 | }
24 |
25 | addListeners() {
26 | this.dialogBtns.addEventListener('click', (event) => {
27 | this.dialogClick(event);
28 | });
29 | this.inputName.addEventListener('keydown', (event) => {
30 | this.inputKeyDown(event);
31 | });
32 | this.toolbarLower.addEventListener('click', (event) => {
33 | this.toolbarLowerClick(event);
34 | });
35 | }
36 |
37 | buildPage() {
38 | this.page = template.page('bookmark-folder-rename');
39 |
40 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
41 | this.page.appendChild(this.toolbarUpper);
42 |
43 | this.scroll = template.scroll('bookmark-folder-rename');
44 | this.dialog = template.divDialog('bookmark-folder-rename', dialogToolset);
45 | this.scroll.appendChild(this.dialog);
46 |
47 | this.message = template.element('div', 'message',
48 | 'bookmark-folder-rename', null, null);
49 | this.scroll.appendChild(this.message);
50 |
51 | this.page.appendChild(this.scroll);
52 |
53 | this.toolbarLower = template.toolbarLower(lowerToolSet);
54 | this.page.appendChild(this.toolbarLower);
55 |
56 | const container = document.querySelector('.container');
57 | container.appendChild(this.page);
58 | }
59 |
60 | dialogClick(event) {
61 | event.preventDefault();
62 | const btn = event.target.closest('div.btn-dialog');
63 | if (btn) {
64 | if (btn === this.btnSave) {
65 | this.saveClick();
66 | }
67 | }
68 | }
69 |
70 | error(message) {
71 | this.message.textContent = message;
72 | this.message.classList.remove('hide');
73 | }
74 |
75 | folderToRename(folderName) {
76 | this.folderName = folderName;
77 | }
78 |
79 | getElements() {
80 | this.inputName = this.dialog.querySelector('.dialog-input');
81 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
82 | this.btnSave = this.dialogBtns.querySelector('.btn-dialog--save');
83 |
84 | this.btnBookmarkFolder = this.toolbarLower
85 | .querySelector('.btn-icon--bookmark-folder');
86 | }
87 |
88 | hide() {
89 | this.page.classList.add('page--hide');
90 | }
91 |
92 | initialize() {
93 | this.buildPage();
94 | this.getElements();
95 | this.getElements();
96 | this.addListeners();
97 | this.subscribe();
98 | }
99 |
100 | inputKeyDown(event) {
101 | if (event.key === 'Enter') {
102 | this.inputName.blur();
103 | this.saveClick();
104 | }
105 | }
106 |
107 | saveClick() {
108 | const name = this.inputName.value;
109 | if (name) {
110 | this.namePkg.new = name;
111 | queue.publish('bookmark-folder-rename.save', this.namePkg);
112 | }
113 | }
114 |
115 | show() {
116 | this.page.classList.remove('page--hide');
117 | this.message.classList.add('hide');
118 | this.namePkg = {
119 | old: this.folderName,
120 | };
121 | this.inputName.value = this.folderName;
122 | this.inputName.focus();
123 | }
124 |
125 | subscribe() {
126 | queue.subscribe('bookmark-folder-rename.hide', () => {
127 | this.hide();
128 | });
129 | queue.subscribe('bookmark-folder-rename.show', (folderName) => {
130 | this.show(folderName);
131 | });
132 |
133 | queue.subscribe('bookmark.folder.rename.error', (message) => {
134 | this.error(message);
135 | });
136 |
137 | queue.subscribe('folder.to.rename', (folderName) => {
138 | this.folderToRename(folderName);
139 | });
140 | }
141 |
142 | toolbarLowerClick(event) {
143 | event.preventDefault();
144 | const btn = event.target.closest('div.btn-icon');
145 | if (btn) {
146 | if (btn === this.btnBookmarkFolder) {
147 | queue.publish('bookmark-folder', null);
148 | }
149 | }
150 | }
151 |
152 | }
153 |
154 | export { BookmarkFolderRenameView };
155 |
--------------------------------------------------------------------------------
/js/View/BookmarkFolderView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 |
7 | const actionSet = [
8 | { icon: 'up', ariaLabel: null },
9 | { icon: 'down', ariaLabel: null },
10 | { icon: 'rename', ariaLabel: null },
11 | { icon: 'delete', ariaLabel: null },
12 | { icon: 'cancel', ariaLabel: null },
13 | ];
14 |
15 | const lowerToolSet = [
16 | { type: 'btn', icon: 'back', ariaLabel: null },
17 | { type: 'btn', icon: 'bookmark-folder-add', ariaLabel: null },
18 | { type: 'btn', icon: 'import', ariaLabel: null },
19 | { type: 'btn', icon: 'export', ariaLabel: null },
20 | { type: 'btn', icon: 'bookmark-list', ariaLabel: null },
21 | ];
22 |
23 | const upperToolSet = [
24 | { type: 'banner', cssModifier: 'bookmark-folder', text: 'Bookmark Folder' },
25 | ];
26 |
27 | class BookmarkFolderView {
28 |
29 | constructor() {
30 | this.initialize();
31 | }
32 |
33 | actionMenuClick(event) {
34 | event.preventDefault();
35 | const btn = event.target.closest('div.btn-icon');
36 | if (btn) {
37 | if (btn === this.btnCancel) {
38 | this.actionMenu.classList.add('hide');
39 | } else {
40 | const entry = this.activeEntry.querySelector('.btn-entry');
41 | const folderName = entry.textContent;
42 | if (btn === this.btnDelete) {
43 | this.delete(folderName);
44 | } else if (btn === this.btnDown) {
45 | this.down(folderName);
46 | } else if (btn === this.btnRename) {
47 | this.rename(folderName);
48 | } else if (btn === this.btnUp) {
49 | this.up(folderName);
50 | }
51 | this.actionMenu.classList.add('hide');
52 | }
53 | }
54 | }
55 |
56 | addListeners() {
57 | this.actionMenu.addEventListener('click', (event) => {
58 | this.actionMenuClick(event);
59 | });
60 | this.list.addEventListener('click', (event) => {
61 | this.listClick(event);
62 | });
63 | this.toolbarLower.addEventListener('click', (event) => {
64 | this.toolbarLowerClick(event);
65 | });
66 | }
67 |
68 | buildEntry(folderName) {
69 | const entry = document.createElement('div');
70 | entry.classList.add('entry', 'entry--folder');
71 | const btnEntry = document.createElement('div');
72 | btnEntry.classList.add('btn-entry', 'btn-entry--folder');
73 | btnEntry.textContent = folderName;
74 | const btnMenu = template.btnIcon('h-menu', 'h-menu', null);
75 | entry.appendChild(btnEntry);
76 | entry.appendChild(btnMenu);
77 | return entry;
78 | }
79 |
80 | buildPage() {
81 | this.page = template.page('bookmark-folder');
82 |
83 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
84 | this.page.appendChild(this.toolbarUpper);
85 |
86 | this.scroll = template.scroll('bookmark-folder');
87 |
88 | this.list = template.element('div', 'list', 'bookmark-folder', null, null);
89 | this.scroll.appendChild(this.list);
90 |
91 | this.actionMenu = template.actionMenu('bookmark-folder', actionSet);
92 | this.scroll.appendChild(this.actionMenu);
93 | this.page.appendChild(this.scroll);
94 |
95 | this.toolbarLower = template.toolbarLower(lowerToolSet);
96 | this.page.appendChild(this.toolbarLower);
97 |
98 | const container = document.querySelector('.container');
99 | container.appendChild(this.page);
100 | }
101 |
102 | delete(folderName) {
103 | queue.publish('bookmark-folder.delete', folderName);
104 | }
105 |
106 | down(folderName) {
107 | queue.publish('bookmark-folder.down', folderName);
108 | }
109 |
110 | folderListUpdate(folderList) {
111 | this.folderList = folderList;
112 | this.updateFolders();
113 | }
114 |
115 | getElements() {
116 | this.btnUp = this.actionMenu.querySelector('.btn-icon--up');
117 | this.btnDown = this.actionMenu.querySelector('.btn-icon--down');
118 | this.btnRename = this.actionMenu.querySelector('.btn-icon--rename');
119 | this.btnDelete = this.actionMenu.querySelector('.btn-icon--delete');
120 | this.btnCancel = this.actionMenu.querySelector('.btn-icon--cancel');
121 |
122 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
123 | this.btnBookmarkList = this.toolbarLower
124 | .querySelector('.btn-icon--bookmark-list');
125 | this.btnBookmarkFolderAdd = this.toolbarLower
126 | .querySelector('.btn-icon--bookmark-folder-add');
127 | this.btnImport = this.toolbarLower.querySelector('.btn-icon--import');
128 | this.btnExport = this.toolbarLower.querySelector('.btn-icon--export');
129 | }
130 |
131 | hide() {
132 | this.page.classList.add('page--hide');
133 | this.actionMenu.classList.add('hide');
134 | }
135 |
136 | initialize() {
137 | this.buildPage();
138 | this.getElements();
139 | this.addListeners();
140 | this.subscribe();
141 | }
142 |
143 | listClick(event) {
144 | event.preventDefault();
145 | const btn = event.target.closest('div');
146 | if (btn) {
147 | if (btn.classList.contains('btn-entry')) {
148 | const folderName = btn.textContent;
149 | queue.publish('bookmark-folder.select', folderName);
150 | } else if (btn.classList.contains('btn-icon--h-menu')) {
151 | const entry = btn.previousSibling;
152 | this.menuClick(entry);
153 | }
154 | }
155 | }
156 |
157 | menuClick(target) {
158 | this.showActionMenu(target);
159 | }
160 |
161 | rename(folderName) {
162 | queue.publish('bookmark-folder-rename', folderName);
163 | }
164 |
165 | show() {
166 | this.page.classList.remove('page--hide');
167 | }
168 |
169 | showActionMenu(target) {
170 | this.activeEntry = target.closest('div.entry');
171 | const top = target.offsetTop;
172 | this.actionMenu.style.top = `${top}px`;
173 | this.actionMenu.classList.remove('hide');
174 | }
175 |
176 | subscribe() {
177 | queue.subscribe('bookmark-folder.hide', () => {
178 | this.hide();
179 | });
180 | queue.subscribe('bookmark-folder.show', () => {
181 | this.show();
182 | });
183 |
184 | queue.subscribe('bookmark.folder-list.update', (folderList) => {
185 | this.folderListUpdate(folderList);
186 | });
187 | }
188 |
189 | toolbarLowerClick(event) {
190 | event.preventDefault();
191 | const btn = event.target.closest('div.btn-icon');
192 | if (btn) {
193 | if (btn === this.btnBack) {
194 | queue.publish('bookmark.back', null);
195 | } else if (btn === this.btnBookmarkList) {
196 | queue.publish('bookmark-list', null);
197 | } else if (btn === this.btnBookmarkFolderAdd) {
198 | queue.publish('bookmark-folder-add', null);
199 | } else if (btn === this.btnExport) {
200 | queue.publish('bookmark-export', null);
201 | } else if (btn === this.btnImport) {
202 | queue.publish('bookmark-import', null);
203 | }
204 | }
205 | }
206 |
207 | up(folderName) {
208 | queue.publish('bookmark-folder.up', folderName);
209 | }
210 |
211 | updateFolders() {
212 | const scrollSave = this.scroll.scrollTop;
213 | util.removeAllChildren(this.list);
214 | const fragment = document.createDocumentFragment();
215 | for (const folderName of this.folderList) {
216 | const entry = this.buildEntry(folderName);
217 | fragment.appendChild(entry);
218 | }
219 | this.list.appendChild(fragment);
220 | this.scroll.scrollTop = scrollSave;
221 | }
222 |
223 | }
224 |
225 | export { BookmarkFolderView };
226 |
--------------------------------------------------------------------------------
/js/View/BookmarkImportView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: 'Paste Bookmark Package Here:' },
8 | { type: 'textarea', ariaLabel: 'Bookmark Package' },
9 | { type: 'btn', cssModifier: 'import', ariaLabel: null, label: 'Import' },
10 | ];
11 |
12 | const lowerToolSet = [
13 | { type: 'btn', icon: 'back', ariaLabel: null },
14 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
15 | ];
16 |
17 | const upperToolSet = [
18 | { type: 'banner', cssModifier: 'bookmark-import', text: 'Bookmark Import' },
19 | ];
20 |
21 | class BookmarkImportView {
22 |
23 | constructor() {
24 | this.initialize();
25 | }
26 |
27 | addListeners() {
28 | this.dialogBtns.addEventListener('click', (event) => {
29 | this.dialogClick(event);
30 | });
31 | this.toolbarLower.addEventListener('click', (event) => {
32 | this.toolbarLowerClick(event);
33 | });
34 | }
35 |
36 | buildPage() {
37 | this.page = template.page('bookmark-import');
38 |
39 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
40 | this.page.appendChild(this.toolbarUpper);
41 |
42 | this.scroll = template.scroll('bookmark-import');
43 | this.dialog = template.divDialog('bookmark-import', dialogToolset);
44 | this.scroll.appendChild(this.dialog);
45 |
46 | this.message = template.element('div', 'message', 'bookmark-import',
47 | null, null);
48 | this.scroll.appendChild(this.message);
49 | this.page.appendChild(this.scroll);
50 |
51 | this.toolbarLower = template.toolbarLower(lowerToolSet);
52 | this.page.appendChild(this.toolbarLower);
53 |
54 | const container = document.querySelector('.container');
55 | container.appendChild(this.page);
56 | }
57 |
58 | dialogClick(event) {
59 | event.preventDefault();
60 | const btn = event.target.closest('div.btn-dialog');
61 | if (btn) {
62 | if (btn === this.btnImport) {
63 | this.importClick();
64 | }
65 | }
66 | }
67 |
68 | error(message) {
69 | this.message.textContent = message;
70 | this.message.classList.remove('hide');
71 | if (message === 'Import successful.') {
72 | this.textarea.value = '';
73 | }
74 | }
75 |
76 | getElements() {
77 | this.textarea = this.scroll.querySelector('.dialog-textarea');
78 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
79 | this.btnImport = this.dialogBtns.querySelector('.btn-dialog--import');
80 |
81 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
82 | this.btnBookmarkFolder = this.toolbarLower
83 | .querySelector('.btn-icon--bookmark-folder');
84 | }
85 |
86 | importClick() {
87 | this.message.textContent = '';
88 | const pkgStr = this.textarea.value;
89 | if (pkgStr) {
90 | queue.publish('bookmark-import.import', pkgStr);
91 | }
92 | }
93 |
94 | hide() {
95 | this.page.classList.add('page--hide');
96 | }
97 |
98 | initialize() {
99 | this.buildPage();
100 | this.getElements();
101 | this.addListeners();
102 | this.subscribe();
103 | }
104 |
105 | show() {
106 | this.textarea.value = '';
107 | this.message.textContent = '';
108 | this.message.classList.add('hide');
109 | this.page.classList.remove('page--hide');
110 | }
111 |
112 | subscribe() {
113 | queue.subscribe('bookmark-import.hide', () => {
114 | this.hide();
115 | });
116 | queue.subscribe('bookmark-import.message', (message) => {
117 | this.error(message);
118 | });
119 | queue.subscribe('bookmark-import.show', () => {
120 | this.show();
121 | });
122 | }
123 |
124 | toolbarLowerClick(event) {
125 | event.preventDefault();
126 | const btn = event.target.closest('div.btn-icon');
127 | if (btn) {
128 | if (btn === this.btnBack) {
129 | queue.publish('bookmark.back', null);
130 | } else if (btn === this.btnBookmarkFolder) {
131 | queue.publish('bookmark-folder', null);
132 | }
133 | }
134 | }
135 |
136 | }
137 |
138 | export { BookmarkImportView };
139 |
--------------------------------------------------------------------------------
/js/View/BookmarkMoveCopyView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 | import { tomeIdx } from '../data/tomeIdx.js';
7 |
8 | const actionSet = [
9 | { icon: 'move', ariaLabel: null },
10 | { icon: 'copy', ariaLabel: null },
11 | { icon: 'cancel', ariaLabel: null },
12 | ];
13 |
14 | const lowerToolSet = [
15 | { type: 'btn', icon: 'bookmark-folder', ariaLabel: null },
16 | ];
17 |
18 | const upperToolSet = [
19 | { type: 'banner', cssModifier: 'bookmark-move-copy', text: null },
20 | ];
21 |
22 | class BookmarkMoveCopyView {
23 |
24 | constructor() {
25 | this.initialize();
26 | }
27 |
28 | actionMenuClick(event) {
29 | event.preventDefault();
30 | const btn = event.target.closest('div.btn-icon');
31 | if (btn) {
32 | if (btn === this.btnCancel) {
33 | this.actionMenu.classList.add('hide');
34 | } else {
35 | const entry = this.activeEntry.querySelector('.btn-entry');
36 | const folderName = entry.textContent;
37 | if (btn === this.btnCopy) {
38 | this.copy(folderName);
39 | } else if (btn === this.btnMove) {
40 | this.move(folderName);
41 | }
42 | this.actionMenu.classList.add('hide');
43 | }
44 | }
45 | }
46 |
47 | addListeners() {
48 | this.actionMenu.addEventListener('click', (event) => {
49 | this.actionMenuClick(event);
50 | });
51 | this.list.addEventListener('click', (event) => {
52 | this.listClick(event);
53 | });
54 | this.toolbarLower.addEventListener('click', (event) => {
55 | this.toolbarLowerClick(event);
56 | });
57 | }
58 |
59 | buildEntry(folderName) {
60 | const entry = document.createElement('div');
61 | entry.classList.add('entry', 'entry--bookmark-move-copy');
62 | const btnEntry = document.createElement('div');
63 | btnEntry.classList.add('btn-entry', 'btn-entry--bookmark-move-copy');
64 | btnEntry.textContent = folderName;
65 | const btnMenu = template.btnIcon('h-menu', 'h-menu', null);
66 | entry.appendChild(btnEntry);
67 | entry.appendChild(btnMenu);
68 | return entry;
69 | }
70 |
71 | buildPage() {
72 | this.page = template.page('bookmark-move-copy');
73 |
74 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
75 | this.page.appendChild(this.toolbarUpper);
76 |
77 | this.scroll = template.scroll('bookmark-move-copy');
78 |
79 | this.empty = template.element('div', 'empty', 'bookmark-move-copy', null,
80 | 'No Target Folder');
81 | this.scroll.appendChild(this.empty);
82 |
83 | this.list = template.element('div', 'list', 'bookmark-move-copy', null,
84 | null);
85 | this.scroll.appendChild(this.list);
86 |
87 | this.actionMenu = template.actionMenu('bookmark-move-copy', actionSet);
88 | this.scroll.appendChild(this.actionMenu);
89 | this.page.appendChild(this.scroll);
90 |
91 | this.toolbarLower = template.toolbarLower(lowerToolSet);
92 | this.page.appendChild(this.toolbarLower);
93 |
94 | const container = document.querySelector('.container');
95 | container.appendChild(this.page);
96 | }
97 |
98 | copy(folderName) {
99 | const copyPkg = {
100 | to: folderName,
101 | verseIdx: this.verseIdx,
102 | };
103 | queue.publish('bookmark-move-copy.copy', copyPkg);
104 | }
105 |
106 | folderUpdate(bookmarksFolder) {
107 | this.bookmarksFolder = bookmarksFolder;
108 | }
109 |
110 | getElements() {
111 | this.banner = this.toolbarUpper
112 | .querySelector('.banner--bookmark-move-copy');
113 |
114 | this.btnMove = this.actionMenu.querySelector('.btn-icon--move');
115 | this.btnCopy = this.actionMenu.querySelector('.btn-icon--copy');
116 | this.btnCancel = this.actionMenu.querySelector('.btn-icon--cancel');
117 |
118 | this.btnBookmarkFolder = this.toolbarLower
119 | .querySelector('.btn-icon--bookmark-folder');
120 | }
121 |
122 | hide() {
123 | this.page.classList.add('page--hide');
124 | this.actionMenu.classList.add('hide');
125 | }
126 |
127 | initialize() {
128 | this.buildPage();
129 | this.getElements();
130 | this.addListeners();
131 | this.subscribe();
132 | }
133 |
134 | listClick(event) {
135 | event.preventDefault();
136 | const btn = event.target.closest('div.btn-icon');
137 | if (btn) {
138 | if (btn.classList.contains('btn-icon--h-menu')) {
139 | const entry = btn.previousSibling;
140 | this.menuClick(entry);
141 | }
142 | }
143 | }
144 |
145 | listUpdate(moveCopyList) {
146 | this.moveCopyList = moveCopyList;
147 | this.updateFolders();
148 | }
149 |
150 | menuClick(target) {
151 | this.showActionMenu(target);
152 | }
153 |
154 | move(folderName) {
155 | const movePkg = {
156 | to: folderName,
157 | verseIdx: this.verseIdx,
158 | };
159 | queue.publish('bookmark-move-copy.move', movePkg);
160 | }
161 |
162 | moveCopyUpdate(verseObj) {
163 | this.moveCopyVerseObj = verseObj;
164 | this.verseIdx = this.moveCopyVerseObj.k;
165 | this.verse = this.moveCopyVerseObj.v;
166 | queue.publish('bookmark-move-copy.ready', null);
167 | }
168 |
169 | show() {
170 | this.updateBanner();
171 | this.page.classList.remove('page--hide');
172 | }
173 |
174 | showActionMenu(target) {
175 | this.activeEntry = target.closest('div.entry');
176 | const top = target.offsetTop;
177 | this.actionMenu.style.top = `${top}px`;
178 | this.actionMenu.classList.remove('hide');
179 | }
180 |
181 | subscribe() {
182 | queue.subscribe('bookmark.active-folder.update', (bookmarksFolder) => {
183 | this.folderUpdate(bookmarksFolder);
184 | });
185 |
186 | queue.subscribe('bookmark-move-copy.hide', () => {
187 | this.hide();
188 | });
189 | queue.subscribe('bookmark-move-copy.list.update', (moveCopyList) => {
190 | this.listUpdate(moveCopyList);
191 | });
192 | queue.subscribe('bookmark-move-copy.show', () => {
193 | this.show();
194 | });
195 |
196 | queue.subscribe('bookmark.move-copy.update', (verseObj) => {
197 | this.moveCopyUpdate(verseObj);
198 | });
199 | }
200 |
201 | toolbarLowerClick(event) {
202 | event.preventDefault();
203 | const btn = event.target.closest('div.btn-icon');
204 | if (btn) {
205 | if (btn === this.btnBookmarkFolder) {
206 | queue.publish('bookmark-folder', null);
207 | }
208 | }
209 | }
210 |
211 | updateBanner() {
212 | const ref = this.verse[tomeIdx.verse.citation];
213 | this.banner.innerHTML = `${ref} Move/Copy to Folder:`;
214 | }
215 |
216 | updateFolders() {
217 | const scrollSave = this.scroll.scrollTop;
218 | util.removeAllChildren(this.list);
219 | if (this.moveCopyList.length === 0) {
220 | this.empty.classList.remove('hide');
221 | } else {
222 | this.empty.classList.add('hide');
223 | const fragment = document.createDocumentFragment();
224 | for (const folderName of this.moveCopyList) {
225 | const entry = this.buildEntry(folderName);
226 | fragment.appendChild(entry);
227 | }
228 | this.list.appendChild(fragment);
229 | }
230 | this.scroll.scrollTop = scrollSave;
231 | }
232 |
233 | }
234 |
235 | export { BookmarkMoveCopyView };
236 |
--------------------------------------------------------------------------------
/js/View/HelpReadView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { helpTopicList } from '../View/HelpTopicView.js';
6 |
7 | const lowerToolSet = [
8 | { type: 'btn', icon: 'back', ariaLabel: null },
9 | { type: 'btn', icon: 'help-topic', ariaLabel: null },
10 | ];
11 |
12 | const upperToolSet = [
13 | { type: 'banner', cssModifier: 'help-read', text: null },
14 | ];
15 |
16 | class HelpReadView {
17 |
18 | constructor() {
19 | this.initialize();
20 | }
21 |
22 | addListeners() {
23 | this.toolbarLower.addEventListener('click', (event) => {
24 | this.toolbarLowerClick(event);
25 | });
26 | }
27 |
28 | buildPage() {
29 | this.page = template.page('help-read');
30 |
31 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
32 | this.page.appendChild(this.toolbarUpper);
33 |
34 | this.scroll = template.scroll('help-read');
35 | this.page.appendChild(this.scroll);
36 |
37 | this.toolbarLower = template.toolbarLower(lowerToolSet);
38 | this.page.appendChild(this.toolbarLower);
39 |
40 | const container = document.querySelector('.container');
41 | container.appendChild(this.page);
42 | }
43 |
44 | getElements() {
45 | this.banner = this.toolbarUpper.querySelector('.banner--help-read');
46 |
47 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
48 | this.btnHelpTopic = this.toolbarLower
49 | .querySelector('.btn-icon--help-topic');
50 | }
51 |
52 | hide() {
53 | this.page.classList.add('page--hide');
54 | }
55 |
56 | initialize() {
57 | this.buildPage();
58 | this.getElements();
59 | this.addListeners();
60 | this.subscribe();
61 | }
62 |
63 | show() {
64 | this.page.classList.remove('page--hide');
65 | }
66 |
67 | subscribe() {
68 | queue.subscribe('help-read.show', () => {
69 | this.show();
70 | });
71 | queue.subscribe('help-read.hide', () => {
72 | this.hide();
73 | });
74 |
75 | queue.subscribe('help.topic.update', (helpTopic) => {
76 | this.topicUpdate(helpTopic);
77 | });
78 | }
79 |
80 | toolbarLowerClick(event) {
81 | event.preventDefault();
82 | const btn = event.target.closest('div.btn-icon');
83 | if (btn) {
84 | if (btn === this.btnBack) {
85 | queue.publish('help.back', null);
86 | } else if (btn === this.btnHelpTopic) {
87 | queue.publish('help-topic', null);
88 | }
89 | }
90 | }
91 |
92 | topicUpdate(helpTopic) {
93 | this.updateBanner(helpTopic);
94 | this.scroll.innerHTML = '';
95 | const url = `help/${helpTopic}.html`;
96 | fetch(url).then((response) => {
97 | if (response.ok) {
98 | return response.text();
99 | } else {
100 | throw new Error('fetch failed');
101 | }
102 | }).then((html) => {
103 | this.scroll.innerHTML = html;
104 | this.scroll.scrollTop = 0;
105 | }).catch((error) => {
106 | console.log(error.message);
107 | });
108 | }
109 |
110 | updateBanner(helpTopic) {
111 | const title = helpTopicList.find(obj => obj.topic === helpTopic).name;
112 | this.banner.textContent = title;
113 | }
114 |
115 | }
116 |
117 | export { HelpReadView };
118 |
--------------------------------------------------------------------------------
/js/View/HelpTopicView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const lowerToolSet = [
7 | { type: 'btn', icon: 'back', ariaLabel: null },
8 | { type: 'btn', icon: 'help-read', ariaLabel: null },
9 | ];
10 |
11 | const upperToolSet = [
12 | { type: 'banner', cssModifier: 'topic', text: 'Topic' },
13 | ];
14 |
15 | export const helpTopicList = [
16 | { topic: 'about', name: 'About' },
17 | { topic: 'overview', name: 'Overview' },
18 | { topic: 'read', name: 'Read' },
19 | { topic: 'clipboard-mode', name: 'Clipboard Mode' },
20 | { topic: 'name-mode', name: 'Name Mode' },
21 | { topic: 'navigator', name: 'Navigator' },
22 | { topic: 'bookmark', name: 'Bookmark' },
23 | { topic: 'search', name: 'Search' },
24 | { topic: 'strong', name: 'Strong' },
25 | { topic: 'setting', name: 'Setting' },
26 | { topic: 'help', name: 'Help' },
27 | ];
28 |
29 | const templateBtnTopic = (helpTopic) => {
30 | const btnTopic = template.element('div', 'btn-topic', helpTopic.topic, null,
31 | helpTopic.name);
32 | btnTopic.dataset.topic = helpTopic.topic;
33 | return btnTopic;
34 | };
35 |
36 | const templateListTopic = () => {
37 | const list = template.element('div', 'list', 'topic', null, null);
38 | for (const topic of helpTopicList) {
39 | const btn = templateBtnTopic(topic);
40 | list.appendChild(btn);
41 | }
42 | return list;
43 | };
44 |
45 | class HelpTopicView {
46 |
47 | constructor() {
48 | this.initialize();
49 | }
50 |
51 | addListeners() {
52 | this.scroll.addEventListener('click', (event) => {
53 | this.scrollClick(event);
54 | });
55 | this.toolbarLower.addEventListener('click', (event) => {
56 | this.toolbarLowerClick(event);
57 | });
58 | }
59 |
60 | buildPage() {
61 | this.page = template.page('help-topic');
62 |
63 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
64 | this.page.appendChild(this.toolbarUpper);
65 |
66 | this.scroll = template.scroll('help-topic');
67 | this.list = templateListTopic();
68 | this.scroll.appendChild(this.list);
69 |
70 | this.page.appendChild(this.scroll);
71 |
72 | this.toolbarLower = template.toolbarLower(lowerToolSet);
73 | this.page.appendChild(this.toolbarLower);
74 |
75 | const container = document.querySelector('.container');
76 | container.appendChild(this.page);
77 | }
78 |
79 | getElements() {
80 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
81 | this.btnHelpRead = this.toolbarLower.querySelector('.btn-icon--help-read');
82 | }
83 |
84 | hide() {
85 | this.page.classList.add('page--hide');
86 | }
87 |
88 | initialize() {
89 | this.buildPage();
90 | this.getElements();
91 | this.addListeners();
92 | this.subscribe();
93 | }
94 |
95 | scrollClick(event) {
96 | event.preventDefault();
97 | const btn = event.target.closest('div.btn-topic');
98 | if (btn) {
99 | if (btn.classList.contains('btn-topic')) {
100 | const helpTopic = btn.dataset.topic;
101 | queue.publish('help-topic.select', helpTopic);
102 | }
103 | }
104 | }
105 |
106 | show() {
107 | this.page.classList.remove('page--hide');
108 | }
109 |
110 | subscribe() {
111 | queue.subscribe('help-topic.show', () => {
112 | this.show();
113 | });
114 | queue.subscribe('help-topic.hide', () => {
115 | this.hide();
116 | });
117 | }
118 |
119 | toolbarLowerClick(event) {
120 | event.preventDefault();
121 | const btn = event.target.closest('div.btn-icon');
122 | if (btn) {
123 | if (btn === this.btnBack) {
124 | queue.publish('help.back', null);
125 | } else if (btn === this.btnHelpRead) {
126 | queue.publish('help-read', null);
127 | }
128 | }
129 | }
130 |
131 | }
132 |
133 | export { HelpTopicView };
134 |
--------------------------------------------------------------------------------
/js/View/NavigatorBookView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { tomeIdx } from '../data/tomeIdx.js';
6 | import { tomeLists } from '../data/tomeLists.js';
7 |
8 | const greekFirstIdx = 39;
9 | const indices = [...Array(66).keys()];
10 |
11 | const lowerToolSet = [
12 | { type: 'btn', icon: 'back', ariaLabel: null },
13 | { type: 'btn', icon: 'navigator-chapter', ariaLabel: null },
14 | ];
15 |
16 | const upperToolSet = [
17 | { type: 'banner', cssModifier: 'navigator-book', text: 'Book' },
18 | ];
19 |
20 | class NavigatorBookView {
21 |
22 | constructor() {
23 | this.initialize();
24 | }
25 |
26 | addListeners() {
27 | this.list.addEventListener('click', (event) => {
28 | this.listClick(event);
29 | });
30 | this.toolbarLower.addEventListener('click', (event) => {
31 | this.toolbarLowerClick(event);
32 | });
33 | }
34 |
35 | bookIdxUpdate(bookIdx) {
36 | let activeBtn = this.list.querySelector('.btn-book--active');
37 | if (activeBtn) {
38 | activeBtn.classList.remove('btn-book--active');
39 | }
40 | const selector = `.btn-book[data-book-idx="${bookIdx}"]`;
41 | activeBtn = this.list.querySelector(selector);
42 | activeBtn.classList.add('btn-book--active');
43 | }
44 |
45 | buildBookDivider() {
46 | const divider = document.createElement('hr');
47 | divider.classList.add('book-divider');
48 | return divider;
49 | }
50 |
51 | buildBookList() {
52 | const booksHebrew = this.buildHebrewList();
53 | const booksGreek = this.buildGreekList();
54 | const divider = this.buildBookDivider();
55 | this.list.appendChild(booksHebrew);
56 | this.list.appendChild(divider);
57 | this.list.appendChild(booksGreek);
58 | }
59 |
60 | buildBtnBook(bookIdx) {
61 | const btn = document.createElement('div');
62 | btn.classList.add('btn-book');
63 | btn.dataset.bookIdx = bookIdx;
64 | btn.textContent = tomeLists.books[bookIdx][tomeIdx.book.shortName];
65 | return btn;
66 | }
67 |
68 | buildGreekList() {
69 | const booksGreek = document.createElement('div');
70 | booksGreek.classList.add('content', 'content--greek-book');
71 | const greekIndices = indices.slice(greekFirstIdx);
72 | for (const idx of greekIndices) {
73 | const btn = this.buildBtnBook(idx);
74 | booksGreek.appendChild(btn);
75 | }
76 | return booksGreek;
77 | }
78 |
79 | buildHebrewList() {
80 | const booksHebrew = document.createElement('div');
81 | booksHebrew.classList.add('content', 'content--hebrew-book');
82 | const hebrewIndices = indices.slice(0, greekFirstIdx);
83 | for (const idx of hebrewIndices) {
84 | const btn = this.buildBtnBook(idx);
85 | booksHebrew.appendChild(btn);
86 | }
87 | return booksHebrew;
88 | }
89 |
90 | buildPage() {
91 | this.page = template.page('navigator-book');
92 |
93 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
94 | this.page.appendChild(this.toolbarUpper);
95 |
96 | this.scroll = template.scroll('navigator-book');
97 | this.list = template.element('div', 'list', 'navigator-book', null, null);
98 | this.scroll.appendChild(this.list);
99 | this.page.appendChild(this.scroll);
100 |
101 | this.toolbarLower = template.toolbarLower(lowerToolSet);
102 | this.page.appendChild(this.toolbarLower);
103 |
104 | const container = document.querySelector('.container');
105 | container.appendChild(this.page);
106 | }
107 |
108 | contentClick(btn) {
109 | const bookIdx = parseInt(btn.dataset.bookIdx);
110 | queue.publish('navigator-book.select', bookIdx);
111 | }
112 |
113 | getElements() {
114 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
115 | this.btnChapter = this.toolbarLower
116 | .querySelector('.btn-icon--navigator-chapter');
117 | }
118 |
119 | hide() {
120 | this.page.classList.add('page--hide');
121 | }
122 |
123 | initialize() {
124 | this.buildPage();
125 | this.getElements();
126 | this.addListeners();
127 | this.subscribe();
128 |
129 | this.buildBookList();
130 | }
131 |
132 | listClick(event) {
133 | event.preventDefault();
134 | const btn = event.target.closest('div.btn-book');
135 | if (btn) {
136 | if (btn.classList.contains('btn-book')) {
137 | this.contentClick(btn);
138 | }
139 | }
140 | }
141 |
142 | show() {
143 | this.page.classList.remove('page--hide');
144 | }
145 |
146 | subscribe() {
147 | queue.subscribe('bookIdx.update', (bookIdx) => {
148 | this.bookIdxUpdate(bookIdx);
149 | });
150 |
151 | queue.subscribe('navigator-book.hide', () => {
152 | this.hide();
153 | });
154 | queue.subscribe('navigator-book.show', () => {
155 | this.show();
156 | });
157 | }
158 |
159 | toolbarLowerClick(event) {
160 | event.preventDefault();
161 | const btn = event.target.closest('div.btn-icon');
162 | if (btn) {
163 | if (btn === this.btnBack) {
164 | queue.publish('navigator.back', null);
165 | } else if (btn === this.btnChapter) {
166 | queue.publish('navigator-chapter', null);
167 | }
168 | }
169 | }
170 |
171 | }
172 |
173 | export { NavigatorBookView };
174 |
--------------------------------------------------------------------------------
/js/View/NavigatorChapterView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 | import { tomeIdx } from '../data/tomeIdx.js';
7 | import { tomeLists } from '../data/tomeLists.js';
8 |
9 | const lowerToolSet = [
10 | { type: 'btn', icon: 'back', ariaLabel: null },
11 | { type: 'btn', icon: 'navigator-book', ariaLabel: null },
12 | ];
13 |
14 | const upperToolSet = [
15 | { type: 'banner', cssModifier: 'navigator-chapter', text: null },
16 | ];
17 |
18 | class NavigatorChapterView {
19 |
20 | constructor() {
21 | this.initialize();
22 | }
23 |
24 | addListeners() {
25 | this.list.addEventListener('click', (event) => {
26 | this.listClick(event);
27 | });
28 | this.toolbarLower.addEventListener('click', (event) => {
29 | this.toolbarLowerClick(event);
30 | });
31 | }
32 |
33 | buildBtnContent(chapterIdx) {
34 | const chapter = tomeLists.chapters[chapterIdx];
35 | const btn = document.createElement('div');
36 | btn.classList.add('btn-chapter');
37 | btn.dataset.bookIdx = chapter[tomeIdx.chapter.bookIdx];
38 | btn.dataset.chapterIdx = chapterIdx;
39 | btn.dataset.chapterName = chapter[tomeIdx.chapter.name];
40 | const num = chapter[tomeIdx.chapter.num];
41 | btn.textContent = num;
42 | return btn;
43 | }
44 |
45 | bookIdxUpdate(bookIdx) {
46 | if (this.bookIdx !== bookIdx) {
47 | this.bookIdx = bookIdx;
48 | this.updateBanner();
49 | this.updateChapterList();
50 | }
51 | }
52 |
53 | buildPage() {
54 | this.page = template.page('navigator-chapter');
55 |
56 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
57 | this.page.appendChild(this.toolbarUpper);
58 |
59 | this.scroll = template.scroll('navigator-chapter');
60 | this.list = template.element('div', 'list', 'navigator-chapter', null,
61 | null);
62 | this.scroll.appendChild(this.list);
63 | this.page.appendChild(this.scroll);
64 |
65 | this.toolbarLower = template.toolbarLower(lowerToolSet);
66 | this.page.appendChild(this.toolbarLower);
67 |
68 | const container = document.querySelector('.container');
69 | container.appendChild(this.page);
70 | }
71 |
72 | chapterIdxUpdate(chapterIdx) {
73 | const oldChapterIdx = this.chapterIdx || chapterIdx;
74 | const oldBookIdx = tomeLists
75 | .chapters[oldChapterIdx][tomeIdx.chapter.bookIdx];
76 | this.chapterIdx = chapterIdx;
77 | const bookIdx = tomeLists
78 | .chapters[this.chapterIdx][tomeIdx.chapter.bookIdx];
79 | if (oldBookIdx !== bookIdx) {
80 | this.updateBanner();
81 | this.updateChapterList();
82 | }
83 | this.updateActive();
84 | }
85 |
86 | contentClick(btn) {
87 | const chapterIdx = parseInt(btn.dataset.chapterIdx);
88 | queue.publish('navigator-chapter.select', chapterIdx);
89 | }
90 |
91 | getElements() {
92 | this.banner = this.toolbarUpper.querySelector('.banner--navigator-chapter');
93 |
94 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
95 | this.btnBook = this.toolbarLower.querySelector('.btn-icon--navigator-book');
96 | }
97 |
98 | hide() {
99 | this.page.classList.add('page--hide');
100 | }
101 |
102 | initialize() {
103 | this.buildPage();
104 | this.getElements();
105 | this.addListeners();
106 | this.subscribe();
107 | }
108 |
109 | listClick(event) {
110 | event.preventDefault();
111 | const btn = event.target.closest('div.btn-chapter');
112 | if (btn) {
113 | if (btn.classList.contains('btn-chapter')) {
114 | this.contentClick(btn);
115 | }
116 | }
117 | }
118 |
119 | show() {
120 | this.page.classList.remove('page--hide');
121 | }
122 |
123 | subscribe() {
124 | queue.subscribe('bookIdx.update', (bookIdx) => {
125 | this.bookIdxUpdate(bookIdx);
126 | });
127 |
128 | queue.subscribe('chapterIdx.update', (chapterIdx) => {
129 | this.chapterIdxUpdate(chapterIdx);
130 | });
131 |
132 | queue.subscribe('navigator-chapter.hide', () => {
133 | this.hide();
134 | });
135 | queue.subscribe('navigator-chapter.show', () => {
136 | this.show();
137 | });
138 | }
139 |
140 | toolbarLowerClick(event) {
141 | event.preventDefault();
142 | const btn = event.target.closest('div.btn-icon');
143 | if (btn) {
144 | if (btn === this.btnBack) {
145 | queue.publish('navigator.back', null);
146 | } else if (btn === this.btnBook) {
147 | queue.publish('navigator-book', null);
148 | }
149 | }
150 | }
151 |
152 | updateActive() {
153 | let activeBtn = this.list.querySelector('.btn-chapter--active');
154 | if (activeBtn) {
155 | activeBtn.classList.remove('btn-chapter--active');
156 | }
157 | const selector =
158 | `.btn-chapter[data-chapter-idx="${this.chapterIdx}"]`;
159 | activeBtn = this.list.querySelector(selector);
160 | activeBtn.classList.add('btn-chapter--active');
161 | }
162 |
163 | updateBanner() {
164 | const longName = tomeLists.books[this.bookIdx][tomeIdx.book.longName];
165 | this.banner.innerHTML = `${longName}`;
166 | }
167 |
168 | updateChapterList() {
169 | this.scroll.scrollTop = 0;
170 | util.removeAllChildren(this.list);
171 | const list = document.createElement('div');
172 | list.classList.add('content', 'content--chapter');
173 | const book = tomeLists.books[this.bookIdx];
174 | const indices = util.range(book[tomeIdx.book.firstChapterIdx],
175 | book[tomeIdx.book.lastChapterIdx] + 1);
176 | for (const idx of indices) {
177 | const btn = this.buildBtnContent(idx);
178 | list.appendChild(btn);
179 | }
180 | this.list.appendChild(list);
181 | }
182 |
183 | }
184 |
185 | export { NavigatorChapterView };
186 |
--------------------------------------------------------------------------------
/js/View/SearchHistoryView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 |
7 | const lowerToolSet = [
8 | { type: 'btn', icon: 'back', ariaLabel: null },
9 | { type: 'btn', icon: 'search-lookup', ariaLabel: null },
10 | { type: 'btn', icon: 'result', ariaLabel: null },
11 | { type: 'btn', icon: 'filter', ariaLabel: null },
12 | { type: 'btn', icon: 'history-clear', ariaLabel: null },
13 | ];
14 |
15 | const upperToolSet = [
16 | { type: 'banner', cssModifier: 'search-history', text: 'Search History' },
17 | ];
18 |
19 | class SearchHistoryView {
20 |
21 | constructor() {
22 | this.initialize();
23 | }
24 |
25 | addListeners() {
26 | this.list.addEventListener('click', (event) => {
27 | this.listClick(event);
28 | });
29 | this.toolbarLower.addEventListener('click', (event) => {
30 | this.toolbarLowerClick(event);
31 | });
32 | }
33 |
34 | buildEntry(query, idx) {
35 | const entry = document.createElement('div');
36 | entry.classList.add('entry', 'entry--history');
37 | const btnEntry = document.createElement('div');
38 | btnEntry.classList.add('btn-entry', 'btn-entry--history');
39 | btnEntry.dataset.historyIdx = idx;
40 | btnEntry.textContent = query;
41 | entry.appendChild(btnEntry);
42 | const btnDelete = template.btnIcon('delete', 'delete', null);
43 | entry.appendChild(btnDelete);
44 | return entry;
45 | }
46 |
47 | buildPage() {
48 | this.page = template.page('search-history');
49 |
50 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
51 | this.page.appendChild(this.toolbarUpper);
52 |
53 | this.scroll = template.scroll('search-history');
54 | this.empty = template.element('div', 'empty', 'search-history', null,
55 | 'No Searches Saved');
56 | this.scroll.appendChild(this.empty);
57 |
58 | this.list = template.element('div', 'list', 'search-history', null, null);
59 | this.scroll.appendChild(this.list);
60 |
61 | this.page.appendChild(this.scroll);
62 |
63 | this.toolbarLower = template.toolbarLower(lowerToolSet);
64 | this.page.appendChild(this.toolbarLower);
65 |
66 | const container = document.querySelector('.container');
67 | container.appendChild(this.page);
68 | }
69 |
70 | getElements() {
71 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
72 | this.btnLookup = this.toolbarLower
73 | .querySelector('.btn-icon--search-lookup');
74 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
75 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
76 | this.btnHistoryClear = this.toolbarLower
77 | .querySelector('.btn-icon--history-clear');
78 | }
79 |
80 | hide() {
81 | this.page.classList.add('page--hide');
82 | }
83 |
84 | historyUpdate(history) {
85 | this.history = history;
86 | this.updateHistory();
87 | }
88 |
89 | initialize() {
90 | this.buildPage();
91 | this.getElements();
92 | this.addListeners();
93 | this.subscribe();
94 | }
95 |
96 | listClick(event) {
97 | event.preventDefault();
98 | const btn = event.target.closest('div');
99 | if (btn) {
100 | if (btn.classList.contains('btn-entry--history')) {
101 | const query = btn.textContent;
102 | queue.publish('search-history.select', query);
103 | } else if (btn.classList.contains('btn-icon--delete')) {
104 | const entry = btn.previousSibling;
105 | const query = entry.textContent;
106 | queue.publish('search-history.delete', query);
107 | }
108 | }
109 | }
110 |
111 | show() {
112 | this.page.classList.remove('page--hide');
113 | }
114 |
115 | subscribe() {
116 | queue.subscribe('search.query.error', () => {
117 | queue.publish('search-lookup', null);
118 | });
119 |
120 | queue.subscribe('search-history.hide', () => {
121 | this.hide();
122 | });
123 | queue.subscribe('search-history.show', () => {
124 | this.show();
125 | });
126 |
127 | queue.subscribe('search.history.update', (history) => {
128 | this.historyUpdate(history);
129 | });
130 | }
131 |
132 | toolbarLowerClick(event) {
133 | event.preventDefault();
134 | const btn = event.target.closest('div.btn-icon');
135 | if (btn) {
136 | if (btn === this.btnBack) {
137 | queue.publish('search.back', null);
138 | } else if (btn === this.btnLookup) {
139 | queue.publish('search-lookup', null);
140 | } else if (btn === this.btnResult) {
141 | queue.publish('search-result', null);
142 | } else if (btn === this.btnFilter) {
143 | queue.publish('search-filter', null);
144 | } else if (btn === this.btnHistoryClear) {
145 | queue.publish('search-history.clear', null);
146 | }
147 | }
148 | }
149 |
150 | updateHistory() {
151 | const scrollSave = this.scroll.scrollTop;
152 | util.removeAllChildren(this.list);
153 | if (this.history.length === 0) {
154 | this.empty.classList.remove('hide');
155 | } else {
156 | this.empty.classList.add('hide');
157 | const fragment = document.createDocumentFragment();
158 | for (const query of this.history) {
159 | const entry = this.buildEntry(query);
160 | fragment.appendChild(entry);
161 | }
162 | this.list.appendChild(fragment);
163 | }
164 | this.scroll.scrollTop = scrollSave;
165 | }
166 |
167 | }
168 |
169 | export { SearchHistoryView };
170 |
--------------------------------------------------------------------------------
/js/View/SearchLookupView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: 'Query' },
8 | { type: 'input', ariaLabel: 'Query' },
9 | { type: 'btn', cssModifier: 'search', ariaLabel: null, label: 'Search' },
10 | ];
11 |
12 | const lowerToolSet = [
13 | { type: 'btn', icon: 'back', ariaLabel: null },
14 | { type: 'btn', icon: 'result', ariaLabel: null },
15 | { type: 'btn', icon: 'filter', ariaLabel: null },
16 | { type: 'btn', icon: 'history', ariaLabel: null },
17 | ];
18 |
19 | const upperToolSet = [
20 | { type: 'banner', cssModifier: 'search-lookup', text: 'Search Lookup' },
21 | ];
22 |
23 | class SearchLookupView {
24 |
25 | constructor() {
26 | this.initialize();
27 | }
28 |
29 | addListeners() {
30 | this.dialogBtns.addEventListener('click', (event) => {
31 | this.dialogClick(event);
32 | });
33 | this.inputQuery.addEventListener('keydown', (event) => {
34 | this.inputKeyDown(event);
35 | });
36 | this.toolbarLower.addEventListener('click', (event) => {
37 | this.toolbarLowerClick(event);
38 | });
39 | }
40 |
41 | buildPage() {
42 | this.page = template.page('search-lookup');
43 |
44 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
45 | this.page.appendChild(this.toolbarUpper);
46 |
47 | this.scroll = template.scroll('search-lookup');
48 | this.dialog = template.divDialog('search-lookup', dialogToolset);
49 | this.scroll.appendChild(this.dialog);
50 |
51 | this.message = template.element('div', 'message', 'search-lookup', null,
52 | null);
53 | this.scroll.appendChild(this.message);
54 |
55 | this.page.appendChild(this.scroll);
56 |
57 | this.toolbarLower = template.toolbarLower(lowerToolSet);
58 | this.page.appendChild(this.toolbarLower);
59 |
60 | const container = document.querySelector('.container');
61 | container.appendChild(this.page);
62 | }
63 |
64 | dialogClick(event) {
65 | event.preventDefault();
66 | const btn = event.target.closest('div.btn-dialog');
67 | if (btn) {
68 | if (btn === this.btnSearch) {
69 | this.searchClick();
70 | }
71 | }
72 | }
73 |
74 | error(message) {
75 | this.queryError = true;
76 | this.message.textContent = message;
77 | this.message.classList.remove('hide');
78 | }
79 |
80 | getElements() {
81 | this.inputQuery = this.dialog.querySelector('.dialog-input');
82 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
83 | this.btnSearch = this.dialogBtns.querySelector('.btn-dialog--search');
84 |
85 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
86 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
87 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
88 | this.btnHistory = this.toolbarLower.querySelector('.btn-icon--history');
89 | }
90 |
91 | hide() {
92 | this.page.classList.add('page--hide');
93 | }
94 |
95 | initialize() {
96 | this.buildPage();
97 | this.getElements();
98 | this.addListeners();
99 | this.subscribe();
100 | this.queryError = false;
101 | this.searchHistorySelect = false;
102 | }
103 |
104 | inputKeyDown(event) {
105 | if (event.key === 'Enter') {
106 | this.inputQuery.blur();
107 | this.searchClick();
108 | }
109 | }
110 |
111 | searchClick() {
112 | const query = this.inputQuery.value;
113 | queue.publish('search-lookup.search', query);
114 | }
115 |
116 | show() {
117 | if (
118 | this.searchHistorySelect === true &&
119 | this.queryError === true
120 | ) {
121 | this.inputQuery.value = this.searchQuery;
122 | this.searchHistorySelect = false;
123 | this.queryError = false;
124 | } else {
125 | this.inputQuery.value = '';
126 | this.error.textContent = '';
127 | this.message.textContent = '';
128 | this.message.classList.add('hide');
129 | }
130 | this.page.classList.remove('page--hide');
131 | this.inputQuery.focus();
132 | }
133 |
134 | subscribe() {
135 | queue.subscribe('search.query.change', (searchQuery) => {
136 | this.searchQuery = searchQuery;
137 | });
138 | queue.subscribe('search.query.error', (message) => {
139 | this.error(message);
140 | });
141 |
142 | queue.subscribe('search-history.select', () => {
143 | this.searchHistorySelect = true;
144 | });
145 | queue.subscribe('search-lookup.hide', () => {
146 | this.hide();
147 | });
148 | queue.subscribe('search-lookup.show', () => {
149 | this.show();
150 | });
151 | }
152 |
153 | toolbarLowerClick(event) {
154 | event.preventDefault();
155 | const btn = event.target.closest('div.btn-icon');
156 | if (btn) {
157 | if (btn === this.btnBack) {
158 | queue.publish('search.back', null);
159 | } else if (btn === this.btnResult) {
160 | queue.publish('search-result', null);
161 | } else if (btn === this.btnFilter) {
162 | queue.publish('search-filter', null);
163 | } else if (btn === this.btnHistory) {
164 | queue.publish('search-history', null);
165 | }
166 | }
167 | }
168 |
169 | }
170 |
171 | export { SearchLookupView };
172 |
--------------------------------------------------------------------------------
/js/View/StrongDefView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 | import { binIdx } from '../data/binIdx.js';
7 | import { strongIdx } from '../data/strongIdx.js';
8 |
9 | const lowerToolSet = [
10 | { type: 'btn', icon: 'back', ariaLabel: null },
11 | { type: 'btn', icon: 'strong-lookup', ariaLabel: null },
12 | { type: 'btn', icon: 'result', ariaLabel: null },
13 | { type: 'btn', icon: 'filter', ariaLabel: null },
14 | { type: 'btn', icon: 'history', ariaLabel: null },
15 | { type: 'btn', icon: 'strong-verse', ariaLabel: null },
16 | { type: 'btn', icon: 'prev', ariaLabel: null },
17 | ];
18 |
19 | const upperToolSet = [
20 | { type: 'banner', cssModifier: 'strong-def', text: 'Strong Definition' },
21 | ];
22 |
23 | class StrongDefView {
24 |
25 | constructor() {
26 | this.initialize();
27 | }
28 |
29 | addListeners() {
30 | this.list.addEventListener('click', (event) => {
31 | this.listClick(event);
32 | });
33 | this.toolbarLower.addEventListener('click', (event) => {
34 | this.toolbarLowerClick(event);
35 | });
36 | }
37 |
38 | buildDef() {
39 | const fragment = document.createDocumentFragment();
40 | const lemma = template.element('div', 'strong-def', 'lemma', null, this.
41 | def[strongIdx.def.lemma].normalize('NFC'));
42 | if (this.strongDef.startsWith('H')) {
43 | lemma.classList.add('font--hebrew');
44 | } else {
45 | lemma.classList.add('font--greek');
46 | }
47 | const xlit = template.element('div', 'strong-def', 'xlit', null,
48 | this.def[strongIdx.def.tranliteration].normalize('NFC'));
49 | const pron = template.element('div', 'strong-def', 'pron', null,
50 | this.def[strongIdx.def.pronunciation].normalize('NFC'));
51 | const definition = this.buildDefinition();
52 | fragment.appendChild(lemma);
53 | fragment.appendChild(xlit);
54 | fragment.appendChild(pron);
55 | fragment.appendChild(definition);
56 | return fragment;
57 | }
58 |
59 | buildDefinition(definition) {
60 | const defDiv = template.element('div', 'strong-def', 'def', null, null);
61 | const deriv = template.strongList(this.def[strongIdx.def.deriv], 'deriv');
62 | defDiv.appendChild(deriv);
63 | const strongDef = template.strongList(this.def[strongIdx.def.strongDef],
64 | 'strong-def');
65 | defDiv.appendChild(strongDef);
66 | const kjvDef = template.strongList(this.def[strongIdx.def.kjvDef],
67 | 'kjv-def');
68 | defDiv.appendChild(kjvDef);
69 | return defDiv;
70 | }
71 |
72 | buildPage() {
73 | this.page = template.page('strong-def');
74 |
75 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
76 | this.page.appendChild(this.toolbarUpper);
77 |
78 | this.scroll = template.scroll('strong-def');
79 | this.list = template.element('div', 'list', 'strong-def', null, null);
80 | this.scroll.appendChild(this.list);
81 | this.page.appendChild(this.scroll);
82 |
83 | this.toolbarLower = template.toolbarLower(lowerToolSet);
84 | this.page.appendChild(this.toolbarLower);
85 |
86 | const container = document.querySelector('.container');
87 | container.appendChild(this.page);
88 | }
89 |
90 | buildWords() {
91 | const strongWords = template.element('div', 'strong-words', null, null,
92 | null);
93 | for (const word of this.words) {
94 | const tomeWord = word[strongIdx.word.tomeWord];
95 | const tomeBin = word[strongIdx.word.tomeBin];
96 | const label =
97 | `${tomeWord} (${tomeBin[binIdx.tomeBinIdx.wordCount]}/\
98 | ${tomeBin[binIdx.tomeBinIdx.verseCount]})`;
99 | const btn = template.element('div', 'btn-strong-word', null, null, label);
100 | btn.dataset.word = word[strongIdx.word.tomeWord];
101 | strongWords.appendChild(btn);
102 | }
103 | return strongWords;
104 | }
105 |
106 | chainUpdate(strongChain) {
107 | this.strongChain = strongChain;
108 | if (this.strongChain.length) {
109 | this.btnPrev.classList.remove('hide');
110 | } else {
111 | this.btnPrev.classList.add('hide');
112 | }
113 | }
114 |
115 | defClick(btn) {
116 | const strongDef = btn.dataset.strongDef;
117 | queue.publish('strong-def.select', strongDef);
118 | }
119 |
120 | defUpdate(strongDefObj) {
121 | this.strongDefObj = strongDefObj;
122 | this.strongDef = this.strongDefObj.k;
123 | this.def = this.strongDefObj.v;
124 | this.updateBanner();
125 | this.updateDefs();
126 | this.updateActiveWord();
127 | }
128 |
129 | getElements() {
130 | this.banner = this.toolbarUpper.querySelector('.banner--strong-def');
131 |
132 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
133 | this.btnLookup = this.toolbarLower
134 | .querySelector('.btn-icon--strong-lookup');
135 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
136 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
137 | this.btnHistory = this.toolbarLower.querySelector('.btn-icon--history');
138 | this.btnVerse = this.toolbarLower.querySelector('.btn-icon--strong-verse');
139 | this.btnPrev = this.toolbarLower.querySelector('.btn-icon--prev');
140 | }
141 |
142 | hide() {
143 | this.page.classList.add('page--hide');
144 | }
145 |
146 | initialize() {
147 | this.buildPage();
148 | this.getElements();
149 | this.addListeners();
150 | this.subscribe();
151 | }
152 |
153 | listClick(event) {
154 | event.preventDefault();
155 | const btn = event.target.closest('div');
156 | if (btn) {
157 | if (btn.classList.contains('btn-strong-word')) {
158 | this.wordClick(btn);
159 | } else if (btn.classList.contains('btn-strong-def')) {
160 | this.defClick(btn);
161 | }
162 | }
163 | }
164 |
165 | show() {
166 | this.page.classList.remove('page--hide');
167 | }
168 |
169 | subscribe() {
170 | queue.subscribe('strong-def.hide', () => {
171 | this.hide();
172 | });
173 | queue.subscribe('strong-def.show', () => {
174 | this.show();
175 | });
176 |
177 | queue.subscribe('strong.chain.update', (strongChain) => {
178 | this.chainUpdate(strongChain);
179 | });
180 | queue.subscribe('strong.def.update', (strongDefObj) => {
181 | this.defUpdate(strongDefObj);
182 | });
183 | queue.subscribe('strong.word.update', (strongWord) => {
184 | this.wordUpdate(strongWord);
185 | });
186 | queue.subscribe('strong.wordObj.update', (strongWordObj) => {
187 | this.wordObjUpdate(strongWordObj);
188 | });
189 | }
190 |
191 | toolbarLowerClick(event) {
192 | event.preventDefault();
193 | const btn = event.target.closest('div.btn-icon');
194 | if (btn) {
195 | if (btn === this.btnBack) {
196 | queue.publish('strong.back', null);
197 | } else if (btn === this.btnLookup) {
198 | queue.publish('strong-lookup', null);
199 | } else if (btn === this.btnResult) {
200 | queue.publish('strong-result', null);
201 | } else if (btn === this.btnFilter) {
202 | queue.publish('strong-filter', null);
203 | } else if (btn === this.btnHistory) {
204 | queue.publish('strong-history', null);
205 | } else if (btn === this.btnVerse) {
206 | queue.publish('strong-verse', null);
207 | } else if (btn === this.btnPrev) {
208 | queue.publish('strong.prev', null);
209 | }
210 | }
211 | }
212 |
213 | updateActiveWord() {
214 | if (this.activeWordBtn) {
215 | this.activeWordBtn.classList.remove('btn-strong-word--active');
216 | }
217 | const strongWords = this.list.querySelector('.strong-words');
218 | if (strongWords) {
219 | const query = `.btn-strong-word[data-word="${this.strongWord}"]`;
220 | const btn = strongWords.querySelector(query);
221 | if (btn) {
222 | btn.classList.add('btn-strong-word--active');
223 | this.activeWordBtn = btn;
224 | }
225 | }
226 | }
227 |
228 | updateBanner() {
229 | if (this.strongDef) {
230 | this.banner.textContent = this.strongDef;
231 | }
232 | }
233 |
234 | updateDefs() {
235 | this.scroll.scrollTop = 0;
236 | util.removeAllChildren(this.list);
237 | const def = this.buildDef();
238 | this.list.appendChild(def);
239 | const strongWords = this.buildWords();
240 | this.list.appendChild(strongWords);
241 | }
242 |
243 | wordClick(btn) {
244 | const word = btn.dataset.word;
245 | queue.publish('strong-def.word.select', word);
246 | }
247 |
248 | wordObjUpdate(strongWordObj) {
249 | this.strongWordObj = strongWordObj;
250 | this.words = this.strongWordObj.v;
251 | }
252 |
253 | wordUpdate(strongWord) {
254 | this.strongWord = strongWord;
255 | if (this.strongWord) {
256 | this.updateActiveWord();
257 | }
258 | }
259 |
260 | }
261 |
262 | export { StrongDefView };
263 |
--------------------------------------------------------------------------------
/js/View/StrongHistoryView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { util } from '../util.js';
5 | import { template } from '../template.js';
6 | import { strongCitations } from '../data/strongDictDb.js';
7 |
8 | const lowerToolSet = [
9 | { type: 'btn', icon: 'back', ariaLabel: null },
10 | { type: 'btn', icon: 'strong-lookup', ariaLabel: null },
11 | { type: 'btn', icon: 'strong-def', ariaLabel: null },
12 | { type: 'btn', icon: 'result', ariaLabel: null },
13 | { type: 'btn', icon: 'filter', ariaLabel: null },
14 | { type: 'btn', icon: 'strong-verse', ariaLabel: null },
15 | { type: 'btn', icon: 'history-clear', ariaLabel: null },
16 | ];
17 |
18 | const upperToolSet = [
19 | { type: 'banner', cssModifier: 'strong-history', text: 'Strong History' },
20 | ];
21 |
22 | const firstXlit = 0;
23 |
24 | class StrongHistoryView {
25 |
26 | constructor() {
27 | this.initialize();
28 | }
29 |
30 | addListeners() {
31 | this.list.addEventListener('click', (event) => {
32 | this.listClick(event);
33 | });
34 | this.toolbarLower.addEventListener('click', (event) => {
35 | this.toolbarLowerClick(event);
36 | });
37 | }
38 |
39 | buildEntry(strongDef) {
40 | const entry = document.createElement('div');
41 | entry.classList.add('entry', 'entry--history');
42 | const btnEntry = document.createElement('div');
43 | btnEntry.classList.add('btn-entry', 'btn-entry--history');
44 | const transliteration = strongCitations[strongDef];
45 | const first = transliteration.replace(',', '').split(' ')[firstXlit];
46 | btnEntry.textContent = `${strongDef} ${first.normalize('NFC')}`;
47 | btnEntry.dataset.def = strongDef;
48 | entry.appendChild(btnEntry);
49 | const btnDelete = template.btnIcon('delete', 'delete', null);
50 | entry.appendChild(btnDelete);
51 | return entry;
52 | }
53 |
54 | buildPage() {
55 | this.page = template.page('strong-history');
56 |
57 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
58 | this.page.appendChild(this.toolbarUpper);
59 |
60 | this.scroll = template.scroll('strong-history');
61 | this.empty = template.element('div', 'empty', 'strong-history', null,
62 | 'No Strong History.');
63 | this.scroll.appendChild(this.empty);
64 |
65 | this.list = template.element('div', 'list', 'strong-history', null, null);
66 | this.scroll.appendChild(this.list);
67 |
68 | this.page.appendChild(this.scroll);
69 |
70 | this.toolbarLower = template.toolbarLower(lowerToolSet);
71 | this.page.appendChild(this.toolbarLower);
72 |
73 | const container = document.querySelector('.container');
74 | container.appendChild(this.page);
75 | }
76 |
77 | getElements() {
78 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
79 | this.btnLookup = this.toolbarLower
80 | .querySelector('.btn-icon--strong-lookup');
81 | this.btnDef = this.toolbarLower.querySelector('.btn-icon--strong-def');
82 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
83 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
84 | this.btnHistory = this.toolbarLower.querySelector('.btn-icon--history');
85 | this.btnVerse = this.toolbarLower.querySelector('.btn-icon--strong-verse');
86 | this.btnHistoryClear = this.toolbarLower
87 | .querySelector('.btn-icon--history-clear');
88 | }
89 |
90 | hide() {
91 | this.page.classList.add('page--hide');
92 | }
93 |
94 | historyUpdate(strongHstory) {
95 | this.history = strongHstory;
96 | this.updateHistory();
97 | }
98 |
99 | initialize() {
100 | this.buildPage();
101 | this.getElements();
102 | this.addListeners();
103 | this.subscribe();
104 | }
105 |
106 | listClick(event) {
107 | event.preventDefault();
108 | const btn = event.target.closest('div');
109 | if (btn) {
110 | if (btn.classList.contains('btn-entry--history')) {
111 | const strongDef = btn.dataset.def;
112 | queue.publish('strong-history.select', strongDef);
113 | } else if (btn.classList.contains('btn-icon--delete')) {
114 | const entry = btn.previousSibling;
115 | const strongDef = entry.dataset.def;
116 | queue.publish('strong-history.delete', strongDef);
117 | }
118 | }
119 | }
120 |
121 | show() {
122 | this.page.classList.remove('page--hide');
123 | }
124 |
125 | subscribe() {
126 | queue.subscribe('strong-history.hide', () => {
127 | this.hide();
128 | });
129 | queue.subscribe('strong-history.show', () => {
130 | this.show();
131 | });
132 |
133 | queue.subscribe('strong.history.update', (strongHistory) => {
134 | this.historyUpdate(strongHistory);
135 | });
136 | }
137 |
138 | toolbarLowerClick(event) {
139 | event.preventDefault();
140 | const btn = event.target.closest('div.btn-icon');
141 | if (btn) {
142 | if (btn === this.btnBack) {
143 | queue.publish('strong.back', null);
144 | } else if (btn === this.btnLookup) {
145 | queue.publish('strong-lookup', null);
146 | } else if (btn === this.btnDef) {
147 | queue.publish('strong-def', null);
148 | } else if (btn === this.btnResult) {
149 | queue.publish('strong-result', null);
150 | } else if (btn === this.btnFilter) {
151 | queue.publish('strong-filter', null);
152 | } else if (btn === this.btnVerse) {
153 | queue.publish('strong-verse', null);
154 | } else if (btn === this.btnHistoryClear) {
155 | queue.publish('strong-history.clear', null);
156 | }
157 | }
158 | }
159 |
160 | updateHistory() {
161 | const scrollSave = this.scroll.scrollTop;
162 | util.removeAllChildren(this.list);
163 | if (this.history.length === 0) {
164 | this.empty.classList.remove('hide');
165 | } else {
166 | this.empty.classList.add('hide');
167 | const fragment = document.createDocumentFragment();
168 | for (const strongDef of this.history) {
169 | const entry = this.buildEntry(strongDef);
170 | fragment.appendChild(entry);
171 | }
172 | this.list.appendChild(fragment);
173 | }
174 | this.scroll.scrollTop = scrollSave;
175 | }
176 |
177 | }
178 |
179 | export { StrongHistoryView };
180 |
--------------------------------------------------------------------------------
/js/View/StrongLookupView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 |
6 | const dialogToolset = [
7 | { type: 'label', text: 'Strong Number' },
8 | { type: 'input', ariaLabel: 'Strong Number' },
9 | { type: 'btn', cssModifier: 'find', ariaLabel: null, label: 'Find' },
10 | ];
11 |
12 | const lowerToolSet = [
13 | { type: 'btn', icon: 'back', ariaLabel: null },
14 | { type: 'btn', icon: 'strong-def', ariaLabel: null },
15 | { type: 'btn', icon: 'result', ariaLabel: null },
16 | { type: 'btn', icon: 'filter', ariaLabel: null },
17 | { type: 'btn', icon: 'history', ariaLabel: null },
18 | { type: 'btn', icon: 'strong-verse', ariaLabel: null },
19 | ];
20 |
21 | const upperToolSet = [
22 | { type: 'banner', cssModifier: 'strong-lookup', text: 'Strong Lookup' },
23 | ];
24 |
25 | class StrongLookupView {
26 |
27 | constructor() {
28 | this.initialize();
29 | }
30 |
31 | addListeners() {
32 | this.dialogBtns.addEventListener('click', (event) => {
33 | this.dialogClick(event);
34 | });
35 | this.inputStrongNum.addEventListener('keydown', (event) => {
36 | this.inputKeyDown(event);
37 | });
38 | this.toolbarLower.addEventListener('click', (event) => {
39 | this.toolbarLowerClick(event);
40 | });
41 | }
42 |
43 | buildPage() {
44 | this.page = template.page('strong-lookup');
45 |
46 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
47 | this.page.appendChild(this.toolbarUpper);
48 |
49 | this.scroll = template.scroll('strong-lookup');
50 | this.dialog = template.divDialog('strong-lookup', dialogToolset);
51 | this.scroll.appendChild(this.dialog);
52 |
53 | this.message = template.element('div', 'message', 'strong-lookup', null,
54 | null);
55 | this.scroll.appendChild(this.message);
56 |
57 | this.page.appendChild(this.scroll);
58 |
59 | this.toolbarLower = template.toolbarLower(lowerToolSet);
60 | this.page.appendChild(this.toolbarLower);
61 |
62 | const container = document.querySelector('.container');
63 | container.appendChild(this.page);
64 | }
65 |
66 | dialogClick(event) {
67 | event.preventDefault();
68 | const btn = event.target.closest('div.btn-dialog');
69 | if (btn) {
70 | if (btn === this.btnFind) {
71 | this.findClick();
72 | }
73 | }
74 | }
75 |
76 | error(message) {
77 | this.message.textContent = message;
78 | this.message.classList.remove('hide');
79 | }
80 |
81 | findClick() {
82 | const strongNum = this.inputStrongNum.value;
83 | queue.publish('strong-lookup.find', strongNum.toUpperCase());
84 | }
85 |
86 | getElements() {
87 | this.banner = this.toolbarUpper.querySelector('.banner--strong-lookup');
88 |
89 | this.inputStrongNum = this.dialog.querySelector('.dialog-input');
90 | this.dialogBtns = this.dialog.querySelector('.dialog-btns');
91 | this.btnFind = this.dialogBtns.querySelector('.btn-dialog--find');
92 |
93 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
94 | this.btnDef = this.toolbarLower.querySelector('.btn-icon--strong-def');
95 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
96 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
97 | this.btnHistory = this.toolbarLower.querySelector('.btn-icon--history');
98 | this.btnVerse = this.toolbarLower.querySelector('.btn-icon--strong-verse');
99 | }
100 |
101 | hide() {
102 | this.page.classList.add('page--hide');
103 | }
104 |
105 | initialize() {
106 | this.buildPage();
107 | this.getElements();
108 | this.addListeners();
109 | this.subscribe();
110 | }
111 |
112 | inputKeyDown(event) {
113 | if (event.key === 'Enter') {
114 | this.inputStrongNum.blur();
115 | this.findClick();
116 | }
117 | }
118 |
119 | show() {
120 | this.inputStrongNum.value = '';
121 | this.error.textContent = '';
122 | this.page.classList.remove('page--hide');
123 | this.message.classList.add('hide');
124 | this.inputStrongNum.focus();
125 | }
126 |
127 | subscribe() {
128 | queue.subscribe('strong-lookup.hide', () => {
129 | this.hide();
130 | });
131 | queue.subscribe('strong-lookup.show', () => {
132 | this.show();
133 | });
134 |
135 | queue.subscribe('strong.def.error', (message) => {
136 | this.error(message);
137 | });
138 | }
139 |
140 | toolbarLowerClick(event) {
141 | event.preventDefault();
142 | const btn = event.target.closest('div.btn-icon');
143 | if (btn) {
144 | if (btn === this.btnBack) {
145 | queue.publish('strong.back', null);
146 | } else if (btn === this.btnDef) {
147 | queue.publish('strong-def', null);
148 | } else if (btn === this.btnResult) {
149 | queue.publish('strong-result', null);
150 | } else if (btn === this.btnFilter) {
151 | queue.publish('strong-filter', null);
152 | } else if (btn === this.btnHistory) {
153 | queue.publish('strong-history', null);
154 | } else if (btn === this.btnVerse) {
155 | queue.publish('strong-verse', null);
156 | }
157 | }
158 | }
159 |
160 | }
161 |
162 | export { StrongLookupView };
163 |
--------------------------------------------------------------------------------
/js/View/StrongVerseView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { queue } from '../CommandQueue.js';
4 | import { template } from '../template.js';
5 | import { util } from '../util.js';
6 | import { tomeIdx } from '../data/tomeIdx.js';
7 | import { strongIdx } from '../data/strongIdx.js';
8 |
9 | const lowerToolSet = [
10 | { type: 'btn', icon: 'back', ariaLabel: null },
11 | { type: 'btn', icon: 'strong-lookup', ariaLabel: null },
12 | { type: 'btn', icon: 'strong-def', ariaLabel: null },
13 | { type: 'btn', icon: 'result', ariaLabel: null },
14 | { type: 'btn', icon: 'filter', ariaLabel: null },
15 | { type: 'btn', icon: 'history', ariaLabel: null },
16 | ];
17 |
18 | const upperToolSet = [
19 | { type: 'banner', cssModifier: 'strong-verse', text: 'Strong Verse' },
20 | ];
21 |
22 | class StrongVerseView {
23 |
24 | constructor() {
25 | this.initialize();
26 | }
27 |
28 | addListeners() {
29 | this.list.addEventListener('click', (event) => {
30 | this.listClick(event);
31 | });
32 | this.toolbarLower.addEventListener('click', (event) => {
33 | this.toolbarLowerClick(event);
34 | });
35 | }
36 |
37 | buildPage() {
38 | this.page = template.page('strong-verse');
39 |
40 | this.toolbarUpper = template.toolbarUpper(upperToolSet);
41 | this.page.appendChild(this.toolbarUpper);
42 |
43 | this.scroll = template.scroll('strong-verse');
44 | this.list = template.element('div', 'list', 'strong-verse', null, null);
45 | this.scroll.appendChild(this.list);
46 | this.page.appendChild(this.scroll);
47 |
48 | this.toolbarLower = template.toolbarLower(lowerToolSet);
49 | this.page.appendChild(this.toolbarLower);
50 |
51 | const container = document.querySelector('.container');
52 | container.appendChild(this.page);
53 | }
54 |
55 | buildStrongFragment(map) {
56 | const text = map[strongIdx.map.verseFragment];
57 | const strongFragment = template.element('div', 'strong-fragment', null,
58 | null, null);
59 | const verseFragment = template.element('div', 'verse-fragment', null,
60 | null, text);
61 | const strongList = template.element('div', 'strong-list', null, null, null);
62 | for (const num of map[strongIdx.map.strongNums]) {
63 | const btn = template.element('div', 'btn-strong', null, null, num);
64 | btn.dataset.strongDef = num.replace(/@/g, '');
65 | strongList.appendChild(btn);
66 | }
67 | strongFragment.appendChild(verseFragment);
68 | strongFragment.appendChild(strongList);
69 | return strongFragment;
70 | }
71 |
72 | getElements() {
73 | this.banner = this.toolbarUpper.querySelector('.banner--strong-verse');
74 |
75 | this.btnBack = this.toolbarLower.querySelector('.btn-icon--back');
76 | this.btnLookup = this.toolbarLower
77 | .querySelector('.btn-icon--strong-lookup');
78 | this.btnDef = this.toolbarLower.querySelector('.btn-icon--strong-def');
79 | this.btnResult = this.toolbarLower.querySelector('.btn-icon--result');
80 | this.btnFilter = this.toolbarLower.querySelector('.btn-icon--filter');
81 | this.btnHistory = this.toolbarLower.querySelector('.btn-icon--history');
82 | }
83 |
84 | hide() {
85 | this.page.classList.add('page--hide');
86 | }
87 |
88 | initialize() {
89 | this.buildPage();
90 | this.getElements();
91 | this.addListeners();
92 | this.subscribe();
93 | }
94 |
95 | listClick(event) {
96 | event.preventDefault();
97 | const btn = event.target.closest('div.btn-strong');
98 | if (btn) {
99 | if (btn.classList.contains('btn-strong')) {
100 | const strongDef = btn.dataset.strongDef;
101 | queue.publish('strong-verse.select', strongDef);
102 | }
103 | }
104 | }
105 |
106 | mapUpdate(strongMapObj) {
107 | this.strongMapObj = strongMapObj;
108 | this.maps = this.strongMapObj.v;
109 | }
110 |
111 | show() {
112 | this.page.classList.remove('page--hide');
113 | }
114 |
115 | subscribe() {
116 | queue.subscribe('strong-verse.hide', () => {
117 | this.hide();
118 | });
119 | queue.subscribe('strong-verse.show', () => {
120 | this.show();
121 | });
122 |
123 | queue.subscribe('strong.map.update', (strongMapObj) => {
124 | this.mapUpdate(strongMapObj);
125 | });
126 | queue.subscribe('strong.verse.update', (strongVerseObj) => {
127 | this.verseUpdate(strongVerseObj);
128 | });
129 | }
130 |
131 | toolbarLowerClick(event) {
132 | event.preventDefault();
133 | const btn = event.target.closest('div.btn-icon');
134 | if (btn) {
135 | if (btn === this.btnBack) {
136 | queue.publish('strong.back', null);
137 | } else if (btn === this.btnLookup) {
138 | queue.publish('strong-lookup', null);
139 | } else if (btn === this.btnDef) {
140 | queue.publish('strong-def', null);
141 | } else if (btn === this.btnResult) {
142 | queue.publish('strong-result', null);
143 | } else if (btn === this.btnFilter) {
144 | queue.publish('strong-filter', null);
145 | } else if (btn === this.btnHistory) {
146 | queue.publish('strong-history', null);
147 | }
148 | }
149 | }
150 |
151 | updateBanner() {
152 | this.banner.textContent = this.verse[tomeIdx.verse.citation];
153 | }
154 |
155 | updateVerse() {
156 | this.scroll.scrollTop = 0;
157 | util.removeAllChildren(this.list);
158 | const docFragment = document.createDocumentFragment();
159 | this.verseWords = this.verse[tomeIdx.verse.text].split(' ');
160 | for (const map of this.maps) {
161 | const strongMap = this.buildStrongFragment(map);
162 | docFragment.appendChild(strongMap);
163 | }
164 | this.list.appendChild(docFragment);
165 | }
166 |
167 | verseUpdate(strongVerseObj) {
168 | this.strongVerseObj = strongVerseObj;
169 | this.strongVerse = this.strongVerseObj.k;
170 | this.verse = this.strongVerseObj.v;
171 | this.updateBanner();
172 | this.updateVerse();
173 | }
174 |
175 | }
176 |
177 | export { StrongVerseView };
178 |
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*eslint no-unused-vars: ["off"]*/
4 |
5 | import { DbModel } from './Model/DbModel.js';
6 |
7 | import { ReadModel } from './Model/ReadModel.js';
8 | import { ReadView } from './View/ReadView.js';
9 | import { ReadController } from './Controller/ReadController.js';
10 |
11 | import { NavigatorModel } from './Model/NavigatorModel.js';
12 | import { NavigatorBookView } from './View/NavigatorBookView.js';
13 | import { NavigatorChapterView } from './View/NavigatorChapterView.js';
14 | import { NavigatorController } from './Controller/NavigatorController.js';
15 |
16 | import { BookmarkModel } from './Model/BookmarkModel.js';
17 | import { BookmarkListView } from './View/BookmarkListView.js';
18 | import { BookmarkMoveCopyView } from './View/BookmarkMoveCopyView.js';
19 | import { BookmarkFolderView } from './View/BookmarkFolderView.js';
20 | import { BookmarkFolderAddView } from './View/BookmarkFolderAddView.js';
21 | import { BookmarkFolderDeleteView } from './View/BookmarkFolderDeleteView.js';
22 | import { BookmarkFolderRenameView } from './View/BookmarkFolderRenameView.js';
23 | import { BookmarkExportview } from './View/BookmarkExportView.js';
24 | import { BookmarkImportView } from './View/BookmarkImportView.js';
25 | import { BookmarkController } from './Controller/BookmarkController.js';
26 |
27 | import { SearchModel } from './Model/SearchModel.js';
28 | import { SearchResultView } from './View/SearchResultView.js';
29 | import { SearchFilterView } from './View/SearchFilterView.js';
30 | import { SearchHistoryView } from './View/SearchHistoryView.js';
31 | import { SearchLookupView } from './View/SearchLookupView.js';
32 | import { SearchController } from './Controller/SearchController.js';
33 |
34 | import { StrongModel } from './Model/StrongModel.js';
35 | import { StrongDefView } from './View/StrongDefView.js';
36 | import { StrongFilterView } from './View/StrongFilterView.js';
37 | import { StrongHistoryView } from './View/StrongHistoryView.js';
38 | import { StrongLookupView } from './View/StrongLookupView.js';
39 | import { StrongResultView } from './View/StrongResultView.js';
40 | import { StrongVerseView } from './View/StrongVerseView.js';
41 | import { StrongController } from './Controller/StrongController.js';
42 |
43 | import { SettingModel } from './Model/SettingModel.js';
44 | import { SettingView } from './View/SettingView.js';
45 | import { SettingController } from './Controller/SettingController.js';
46 |
47 | import { HelpModel } from './Model/HelpModel.js';
48 | import { HelpReadView } from './View/HelpReadView.js';
49 | import { HelpTopicView } from './View/HelpTopicView.js';
50 | import { HelpController } from './Controller/HelpController.js';
51 | import { initializeTomeLists } from './data/tomeLists.js';
52 | import { initializeKjvPureDb } from './data/kjvPureDb.js';
53 | import { initializeKjvNameDb } from './data/kjvNameDb.js';
54 | import { initializeStrongDictDb } from './data/strongDictDb.js';
55 | import { initializeStrongPure } from './data/strongPureDb.js';
56 | import { initializeStrongName } from './data/strongNameDb.js';
57 |
58 | const APP_FONT = 'font--roboto';
59 |
60 | const loadMsg = document.querySelector('.load-msg');
61 | const loadScroll = document.querySelector('.load-scroll');
62 |
63 | (async () => {
64 | const body = document.body;
65 | const load = body.querySelector('.load');
66 |
67 | await initializeTomeLists();
68 | await initializeKjvPureDb();
69 | await initializeKjvNameDb();
70 | await initializeStrongDictDb();
71 | await initializeStrongPure();
72 | await initializeStrongName();
73 |
74 | const dbModel = new DbModel();
75 |
76 | const readView = new ReadView();
77 | const readController = new ReadController();
78 | const readModel = new ReadModel();
79 |
80 | const navigatorBookView = new NavigatorBookView();
81 | const navigatorChapterView = new NavigatorChapterView();
82 | const navigatorController = new NavigatorController();
83 | const navigatorModel = new NavigatorModel();
84 |
85 | const bookmarkListView = new BookmarkListView();
86 | const bookmarkMoveCopyView = new BookmarkMoveCopyView();
87 | const bookmarkFolderView = new BookmarkFolderView();
88 | const bookmarkFolderAddView = new BookmarkFolderAddView();
89 | const bookmarkFolderDeleteView = new BookmarkFolderDeleteView();
90 | const bookmarkFolderRenameView = new BookmarkFolderRenameView();
91 | const bookmarkExportview = new BookmarkExportview();
92 | const bookmarkImportView = new BookmarkImportView();
93 | const bookmarkController = new BookmarkController();
94 | const bookmarkModel = new BookmarkModel();
95 |
96 | const searchResultView = new SearchResultView();
97 | const searchFilterView = new SearchFilterView();
98 | const searchHistoryView = new SearchHistoryView();
99 | const searchLookupView = new SearchLookupView();
100 | const searchController = new SearchController();
101 | const searchModel = new SearchModel();
102 |
103 | const strongDefView = new StrongDefView();
104 | const strongFilterView = new StrongFilterView();
105 | const strongHistoryView = new StrongHistoryView();
106 | const strongLookupView = new StrongLookupView();
107 | const strongSearchView = new StrongResultView();
108 | const strongVerseView = new StrongVerseView();
109 | const strongController = new StrongController();
110 | const strongModel = new StrongModel();
111 |
112 | const settingView = new SettingView();
113 | const settingController = new SettingController();
114 | const settingModel = new SettingModel();
115 |
116 | const helpReadView = new HelpReadView();
117 | const helpTopicView = new HelpTopicView();
118 | const helpController = new HelpController();
119 | const helpModel = new HelpModel();
120 |
121 | load.classList.add('hide');
122 | document.documentElement.classList.add(APP_FONT);
123 |
124 | console.log(`intializeApp(): ${Date.now()}`);
125 | readController.initializeApp();
126 | console.log(`ready: ${Date.now()}`);
127 |
128 | })();
129 |
--------------------------------------------------------------------------------
/js/data/binIdx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const binIdx = {};
4 |
5 | binIdx.tomeBinIdx = {
6 | wordCount: 0,
7 | verseCount: 1,
8 | books: 2,
9 | verses: 3,
10 | };
11 |
12 | binIdx.bookBinIdx = {
13 | bookIdx: 0,
14 | wordCount: 1,
15 | verseCount: 2,
16 | sliceStart: 3,
17 | sliceEnd: 4,
18 | chapters: 5,
19 | };
20 |
21 | binIdx.chapterBinIdx = {
22 | chapterIdx: 0,
23 | wordCount: 1,
24 | verseCount: 2,
25 | sliceStart: 3,
26 | sliceEnd: 4,
27 | };
28 |
--------------------------------------------------------------------------------
/js/data/dbUtil.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { Dexie } from '../lib/dexie.v4.0.4.min.mjs';
5 |
6 | export const dbUtil = {};
7 |
8 | dbUtil.fetchJson = async (url) => {
9 | progress('fetching...');
10 | const response = await fetch(url);
11 | progress('parsing...');
12 | const data = await response.json();
13 |
14 | return data;
15 | };
16 |
17 | dbUtil.getVersion = (dbName) => {
18 | const defaultVersion = '1970-01-01';
19 | let version = localStorage.getItem(`${dbName}Version`);
20 | if (!version) {
21 | version = defaultVersion;
22 | } else {
23 | try {
24 | version = JSON.parse(version);
25 | } catch (error) {
26 | version = defaultVersion;
27 | }
28 | if (typeof version !== 'string') {
29 | version = defaultVersion;
30 | }
31 | }
32 | localStorage.setItem(`${dbName}Version`,
33 | JSON.stringify(version));
34 |
35 | return version;
36 | };
37 |
38 | dbUtil.versionCheck = async (dbSetup) => {
39 | const currentVersion = dbUtil.getVersion(dbSetup.name);
40 |
41 | const db = new Dexie(dbSetup.name);
42 | await db.version(1).stores(dbSetup.stores);
43 | db.open();
44 |
45 | if (dbSetup.version !== currentVersion) {
46 | progress('new version.');
47 | for (const store of Object.keys(dbSetup.stores)) {
48 | progress(`clearing ${store}...`);
49 | await db.table(store).clear();
50 | }
51 | localStorage.setItem(`${dbSetup.name}Version`,
52 | JSON.stringify(dbSetup.version));
53 | }
54 |
55 | return db;
56 | };
57 |
--------------------------------------------------------------------------------
/js/data/kjvNameDb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from '../data/dbUtil.js';
5 |
6 | const dbSetup = {
7 | name: 'kjv_name',
8 | stores: {
9 | verses: 'k',
10 | words: 'k',
11 | },
12 | url: '/json/kjv_name.json',
13 | version: '2024-05-09',
14 | };
15 |
16 | export let kjvNameDb = null;
17 | export let kjvNameVerseCount = null;
18 | export let kjvNameWords = null;
19 |
20 | export const initializeKjvNameDb = async () => {
21 | progress('');
22 | progress('* kjv name database *');
23 | progress('');
24 | kjvNameDb = await dbUtil.versionCheck(dbSetup);
25 | await populateKjv();
26 | await loadKjvNameWords();
27 | kjvNameVerseCount = await kjvNameDb.verses.count();
28 | progress('kjv name initialized.');
29 | };
30 |
31 |
32 | const loadKjvNameWords = async () => {
33 | progress('loading kjv name words...');
34 | const wordObjs = await kjvNameDb.words.toArray();
35 | kjvNameWords = wordObjs.map(x => x.k);
36 | };
37 |
38 | const populateKjv = async () => {
39 | const wordCount = await kjvNameDb.words.count();
40 | if (wordCount === 0) {
41 | const data = await dbUtil.fetchJson(dbSetup.url);
42 |
43 | progress('populating kjv name verses...');
44 | await kjvNameDb.verses.bulkAdd(data.verses);
45 | progress('populating kjv name words...');
46 | await kjvNameDb.words.bulkAdd(data.words);
47 | progress('kjv name population complete.');
48 | } else {
49 | progress('kjv name already populated.');
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/js/data/kjvPureDb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from '../data/dbUtil.js';
5 |
6 | const dbSetup = {
7 | name: 'kjv_pure',
8 | stores: {
9 | verses: 'k',
10 | words: 'k',
11 | },
12 | url: '/json/kjv_pure.json',
13 | version: '2024-05-09',
14 | };
15 |
16 | export let kjvPureDb = null;
17 | export let kjvPureVerseCount = null;
18 | export let kjvPureWords = null;
19 |
20 | export const initializeKjvPureDb = async () => {
21 | progress('');
22 | progress('* kjv pure database *');
23 | progress('');
24 | kjvPureDb = await dbUtil.versionCheck(dbSetup);
25 | await populateDb();
26 | await loadKjvPureWords();
27 | kjvPureVerseCount = await kjvPureDb.verses.count();
28 | progress('kjv pure initialized.');
29 | };
30 |
31 | const loadKjvPureWords = async () => {
32 | progress('loading kjv pure words...');
33 | const wordObjs = await kjvPureDb.words.toArray();
34 | kjvPureWords = wordObjs.map(x => x.k);
35 | };
36 |
37 | const populateDb = async () => {
38 | const wordCount = await kjvPureDb.words.count();
39 | if (wordCount === 0) {
40 | const data = await dbUtil.fetchJson(dbSetup.url);
41 |
42 | progress('populating kjv pure verses...');
43 | await kjvPureDb.verses.bulkAdd(data.verses);
44 | progress('populating kjv pure words...');
45 | await kjvPureDb.words.bulkAdd(data.words);
46 | progress('kjv pure population complete.');
47 | } else {
48 | progress('kjv pure already populated.');
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/js/data/strongDictDb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from '../data/dbUtil.js';
5 | import { strongIdx } from '../data/strongIdx.js';
6 |
7 | const strongSetup = {
8 | name: 'strong_dict',
9 | stores: {
10 | dict: 'k',
11 | },
12 | url: '/json/strong_dict.json',
13 | version: '2024-05-09',
14 | };
15 |
16 | export const strongCitations = {};
17 | export let strongDictDb = null;
18 | export let strongNums = null;
19 |
20 | export const initializeStrongDictDb = async () => {
21 | progress('');
22 | progress('* strong dictionary database *');
23 | progress('');
24 | strongDictDb = await dbUtil.versionCheck(strongSetup);
25 | await populateStrong();
26 | await loadStrongCitations();
27 | await loadStrongNums();
28 | progress('strong initialized.');
29 | };
30 |
31 | const loadStrongCitations = async () => {
32 | progress('loading strong citations...');
33 | const defObjs = await strongDictDb.dict.toArray();
34 | defObjs.map(x => strongCitations[x.k] = x.v[strongIdx.def.tranliteration]);
35 | };
36 |
37 | const loadStrongNums = async () => {
38 | progress('loading strong numbers...');
39 | const strongDictObjs = await strongDictDb.dict.toArray();
40 | strongNums = strongDictObjs.map(x => x.k);
41 | };
42 |
43 | const populateStrong = async () => {
44 | const dictCount = await strongDictDb.dict.count();
45 | if (dictCount === 0) {
46 | const data = await dbUtil.fetchJson(strongSetup.url);
47 |
48 | progress('populating dict...');
49 | await strongDictDb.dict.bulkAdd(data);
50 | progress('population complete.');
51 | } else {
52 | progress('strong dictionary already populated.');
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/js/data/strongIdx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const strongIdx = {};
4 |
5 | strongIdx.def = {
6 | lemma: 0,
7 | tranliteration: 1,
8 | pronunciation: 2,
9 | deriv: 3,
10 | strongDef: 4,
11 | kjvDef: 5,
12 | };
13 |
14 | strongIdx.map = {
15 | verseFragment: 0,
16 | strongNums: 1,
17 | };
18 |
19 | strongIdx.word = {
20 | tomeWord: 0,
21 | tomeBin: 1,
22 | };
23 |
--------------------------------------------------------------------------------
/js/data/strongNameDb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from '../data/dbUtil.js';
5 |
6 | const strongSetup = {
7 | name: 'strong_name',
8 | stores: {
9 | maps: 'k',
10 | words: 'k',
11 | },
12 | url: '/json/strong_name.json',
13 | version: '2024-05-09',
14 | };
15 |
16 | export let strongNameDb = null;
17 |
18 | export const initializeStrongName = async () => {
19 | progress('');
20 | progress('* strong name database *');
21 | progress('');
22 | strongNameDb = await dbUtil.versionCheck(strongSetup);
23 | await populateStrong();
24 | progress('strong name initialized.');
25 | };
26 |
27 | const populateStrong = async () => {
28 | const wordsCount = await strongNameDb.words.count();
29 | if (wordsCount === 0) {
30 | const data = await dbUtil.fetchJson(strongSetup.url);
31 |
32 | progress('populating maps...');
33 | await strongNameDb.maps.bulkAdd(data.maps);
34 | progress('populating words...');
35 | await strongNameDb.words.bulkAdd(data.words);
36 | progress('population complete.');
37 | } else {
38 | progress('strong name already populated.');
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/js/data/strongPureDb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from '../data/dbUtil.js';
5 |
6 | const strongSetup = {
7 | name: 'strong_pure',
8 | stores: {
9 | maps: 'k',
10 | words: 'k',
11 | },
12 | url: '/json/strong_pure.json',
13 | version: '2024-05-09',
14 | };
15 |
16 | export let strongPureDb = null;
17 |
18 | export const initializeStrongPure = async () => {
19 | progress('');
20 | progress('* strong pure database *');
21 | progress('');
22 | strongPureDb = await dbUtil.versionCheck(strongSetup);
23 | await populateStrong();
24 | progress('strong pure initialized.');
25 | };
26 |
27 | const populateStrong = async () => {
28 | const wordsCount = await strongPureDb.words.count();
29 | if (wordsCount === 0) {
30 | const data = await dbUtil.fetchJson(strongSetup.url);
31 |
32 | progress('populating maps...');
33 | await strongPureDb.maps.bulkAdd(data.maps);
34 | progress('populating words...');
35 | await strongPureDb.words.bulkAdd(data.words);
36 | progress('population complete.');
37 | } else {
38 | progress('strong pure already populated.');
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/js/data/tomeIdx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const tomeIdx = {};
4 |
5 | tomeIdx.book = {
6 | longName: 0,
7 | shortName: 1,
8 | firstVerseIdx: 2,
9 | lastVerseIdx: 3,
10 | firstChapterIdx: 4,
11 | lastChapterIdx: 5,
12 | };
13 |
14 | tomeIdx.chapter = {
15 | bookIdx: 0,
16 | name: 1,
17 | num: 2,
18 | firstVerseIdx: 3,
19 | lastVerseIdx: 4,
20 | };
21 |
22 | tomeIdx.verse = {
23 | text: 0,
24 | bookIdx: 1,
25 | chapterIdx: 2,
26 | citation: 3,
27 | num: 4,
28 | };
29 |
30 | tomeIdx.word = {
31 | verseIdx: 0,
32 | count: 1,
33 | };
34 |
--------------------------------------------------------------------------------
/js/data/tomeLists.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '../progress.js';
4 | import { dbUtil } from './dbUtil.js';
5 | import { tomeIdx } from './tomeIdx.js';
6 |
7 | export let tomeLists;
8 | const url = '/json/kjv_lists.json';
9 |
10 | export const chapterIdxByVerseIdx = (verseIdx) => {
11 | const chapterIdx = tomeLists.chapters
12 | .findIndex(x => x[tomeIdx.chapter.lastVerseIdx] >= verseIdx);
13 | return chapterIdx;
14 | };
15 |
16 | export const firstVerseIdxByChapterIdx = (chapterIdx) => {
17 | const verseIdx =
18 | tomeLists.chapters[chapterIdx][tomeIdx.chapter.firstVerseIdx];
19 | return verseIdx;
20 | };
21 |
22 | export const initializeTomeLists = async () => {
23 | progress('loading kjs lists...');
24 | tomeLists = await dbUtil.fetchJson(url);
25 | tomeLists.tomeName = 'kjv';
26 | };
27 |
--------------------------------------------------------------------------------
/js/load.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { progress } from '/js/progress.js';
4 |
5 | const prod = true;
6 |
7 | let newInstall = false;
8 | let updateFound = false;
9 |
10 | window.onload = () => {
11 | console.log(`window.onload: ${Date.now()}`);
12 | progress('');
13 | progress('* Download app *');
14 |
15 | if (prod) {
16 | swEvents();
17 | } else {
18 | loadApp();
19 | }
20 | };
21 |
22 | const swEvents = () => {
23 | if (navigator.serviceWorker) {
24 | navigator.serviceWorker.ready.then(() => {
25 | console.log(`sw.ready: ${Date.now()}`);
26 | if (!updateFound) {
27 | loadApp();
28 | } else {
29 | newInstall = true;
30 | console.log(`new install: ${Date.now()}`);
31 | }
32 | }).catch((error) => {
33 | console.log(`sw.ready error: ${error.message}`);
34 | });
35 | }
36 |
37 | navigator.serviceWorker.register('/sw.js').then((reg) => {
38 | console.log(`sw registered: ${Date.now()}`);
39 | reg.onupdatefound = () => {
40 | updateFound = true;
41 | console.log(`reg.updatefound: ${Date.now()}`);
42 | const newWorker = reg.installing;
43 | newWorker.onstatechange = (event) => {
44 | if (event.target.state === 'activated') {
45 | console.log(`nw.activated: ${Date.now()}`);
46 | if (newInstall) {
47 | loadApp();
48 | } else {
49 | refresh();
50 | }
51 | }
52 | };
53 | };
54 | }).catch((error) => {
55 | console.log(`reg.error: ${error.message}`);
56 | });
57 | };
58 |
59 | const refresh = () => {
60 | console.log(`refresh(): ${Date.now()}`);
61 | };
62 |
63 | const loadApp = async () => {
64 | console.log(`loadApp(): ${Date.now()}`);
65 | progress('');
66 | progress('* Launch app *');
67 |
68 | const font = document.createElement('link');
69 | font.rel = 'stylesheet';
70 | font.href = '/css/font.css';
71 | document.head.appendChild(font);
72 |
73 | const script = document.createElement('script');
74 | if (prod) {
75 | script.src = '/bundle.js';
76 | } else {
77 | script.type = 'module';
78 | script.src = '/js/app.js';
79 | }
80 | document.body.appendChild(script);
81 | };
82 |
--------------------------------------------------------------------------------
/js/progress.js:
--------------------------------------------------------------------------------
1 | const loadMsg = document.querySelector('.load-msg');
2 | const loadScroll = document.querySelector('.load-scroll');
3 |
4 | export const progress = (msg) => {
5 | loadMsg.innerHTML += msg + ' ';
6 | loadScroll.scrollTop = loadScroll.scrollHeight;
7 | };
8 |
--------------------------------------------------------------------------------
/js/template.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { tomeLists } from './data/tomeLists.js';
4 |
5 | const svgNS = 'http://www.w3.org/2000/svg';
6 | const xlinkNS = 'http://www.w3.org/1999/xlink';
7 |
8 | export const template = {};
9 |
10 | template.acrostic = (verseObj) => {
11 | let acrosticDiv = null;
12 | if (tomeLists.acrostics) {
13 | const acrostic = tomeLists.acrostics[verseObj.k];
14 | if (acrostic) {
15 | const glyph = acrostic.slice(0, 1);
16 | const xlit = acrostic.slice(1);
17 | const glyphSpan = template.element('span', 'font--hebrew', null, null,
18 | glyph);
19 | const xlitSpan = template.element('span', 'font--bold', null, null,
20 | xlit + ' ');
21 | acrosticDiv = template.element('div', 'acrostic', null, null, null);
22 | acrosticDiv.appendChild(glyphSpan);
23 | acrosticDiv.appendChild(xlitSpan);
24 | }
25 | }
26 | return acrosticDiv;
27 | };
28 |
29 | template.actionMenu = (cssModifier, actionSet) => {
30 | const actionMenu = template.element('div', 'action-menu', cssModifier, null,
31 | null);
32 | actionMenu.classList.add('hide');
33 | for (const btn of actionSet) {
34 | const element = template.btnIcon(btn.icon, btn.icon, null);
35 | actionMenu.appendChild(element);
36 | }
37 | return actionMenu;
38 | };
39 |
40 | template.btnBanner = (cssModifier, ariaLabel) => {
41 | const btnIcon = template.element('div', 'btn-banner', cssModifier, ariaLabel,
42 | null);
43 | return btnIcon;
44 | };
45 |
46 | template.btnIcon = (svgId, cssModifier, ariaLabel) => {
47 | const svgTag = document.createElementNS(svgNS, 'svg');
48 | svgTag.classList.add('icon-svg');
49 | const useTag = document.createElementNS(svgNS, 'use');
50 | useTag.setAttributeNS(xlinkNS, 'xlink:href', `icons.svg#${svgId}`);
51 | svgTag.appendChild(useTag);
52 | const btnIcon = template.element('div', 'btn-icon', cssModifier, ariaLabel,
53 | null);
54 | btnIcon.appendChild(svgTag);
55 | return btnIcon;
56 | };
57 |
58 | template.colophon = (verseObj) => {
59 | let colophonDiv = null;
60 | if (tomeLists.colophons) {
61 | const verseIdx = verseObj.k;
62 | if (verseIdx in tomeLists.colophons) {
63 | colophonDiv = template.element('div', 'colophon', null, null, null);
64 | colophonDiv.textContent = tomeLists.colophons[verseIdx];
65 | }
66 | }
67 | return colophonDiv;
68 | };
69 |
70 | template.readOptionCheckbox = (id, ariaLabel) => {
71 | const divCheckbox = template.element('div', 'checkbox-container', null,
72 | null, null);
73 | const checkbox = template.element('input', null, null, ariaLabel, null);
74 | checkbox.setAttribute('type', 'checkbox');
75 | checkbox.setAttribute('id', id);
76 | const label = template.element('label', null, null, null, null);
77 | label.setAttribute('for', id);
78 | label.textContent = ariaLabel;
79 | divCheckbox.appendChild(checkbox);
80 | divCheckbox.appendChild(label);
81 | return divCheckbox;
82 | };
83 |
84 | template.divDialog = (cssModifier, toolSet) => {
85 | const divDialog = template.element('div', 'dialog', cssModifier, null, null);
86 | const divDialogBtns = template.element('div', 'dialog-btns', cssModifier,
87 | null, null);
88 | for (const tool of toolSet) {
89 | let element;
90 | if (tool.type === 'btn') {
91 | element = template.element('div', 'btn-dialog', tool.cssModifier,
92 | tool.ariaLabel, tool.label);
93 | divDialogBtns.appendChild(element);
94 | } else if (tool.type === 'input') {
95 | element = template.input('dialog-input', cssModifier, tool.ariaLabel);
96 | divDialog.appendChild(element);
97 | } else if (tool.type === 'label') {
98 | element = template.element('div', 'dialog-label', cssModifier, null,
99 | null);
100 | if (tool.text) {
101 | element.textContent = tool.text;
102 | }
103 | divDialog.appendChild(element);
104 | } else if (tool.type === 'textarea') {
105 | element = template.element('textarea', 'dialog-textarea', cssModifier,
106 | tool.ariaLabel, null);
107 | divDialog.appendChild(element);
108 | }
109 | }
110 | divDialog.appendChild(divDialogBtns);
111 | return divDialog;
112 | };
113 |
114 | template.element = (tagName, cssBlock, cssModifier, ariaLabel, textContent) => {
115 | const element = document.createElement(tagName);
116 | if (cssBlock) {
117 | element.classList.add(cssBlock);
118 | }
119 | if (cssModifier) {
120 | element.classList.add(`${cssBlock}--${cssModifier}`);
121 | }
122 | if (ariaLabel) {
123 | element.setAttribute('aria-label', ariaLabel);
124 | }
125 | if (textContent) {
126 | element.textContent = textContent;
127 | }
128 | return element;
129 | };
130 |
131 | template.input = (cssBlock, cssModifier, ariaLabel) => {
132 | const input = template.element('input', cssBlock, cssModifier, ariaLabel,
133 | null);
134 | input.setAttribute('type', 'text');
135 | return input;
136 | };
137 |
138 | template.page = (cssModifier) => {
139 | const page = template.element('div', 'page', cssModifier, null, null);
140 | page.classList.add('page--hide');
141 | return page;
142 | };
143 |
144 | template.paragraph = (verseObj) => {
145 | let paragraphDiv = null;
146 | if (tomeLists.paragraphs) {
147 | const verseIdx = verseObj.k;
148 | if (tomeLists.paragraphs.includes(verseIdx)) {
149 | paragraphDiv = template.element('div', 'paragraph', null, null, null);
150 | paragraphDiv.textContent = '¶';
151 | }
152 | }
153 | return paragraphDiv;
154 | };
155 |
156 | template.scroll = (cssModifier) => {
157 | const scroll = template.element('div', 'scroll', cssModifier, null, null);
158 | return scroll;
159 | };
160 |
161 | template.strongList = (list, cssModifier) => {
162 | const strongList = template.element('ul', 'strong-def-list', cssModifier,
163 | null, null);
164 | for (const listItem of list) {
165 | const strongItem = template.element('li', 'list-item', null, null, null);
166 | const frags = listItem.split(/[HG]\d+/);
167 | const words = listItem.match(/[HG]\d+/g);
168 | if (words) {
169 | frags.map((value, index) => {
170 | const span = document.createElement('span');
171 | span.textContent = value;
172 | strongItem.appendChild(span);
173 | if (words[index]) {
174 | const num = words[index];
175 | const btn = template.element('div', 'btn-strong-def', null, null,
176 | num);
177 | btn.dataset.strongDef = num;
178 | strongItem.appendChild(btn);
179 | }
180 | });
181 | } else {
182 | strongItem.textContent = listItem;
183 | }
184 | strongList.appendChild(strongItem);
185 | }
186 | return strongList;
187 | };
188 |
189 | template.superscription = (verseObj) => {
190 | let superscriptionDiv = null;
191 | if (tomeLists.superscriptions) {
192 | const verseIdx = verseObj.k;
193 | if (verseIdx in tomeLists.superscriptions) {
194 | superscriptionDiv = template.element('div', 'superscription', null,
195 | null, null);
196 | superscriptionDiv.textContent = tomeLists.superscriptions[verseIdx];
197 | }
198 | }
199 | return superscriptionDiv;
200 | };
201 |
202 | template.toolbar = (cssModifier) => {
203 | const toolbar = template.element('div', 'toolbar', cssModifier, null, null);
204 | return toolbar;
205 | };
206 |
207 | template.toolbarLower = (toolSet) => {
208 | const toolbarLower = template.toolbar('lower');
209 | for (const tool of toolSet) {
210 | let element;
211 | if (tool.type === 'btn') {
212 | element = template.btnIcon(tool.icon, tool.icon, tool.ariaLabel);
213 | toolbarLower.appendChild(element);
214 | } else if (tool.type === 'input') {
215 | element = template.input('input', tool.modifier, tool.ariaLabel);
216 | toolbarLower.appendChild(element);
217 | }
218 | }
219 | return toolbarLower;
220 | };
221 |
222 | template.toolbarMenu = (modifier, actionSet) => {
223 | const toolbarMenu = template.element('div', 'toolbar-menu', modifier, null,
224 | null);
225 | toolbarMenu.classList.add('toolbar-menu--hide');
226 | for (const btn of actionSet) {
227 | const element = template.btnIcon(btn.icon, `${modifier}-${btn.icon}`,
228 | btn.label);
229 | toolbarMenu.appendChild(element);
230 | }
231 | return toolbarMenu;
232 | };
233 |
234 | template.toolbarUpper = (toolSet) => {
235 | const toolbarUpper = template.toolbar('upper');
236 | for (const tool of toolSet) {
237 | let element;
238 | if (tool.type === 'btn') {
239 | element = template.btnIcon(tool.icon, tool.icon, tool.ariaLabel);
240 | toolbarUpper.appendChild(element);
241 | } else if (tool.type === 'btn-banner') {
242 | element = template.btnBanner(tool.cssModifier, tool.ariaLabel);
243 | toolbarUpper.appendChild(element);
244 | } else if (tool.type === 'banner') {
245 | element = template.element('div', 'banner', tool.cssModifier, null,
246 | tool.text);
247 | toolbarUpper.appendChild(element);
248 | }
249 | }
250 | return toolbarUpper;
251 | };
252 |
--------------------------------------------------------------------------------
/js/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const util = {};
4 |
5 | util.centerScrollElement = (scrollElement, element) => {
6 | const y = element.offsetTop - scrollElement.offsetTop -
7 | (scrollElement.clientHeight - element.clientHeight) / 2;
8 | scrollElement.scrollTop = y;
9 | };
10 |
11 | util.range = (start, stop, step = 1) => {
12 | return Array(Math.ceil((stop - start) / step))
13 | .fill(start)
14 | .map((x, y) => x + y * step);
15 | };
16 |
17 | util.removeAllChildren = (element) => {
18 | while (element.hasChildNodes()) {
19 | element.removeChild(element.lastChild);
20 | }
21 | };
22 |
23 | util.sideScrollElement = (scrollElement, element) => {
24 | const x = element.offsetLeft - 8;
25 | scrollElement.scrollLeft = x;
26 | };
27 |
28 | util.writeClipboardText = async (text) => {
29 | try {
30 | await navigator.clipboard.writeText(text);
31 | } catch (error) {
32 | console.log(error.message);
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "transparent",
3 | "description": "KJS - King James with Strong",
4 | "dir": "ltr",
5 | "display": "standalone",
6 | "icons": [
7 | {
8 | "src": "/png/icon-192.png",
9 | "sizes": "192x192",
10 | "type": "image/png",
11 | "purpose": "any"
12 | },
13 | {
14 | "src": "/png/icon-512.png",
15 | "sizes": "512x512",
16 | "type": "image/png",
17 | "purpose": "any"
18 | },
19 | {
20 | "src": "/png/maskable-icon-192.png",
21 | "sizes": "192x192",
22 | "type": "image/png",
23 | "purpose": "maskable"
24 | },
25 | {
26 | "src": "/png/maskable-icon-512.png",
27 | "sizes": "512x512",
28 | "type": "image/png",
29 | "purpose": "maskable"
30 | }
31 | ],
32 | "id": "/",
33 | "lang": "en",
34 | "name": "KJS - King James with Strong",
35 | "orientation": "any",
36 | "scope": "/",
37 | "screenshots": [
38 | {
39 | "form_factor": "narrow",
40 | "label": "Android Screenshot",
41 | "sizes": "1080x2220",
42 | "src": "/webp/android.webp",
43 | "type": "image/webp"
44 | },
45 | {
46 | "form_factor": "wide",
47 | "label": "Desktop Screenshot",
48 | "sizes": "816x960",
49 | "src": "/webp/desktop.webp",
50 | "type": "image/webp"
51 | }
52 | ],
53 | "short_name": "KJS",
54 | "start_url": "/",
55 | "theme_color": "transparent"
56 | }
--------------------------------------------------------------------------------
/png/icon-032.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/png/icon-032.png
--------------------------------------------------------------------------------
/png/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/png/icon-192.png
--------------------------------------------------------------------------------
/png/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/png/icon-512.png
--------------------------------------------------------------------------------
/png/maskable-icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/png/maskable-icon-192.png
--------------------------------------------------------------------------------
/png/maskable-icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/png/maskable-icon-512.png
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './js/app.js',
3 | output: {
4 | file: './bundle.js',
5 | format: 'iife',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/sw.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const appCaches = [
4 | {
5 | name: 'core-20250710.01',
6 | urls: [
7 | '/',
8 | '/bundle.js',
9 | '/favicon.png',
10 | '/help/about.html',
11 | '/icons.svg',
12 | '/index.html',
13 | '/js/load.js',
14 | '/js/progress.js',
15 | '/manifest.json',
16 | '/robots.txt',
17 | ]
18 | },
19 | {
20 | name: 'css-20250715.01',
21 | urls: [
22 | '/css/font.css',
23 | '/css/kjs.css',
24 | ]
25 | },
26 | {
27 | name: 'font-20250710.01',
28 | urls: [
29 | '/font/courgette-v18-latin-regular.woff2',
30 | '/font/inconsolata-v36-latin-regular.woff2',
31 | '/font/merienda-v21-latin-regular.woff2',
32 | '/font/merriweather-v32-latin-regular.woff2',
33 | '/font/noto-serif-hebrew-v29-latin-regular.woff2',
34 | '/font/open-sans-v43-latin-regular.woff2',
35 | '/font/roboto-mono-v30-latin-regular.woff2',
36 | '/font/roboto-slab-v35-latin-regular.woff2',
37 | '/font/roboto-v48-latin-regular.woff2',
38 | ]
39 | },
40 | {
41 | name: 'help-20250710.01',
42 | urls: [
43 | '/help/bookmark.html',
44 | '/help/clipboard-mode.html',
45 | '/help/help.html',
46 | '/help/name-mode.html',
47 | '/help/navigator.html',
48 | '/help/overview.html',
49 | '/help/read.html',
50 | '/help/search.html',
51 | '/help/setting.html',
52 | '/help/strong.html',
53 | ]
54 | },
55 | {
56 | name: 'json-20250710.01',
57 | urls: [
58 | '/json/kjv_lists.json',
59 | '/json/kjv_name.json',
60 | '/json/kjv_pure.json',
61 | '/json/strong_dict.json',
62 | '/json/strong_name.json',
63 | '/json/strong_pure.json',
64 | ]
65 | },
66 | {
67 | name: 'png-20240516.02',
68 | urls: [
69 | '/png/icon-032.png',
70 | '/png/icon-192.png',
71 | '/png/icon-512.png',
72 | '/png/maskable-icon-192.png',
73 | '/png/maskable-icon-512.png',
74 | ],
75 | },
76 | {
77 | name: 'webp-20240509.01',
78 | urls: [
79 | '/webp/android.webp',
80 | '/webp/desktop.webp',
81 | ],
82 | },
83 | ];
84 |
85 | const cacheNames = appCaches.map((cache) => cache.name);
86 |
87 | self.addEventListener('install', (event) => {
88 | event.waitUntil(caches.keys().then((keys) => {
89 | return Promise.all(appCaches.map(async (appCache) => {
90 | if (keys.indexOf(appCache.name) === -1) {
91 | const cache = await caches.open(appCache.name);
92 | console.log(`Caching: ${appCache.name}`);
93 | return await cache.addAll(appCache.urls);
94 | } else {
95 | console.log(`Found: ${appCache.name}`);
96 | return Promise.resolve(true);
97 | }
98 | }));
99 | }));
100 | self.skipWaiting();
101 | });
102 |
103 | self.addEventListener('activate', (event) => {
104 | event.waitUntil(
105 | caches.keys().then((keys) => {
106 | return Promise.all(keys.map((key) => {
107 | if (cacheNames.indexOf(key) === -1) {
108 | console.log(`Deleting: ${key}`);
109 | return caches.delete(key);
110 | }
111 | }));
112 | })
113 | );
114 | self.clients.claim();
115 | });
116 |
117 | self.addEventListener('fetch', (event) => {
118 | event.respondWith(
119 | caches.match(event.request).then((response) => response ||
120 | fetch(event.request).then((response) => response)).catch((error) => {
121 | console.log('Fetch failed:', error);
122 | })
123 | );
124 | });
125 |
--------------------------------------------------------------------------------
/webp/android.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/webp/android.webp
--------------------------------------------------------------------------------
/webp/desktop.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1John419/kjs/b9e772ff615247f53bfb98b280d73d81126c2c60/webp/desktop.webp
--------------------------------------------------------------------------------