/* This file is part of Ezra Bible App.
Copyright (C) 2019 - 2023 Ezra Bible App Development Team <contact@ezrabibleapp.net>
Ezra Bible App 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 2 of the License, or
(at your option) any later version.
Ezra Bible App 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.
You should have received a copy of the GNU General Public License
along with Ezra Bible App. See the file LICENSE.
If not, see <http://www.gnu.org/licenses/>. */
const VerseBox = require("../ui_models/verse_box.js");
const i18nHelper = require('../helpers/i18n_helper.js');
const eventController = require('../controllers/event_controller.js');
const { getPlatform } = require('../helpers/ezra_helper.js');
const verseListController = require('../controllers/verse_list_controller.js');
const VerseBoxHelper = require('../helpers/verse_box_helper.js');
const sectionLabelHelper = require('../helpers/section_label_helper.js');
const MAX_VERSES_FOR_DETAILED_LABEL = 5;
/**
* The VerseSelection component implements the label that shows the currently selected verses
* and it provides an API and an event (on-verses-selected) that other components can use to get the current verse selection.
*
* @category Component
*/
class VerseSelection {
constructor() {
this.selectedVerseBoxElements = [];
this.selectedVerseReferences = null;
this.previousSelectionExisting = false;
this.previousFirstVerseReference = null;
this.previousVerseCount = null;
this.verseReferenceHelper = null;
this.previousSelectionIndex = -1;
this.verseBoxHelper = new VerseBoxHelper();
eventController.subscribe('on-locale-changed', async () => {
let currentTab = app_controller.tab_controller.getTab();
let textType = currentTab.getTextType();
if (textType != 'tagged_verses' && textType != 'search_results') {
await this.updateSelectedVersesLabel();
}
if (this.allVersesSelected() || this.someVersesSelected()) {
let selectionLocaleText = '';
if (textType == 'search_results') {
if (this.allVersesSelected()) {
selectionLocaleText = 'bible-browser.all-search-results';
} else if (this.someVersesSelected()) {
selectionLocaleText = 'bible-browser.some-search-results';
}
} else if (textType == 'tagged_verses') {
if (this.allVersesSelected()) {
selectionLocaleText = 'bible-browser.all-verses';
} else if (this.someVersesSelected()) {
selectionLocaleText = 'bible-browser.all-verses';
}
}
this.updateSelected();
this.updateViewsAfterVerseSelection(i18n.t(selectionLocaleText));
}
});
eventController.subscribe('on-bible-text-loaded', (tabIndex) => {
this.init(tabIndex);
});
eventController.subscribe('on-tab-selected', (tabIndex) => {
this.clearVerseSelection(true, tabIndex);
});
eventController.subscribe('on-all-translations-removed', () => {
this.clearVerseSelection();
});
}
getSelectedVerseBoxes() {
return this.selectedVerseBoxElements;
}
initSelectable(verseList) {
if (verseList.hasClass('ui-selectable')) {
verseList.selectable('destroy');
}
verseList.selectable({
filter: '.verse-text',
cancel: '.verse-reference-content, .sword-xref-marker, .verse-notes, .tag-box, .tag, .load-book-results, .select-all-verses-button, tag-distribution-matrix',
// eslint-disable-next-line no-unused-vars
start: (event, ui) => {
// Only reset existing selection if metaKey and ctrlKey are not pressed.
// If one of these keys is pressed that indicates that the user wants to select individual non-consecutive verses.
// And in this case the start event is fired for each individual verse.
if (event.metaKey == false && event.ctrlKey == false) {
this.selectedVerseReferences = new Array;
this.selectedVerseBoxElements = new Array;
}
app_controller.handleBodyClick(event);
},
// eslint-disable-next-line no-unused-vars
stop: (event, ui) => {
this.updateSelected(verseList);
let currentFirstVerseReference = this.getFirstSelectedVerseReferenceId();
let currentVerseCount = this.selectedVerseBoxElements.length;
if (this.previousSelectionExisting &&
currentFirstVerseReference == this.previousFirstVerseReference &&
currentVerseCount == this.previousVerseCount) {
this.clearVerseSelection(true, undefined);
this.updateViewsAfterVerseSelection();
this.previousSelectionExisting = false;
} else {
this.updateViewsAfterVerseSelection();
this.publishVersesSelected();
this.previousSelectionExisting = true;
if (this.selectedVerseBoxElements.length > 0) {
this.previousFirstVerseReference = this.getFirstSelectedVerseReferenceId();
this.previousVerseCount = this.selectedVerseBoxElements.length;
} else {
this.previousVerseCount = 0;
this.previousFirstVerseReference = null;
}
}
},
// Solution taken from https://stackoverflow.com/a/14469388/1269556
// This implements shift-based selection
// (adding verses between the first clicked verse and the last clicked verse while holding shift)
selecting: (e, ui) => { // on select
let currentSelectionIndex = $(ui.selecting.tagName, e.target).index(ui.selecting); // get selecting item index
if (this.previousVerseCount != null && this.previousVerseCount > 1) {
currentSelectionIndex = currentSelectionIndex - this.previousVerseCount;
}
// if shift key was pressed and there is a previous selection - select all verses between the two clicked verses
if(e.shiftKey && this.previousSelectionIndex > -1) {
let startIndex = Math.min(this.previousSelectionIndex, currentSelectionIndex) - 5;
let endIndex = Math.max(this.previousSelectionIndex, currentSelectionIndex) + 5;
let $selectedElements = $(ui.selecting.tagName, e.target).slice(startIndex, endIndex);
for (let i = 0; i < $selectedElements.length; i++) {
let currentElement = $selectedElements[i];
if (currentElement.classList.contains('verse-text')) {
currentElement.classList.add('ui-selected');
}
}
this.previousSelectionIndex = -1; // and reset previousSelection
} else {
this.previousSelectionIndex = currentSelectionIndex; // othervise just save previousSelection
}
},
// eslint-disable-next-line no-unused-vars
selected: (event, ui) => {
// Not needed anymore!
}
});
}
getFirstSelectedVerseReferenceId() {
if (this.selectedVerseBoxElements != null && this.selectedVerseBoxElements.length > 0) {
return this.selectedVerseBoxElements[0].getAttribute('verse-reference-id');
} else {
return null;
}
}
publishVersesSelected(tabIndex=undefined) {
eventController.publishAsync('on-verses-selected', {
'selectedElements': this.selectedVerseBoxElements,
'selectedVerseTags': this.getCurrentSelectionTags(),
'tabIndex': tabIndex
});
}
init(tabIndex) {
var currentVerseListFrame = verseListController.getCurrentVerseListFrame(tabIndex);
this.initSelectable(currentVerseListFrame);
// This event handler ensures that the selection is cancelled
// if the user clicks somewhere else in the verse list
currentVerseListFrame.bind('click', (e) => {
if (e.target.matches('.tag-box') ||
e.target.matches('.verse-box') ||
e.target.matches('.verse-list-frame')) {
this.clearVerseSelection();
app_controller.handleBodyClick(e);
}
});
}
versesSelected() {
return this.selectedVerseBoxElements.length > 0;
}
setVerseAsSelection(verseText) {
if (verseText != null) {
this.clearVerseSelection(false, undefined);
verseText.classList.add('ui-selected');
verseText.classList.add('ui-selectee');
this.selectedVerseBoxElements.push(verseText);
this.updateSelected();
this.updateViewsAfterVerseSelection();
this.publishVersesSelected();
}
}
updateSelected(verseList=undefined) {
if (verseList == undefined) {
verseList = verseListController.getCurrentVerseList();
}
this.selectedVerseBoxElements = verseList.find('.ui-selected').closest('.verse-box');
var selectedVerseReferences = [];
for (var i = 0; i < this.selectedVerseBoxElements.length; i++) {
var verseBox = $(this.selectedVerseBoxElements[i]);
var currentVerseReferenceAnchor = verseBox.find('a:first').attr('name');
var splittedVerseReference = currentVerseReferenceAnchor.split(" ");
var currentVerseReference = splittedVerseReference[splittedVerseReference.length - 1];
selectedVerseReferences.push(currentVerseReference);
}
this.selectedVerseReferences = selectedVerseReferences;
}
clearVerseSelection(updateViews=true, tabIndex=undefined) {
this.previousSelectionIndex = -1;
this.selectedVerseReferences = new Array;
this.selectedVerseBoxElements = new Array;
var verseList = verseListController.getCurrentVerseList(tabIndex);
verseList[0].querySelectorAll('.ui-selected').forEach((verseText) => {
verseText.classList.remove('ui-selectee');
verseText.classList.remove('ui-selected');
verseText.classList.remove('ui-state-highlight');
});
if (updateViews) {
this.updateViewsAfterVerseSelection();
this.publishVersesSelected(tabIndex);
}
}
async getSelectedBooks() {
var selectedBooks = [];
for (var i = 0; i < this.selectedVerseBoxElements.length; i++) {
var currentVerseBox = this.selectedVerseBoxElements[i];
var currentBookShortName = new VerseBox(currentVerseBox).getBibleBookShortTitle();
if (!selectedBooks.includes(currentBookShortName)) {
selectedBooks.push(currentBookShortName);
}
}
return selectedBooks;
}
element_list_to_xml_verse_list(element_list) {
var xml_verse_list = "<verse-list>";
for (var i = 0; i < element_list.length; i++) {
var verse_box_element = $(element_list[i]).closest('.verse-box')[0];
var verse_reference = verse_box_element.querySelector('.verse-reference-content').innerText;
var verse_reference_id = "";
var verse_box = new VerseBox(verse_box_element);
verse_reference_id = verse_box.getVerseReferenceId();
var verse_bible_book = verse_box.getBibleBookShortTitle();
var abs_verse_nr = verse_box.getAbsoluteVerseNumber();
xml_verse_list += "<verse>";
xml_verse_list += "<verse-bible-book>" + verse_bible_book + "</verse-bible-book>";
xml_verse_list += "<verse-reference>" + verse_reference + "</verse-reference>";
xml_verse_list += "<verse-reference-id>" + verse_reference_id + "</verse-reference-id>";
xml_verse_list += "<abs-verse-nr>" + abs_verse_nr + "</abs-verse-nr>";
xml_verse_list += "</verse>";
}
xml_verse_list += "</verse-list>";
return xml_verse_list;
}
current_verse_selection_as_xml() {
var selected_verse_elements = this.selectedVerseBoxElements;
return (this.element_list_to_xml_verse_list(selected_verse_elements));
}
current_verse_selection_as_verse_reference_ids() {
var selected_verse_ids = new Array;
var selected_verse_elements = this.selectedVerseBoxElements;
for (var i = 0; i < selected_verse_elements.length; i++) {
var verse_box_element = selected_verse_elements[i];
var verse_box = new VerseBox(verse_box_element);
var verse_reference_id = verse_box.getVerseReferenceId();
selected_verse_ids.push(verse_reference_id);
}
return selected_verse_ids;
}
async getSelectedVerseLabelText(selectedVerseDisplayText=undefined, useShortBookTitles=false) {
var preDefinedText = false;
if (!this.someVersesSelected()) {
if (selectedVerseDisplayText == undefined && !this.someVersesSelected()) {
const selectedBooks = await this.getSelectedBooks();
selectedVerseDisplayText = await sectionLabelHelper.getVerseDisplayText(selectedBooks, this.selectedVerseBoxElements, false, useShortBookTitles);
} else {
preDefinedText = true;
}
}
var selectedVersesLabel = this.getSelectedVersesLabel();
var selectedVersesLabelText = selectedVersesLabel.text();
var allSearchResultsText = i18n.t('bible-browser.all-search-results');
var someSearchResultsText = i18n.t('bible-browser.some-search-results');
var selectedVerseReferenceCount = this.selectedVerseReferences.length;
if ((selectedVersesLabelText == allSearchResultsText || selectedVersesLabelText == someSearchResultsText) && selectedVerseReferenceCount > 1 && !preDefinedText) {
selectedVerseDisplayText = i18n.t('bible-browser.some-search-results');
}
return selectedVerseDisplayText;
}
async updateSelectedVersesLabel(selectedVerseDisplayText=undefined) {
selectedVerseDisplayText = await this.getSelectedVerseLabelText(selectedVerseDisplayText);
this.getSelectedVersesLabel().html(selectedVerseDisplayText);
}
async updateViewsAfterVerseSelection(selectedVerseDisplayText=undefined) {
await this.updateSelectedVersesLabel(selectedVerseDisplayText);
var tabId = app_controller.tab_controller.getSelectedTabId();
if (tabId !== undefined) {
uiHelper.configureButtonStyles('#' + tabId);
}
}
getSelectedVersesLabel() {
var currentVerseListMenu = app_controller.getCurrentVerseListMenu();
return $(currentVerseListMenu.find('.selected-verses')[0]);
}
async getSelectedVerseText(html=false) {
const bibleTranslationId = app_controller.tab_controller.getTab().getBibleTranslationId();
const separator = await i18nHelper.getReferenceSeparator(bibleTranslationId);
const selectedBooks = await this.getSelectedBooks();
let verseReferenceText = await sectionLabelHelper.getVerseDisplayText(selectedBooks, this.selectedVerseBoxElements, true);
let selectedText = await this.verseBoxHelper.getVerseTextFromVerseElements(this.selectedVerseBoxElements, verseReferenceText, html, separator);
return selectedText;
}
async copySelectedVerseTextToClipboard() {
let selectedVerseText = await this.getSelectedVerseText();
let selectedVerseTextHtml = await this.getSelectedVerseText(true);
getPlatform().copyToClipboard(selectedVerseText, selectedVerseTextHtml);
// eslint-disable-next-line no-undef
iziToast.success({
message: i18n.t('bible-browser.copy-verse-text-to-clipboard-success'),
position: 'bottomRight',
timeout: 2000
});
}
getCurrentSelectionTags() {
var verse_selection_tags = new Array;
if (this.selectedVerseBoxElements == null) {
return verse_selection_tags;
}
for (let i = 0; i < this.selectedVerseBoxElements.length; i++) {
let current_verse_box = $(this.selectedVerseBoxElements[i]);
let current_tag_list = current_verse_box.find('.tag-data').children();
for (let j = 0; j < current_tag_list.length; j++) {
let current_tag = $(current_tag_list[j]);
let current_tag_title = current_tag.find('.tag-title').html();
let tag_obj = null;
for (let k = 0; k < verse_selection_tags.length; k++) {
let current_tag_obj = verse_selection_tags[k];
if (current_tag_obj.title == current_tag_title &&
current_tag_obj.category == current_tag.attr('class')) {
tag_obj = current_tag_obj;
break;
}
}
if (tag_obj == null) {
tag_obj = {
title: current_tag_title,
category: current_tag.attr('class'),
count: 0
};
verse_selection_tags.push(tag_obj);
}
tag_obj.count += 1;
}
}
for (let i = 0; i < verse_selection_tags.length; i++) {
let current_tag_obj = verse_selection_tags[i];
current_tag_obj.complete = (current_tag_obj.count == this.selectedVerseBoxElements.length);
}
return verse_selection_tags;
}
getSelectedElements() {
return this.selectedVerseBoxElements;
}
selectAllVerses(selectionLocaleText) {
const currentVerseList = verseListController.getCurrentVerseList();
let allVerseTextElements = currentVerseList[0].querySelectorAll('.verse-text');
allVerseTextElements.forEach((verseTextElement) => {
verseTextElement.classList.add('ui-selected');
});
this.updateSelected();
this.updateViewsAfterVerseSelection(i18n.t(selectionLocaleText));
this.publishVersesSelected();
}
allVersesSelected() {
const currentVerseList = verseListController.getCurrentVerseList();
let allVerseTextElements = currentVerseList[0].querySelectorAll('.verse-text');
let allSelectedElements = currentVerseList[0].querySelectorAll('.ui-selected');
return allVerseTextElements.length == allSelectedElements.length != 0;
}
someVersesSelected() {
const currentVerseList = verseListController.getCurrentVerseList();
let someVersesSelected = false;
if (currentVerseList != null) {
let allSelectedElements = currentVerseList[0].querySelectorAll('.ui-selected');
someVersesSelected = allSelectedElements.length > MAX_VERSES_FOR_DETAILED_LABEL;
}
return someVersesSelected;
}
}
module.exports = VerseSelection;
Source