Source

frontend/components/sword_notes.js

/* 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 eventController = require('../controllers/event_controller.js');
const verseListController = require('../controllers/verse_list_controller.js');

/**
 * The SwordNotes component implements the handling of SWORD-based note elements
 * like cross-references and footnotes.
 * 
 * @category Component
 */
class SwordNotes {
  constructor() {
    this.notesCharacter = null;

    eventController.subscribe('on-bible-text-loaded', (tabIndex) => {
      this.initForTab(tabIndex);
    });
  }
  
  getNotesCharacter() {
    if (this.notesCharacter == null) {
      this.notesCharacter = i18n.t('bible-browser.footnote-character');
    }

    return this.notesCharacter;
  }

  getCurrentTabNotes(tabIndex) {
    var verseList = verseListController.getCurrentVerseList(tabIndex);
    var swordNotes = verseList[0].querySelectorAll('.sword-note');
    return swordNotes;
  }

  createMarker(markerClass, title, content) {
    var marker = document.createElement('div');
    marker.classList.add(markerClass);
    marker.classList.add('sword-marker');
    marker.setAttribute('title', title);
    marker.textContent = content;

    return marker;
  }

  initForTab(tabIndex=undefined) {
    var swordNotes = this.getCurrentTabNotes(tabIndex);
    this.initNotes(swordNotes);
  }

  initForContainer(container) {
    if (container == null) {
      return;
    }

    var swordNotes = container.querySelectorAll('.sword-note');
    this.initNotes(swordNotes);
  }

  cleanNotes(swordNotes) {
    var filteredNotes = [...swordNotes].filter(e => {
      return e.getAttribute('type') == 'crossReference';
    });

    var textNodes = [];

    for (let i = 0; i < filteredNotes.length; i++) {
      var currentNote = filteredNotes[i];

      var nextNode;
      var walk = document.createTreeWalker(currentNote, NodeFilter.SHOW_TEXT);

      // eslint-disable-next-line no-cond-assign
      while (nextNode = walk.nextNode()) {
        if (nextNode.parentNode.nodeName != "REFERENCE") {
          textNodes.push(nextNode);
        }
      }
    }

    for (let i = 0; i < textNodes.length; i++) {
      textNodes[i].replaceWith("");
    }
  }

  initNotes(swordNotes) {
    //console.time('SwordNotes.initForTab');
    //console.log(`Got ${swordNotes.length} sword xref elements!`);

    // Within crossReference notes: Remove text nodes containing ';'
    this.cleanNotes(swordNotes);
    var notesCharacter = this.getNotesCharacter();

    for (let i = 0; i < swordNotes.length; i++) {
      var currentNote = swordNotes[i];

      if (currentNote.hasAttribute('type') && currentNote.getAttribute('type') == 'crossReference') {
        this.initCrossReferenceNote(currentNote);
      } else {
        this.initRegularNote(currentNote, notesCharacter);
      }
    }

    var jqSwordNotes = $(swordNotes);
    jqSwordNotes.css('display', 'inline-block');
    jqSwordNotes.css('margin-left', '0.1em');
    //console.timeEnd('SwordNotes.initForTab');
  }

  initCrossReferenceNote(note) {
    var noteContent = note.innerHTML;

    if (noteContent.indexOf("sword-xref-marker") == -1) {
      var currentReferences = note.querySelectorAll('reference');
      var currentTitle = "";

      if (currentReferences.length > 1) {
        var currentTitleArray = [];

        currentReferences.forEach(ref => {
          var currentRef = ref.textContent;
          currentTitleArray.push(currentRef);
        });

        currentTitle = currentTitleArray.join('; ');

      } else if (currentReferences.length == 1) {

        currentTitle = currentReferences[0].textContent;
      }

      var xrefMarker = this.createMarker('sword-xref-marker', currentTitle, 'x');
      note.insertBefore(xrefMarker, note.firstChild);
    } else {
      // This is necessary when reloading a tab from cache. For some reason, the x content in the notes is not persisted.
      note.firstChild.innerText = 'x';
    }
  }

  initRegularNote(note, notesCharacter) {
    var noteContent = note.innerHTML;

    if (noteContent.indexOf("sword-note-marker") == -1) {
      var currentTitle = note.textContent;
      var noteMarker = this.createMarker('sword-note-marker', currentTitle, notesCharacter);
      note.innerText = "";
      note.insertBefore(noteMarker, note.firstChild);
    }
  }
}

module.exports = SwordNotes;