Source

frontend/helpers/sword_module_helper.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/>. */

/**
 * This module offers sword module related functionality
 * @module sword_module_helper
 * @category Helper
 */

const i18nHelper = require('./i18n_helper.js');

const PUBLIC_LICENSES = ['Public Domain', 'General public license for distribution for any purpose'];

var _moduleVersificationCache = {};
var _cachedModule;

module.exports.getSwordModule = async function(moduleId, isRemote=false) {
  if (moduleId == null) {
    return null;
  }

  if (!_cachedModule || _cachedModule.name !== moduleId || (_cachedModule.name === moduleId && _cachedModule.remote !== isRemote)) {
    let swordModule = null;

    try {
      if (isRemote) {
        swordModule = await ipcNsi.getRepoModule(moduleId);
      } else {
        swordModule = await ipcNsi.getLocalModule(moduleId);
      }
      swordModule.remote = isRemote;

      _cachedModule = swordModule;
    } catch (e) {
      console.log(`Could not get ${isRemote ? 'remote' : 'local'} sword module for ${moduleId}`);
    }

  }
  return _cachedModule;
};

module.exports.resetModuleCache = function() {
  _cachedModule = null;
};

module.exports.getModuleDescription = async function(moduleId, isRemote=false) {

  const swordModule = await this.getSwordModule(moduleId, isRemote);

  if (!swordModule) {
    return "No info available!";
  }

  var moduleInfo = "";   
  if (isRemote) {
    moduleInfo += "<b>" + swordModule.description + "</b><br><br>";
  }
  moduleInfo += await this.getModuleAbout(swordModule);

  moduleInfo = urlify(moduleInfo);

  return moduleInfo;
};

module.exports.getModuleInfo = async function(moduleId, isRemote=false, includeModuleDescription=true) {

  const swordModule = await this.getSwordModule(moduleId, isRemote);

  if (!swordModule) {
    return "No info available!";
  }

  var moduleInfo = "No info available!";

  try {
    
    moduleInfo = "";

    if (includeModuleDescription) {
      moduleInfo += await this.getModuleDescription(moduleId, isRemote);
    }
    
    var yes = i18n.t("general.yes");
    var no = i18n.t("general.no");

    if (includeModuleDescription) {
      moduleInfo += `<p style='margin-top: 1em; padding-top: 1em; border-top: 1px solid grey; font-weight: bold'>${i18n.t("general.sword-module-info")}</p>`;
    }

    let lastUpdate = swordModule.lastUpdate;

    if (lastUpdate != "") {
      try {
        lastUpdate = i18nHelper.getLocalizedDate(lastUpdate);
      } catch (e) {
        console.log(e);
      }
    }

    moduleInfo += "<table>";
    moduleInfo += "<tr><td style='width: 11em;'>" + i18n.t("general.module-name") + ":</td><td>" + swordModule.name + "</td></tr>";
    moduleInfo += "<tr><td>" + i18n.t("general.module-version") + ":</td><td>" + swordModule.version + "</td></tr>";
    moduleInfo += "<tr><td>" + i18n.t("general.module-last-update") + ":</td><td>" + lastUpdate + "</td></tr>";
    moduleInfo += "<tr><td>" + i18n.t("general.module-language") + ":</td><td>" + i18nHelper.getLanguageName(swordModule.language) + "</td></tr>";
    moduleInfo += "<tr><td>" + i18n.t("general.module-license") + ":</td><td>" + swordModule.distributionLicense + "</td></tr>";

    if (swordModule.type == 'Biblical Texts') {
      moduleInfo += "<tr><td>" + i18n.t("general.module-strongs") + ":</td><td>" + (swordModule.hasStrongs ? yes : no) + "</td></tr>";
      moduleInfo += "<tr><td>" + i18n.t("general.module-headings") + ":</td><td>" + (swordModule.hasHeadings ? yes : no) + "</td></tr>";
      moduleInfo += "<tr><td>" + i18n.t("general.module-footnotes") + ":</td><td>" + (swordModule.hasFootnotes ? yes : no) + "</td></tr>";
      moduleInfo += "<tr><td>" + i18n.t("general.module-xrefs") + ":</td><td>" + (swordModule.hasCrossReferences ? yes : no) + "</td></tr>";
      moduleInfo += "<tr><td>" + i18n.t("general.module-redletter") + ":</td><td>" + (swordModule.hasRedLetterWords ? yes : no) + "</td></tr>";
    }

    moduleInfo += "<tr><td>" + i18n.t("general.module-size") + ":</td><td>" + this.getModuleSize(swordModule) + "</td></tr>";

    if (swordModule.repository != '') {
      moduleInfo += "<tr><td>" + i18n.t("module-assistant.repository_singular") + ":</td><td>" + swordModule.repository + "</td></tr>";
    }

    if (!isRemote) {
      moduleInfo += "<tr><td>" + i18n.t("general.module-location") + ":</td><td>" + swordModule.location + "</td></tr>";
    }

    moduleInfo += "</table>";

    if (isRemote && swordModule.locked && swordModule.unlockInfo != "") {
      moduleInfo += "<p style='margin-top: 1em; padding-top: 1em; border-top: 1px solid grey; font-weight: bold'>" + i18n.t("general.sword-unlock-info") + "</p>";
      moduleInfo += "<p class='external'>" + swordModule.unlockInfo + "</p>";
    }
  } catch (ex) {
    console.error("Got exception while trying to get module info: " + ex);
  }

  return moduleInfo;
};

module.exports.getModuleAbout = async function(swordModuleOrId) {
  var swordModule;
  if (typeof swordModuleOrId === 'string') {
    swordModule = await this.getSwordModule(swordModuleOrId);
  } else {
    swordModule = swordModuleOrId;
  }

  if (!swordModule || !swordModule.about) {
    return '';
  }
  
  const about = `
    <p class="external">
      ${swordModule.about.replace(/\\pard/g, "").replace(/\\par/g, "<br>")}
    </p>`;

  return about;
};

module.exports.getModuleSize = function(swordModule) {
  return Math.round(swordModule.size / 1024) + " KB";
};

module.exports.moduleHasStrongs = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.hasStrongs;
  } else {
    return false;
  }
};

module.exports.moduleIsRTL = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.isRightToLeft;
  } else {
    return false;
  }
};

module.exports.bookHasHeaders = async function(moduleId, book, validate=true) {
  var hasHeaders = false;
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    hasHeaders = swordModule.hasHeadings;

    if (hasHeaders && validate) {
      const headerList = await ipcNsi.getBookHeaderList(moduleId, book);
      if (headerList.length == 0) {
        hasHeaders = false;
      }
    }
  }

  return hasHeaders;
};

module.exports.moduleHasApocryphalBooks = async function(moduleId) {
  return await ipcNsi.moduleHasApocryphalBooks(moduleId);
};

module.exports.getVersification = async function(moduleId) {
  if (moduleId == null) {
    return null;
  }

  if (moduleId in _moduleVersificationCache) {
    return _moduleVersificationCache[moduleId];
  }

  var versification = null;
  var psalm3Verses = [];
  var revelation12Verses = [];

  try {
    psalm3Verses = await ipcNsi.getChapterText(moduleId, 'Psa', 3);
  } catch (e) {
    console.log("TranslationController.getVersification: Could not retrieve chapter text for Psalm 3 of " + moduleId);
  }

  try {
    revelation12Verses = await ipcNsi.getChapterText(moduleId, 'Rev', 12);
  } catch (e) {
    console.log("TranslationController.getVersification: Could not retrieve chapter text for Revelation 12 of " + moduleId);
  }

  if (psalm3Verses.length == 8 || revelation12Verses.length == 17) { // ENGLISH versification
    versification = "ENGLISH";

  } else if (psalm3Verses.length == 9 || revelation12Verses.length == 18) { // HEBREW versification
    versification = "HEBREW";

  } else { // Unknown versification

    versification = "UNKNOWN";

    /*console.log("Unknown versification!");
    console.log("Psalm 3 has " + psalm3Verses.length + " verses.");
    console.log("Revelation 12 has " + revelation12Verses.length + " verses.");*/
  }

  _moduleVersificationCache[moduleId] = versification;
  return versification;
};

module.exports.getThreeLetterVersification = async function(moduleId) {
  var versification = (await this.getVersification(moduleId) == 'ENGLISH' ? 'eng' : 'heb');
  return versification;
};

module.exports.getModuleLanguage = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.language;
  } else {
    return false;
  }
};

module.exports.getModuleFullName = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.description;
  } else {
    return moduleId;
  }
};

module.exports.getModuleCopyright = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.shortCopyright || swordModule.copyright;
  } else {
    return false;
  }
};

module.exports.getModuleLicense = async function(moduleId) {
  const swordModule = await this.getSwordModule(moduleId);

  if (swordModule != null) {
    return swordModule.distributionLicense;
  } else {
    return false;
  }
};

module.exports.isPublicDomain = async function(moduleId) {
  const license = await this.getModuleLicense(moduleId);
  return !license || PUBLIC_LICENSES.includes(license);
};

module.exports.getLanguages = async function(moduleType='BIBLE', localModules=undefined) {
  if (localModules == undefined) {
    localModules = await ipcNsi.getAllLocalModules(moduleType);
  }

  if (localModules == null) {
    return [];
  }
  
  var languages = [];
  var languageCodes = [];

  for (let i = 0; i < localModules.length; i++) {
    const module = localModules[i];
    const languageName = i18nHelper.getLanguageName(module.language);

    if (!languageCodes.includes(module.language)) {
      languages.push({
        'languageName': languageName,
        'languageCode': module.language
      });
      languageCodes.push(module.language);
    }
  }

  return languages;
};

module.exports.isStrongsTranslationAvailable = async function() {
  var allTranslations = await ipcNsi.getAllLocalModules();

  if (allTranslations == null) {
    return false;
  }

  for (var dbTranslation of allTranslations) {
    if (dbTranslation.hasStrongs) {
      return true;
    }
  }

  return false;
};

module.exports.sortModules = function(a,b) {
  const isMobile = platformHelper.isMobile();
  let aDescription = isMobile ? a.name : a.description;
  aDescription = aDescription.toLowerCase();

  let bDescription = isMobile ? b.name : b.description;
  bDescription = bDescription.toLowerCase();

  if (aDescription < bDescription) {
    return -1;
  } else if (aDescription > bDescription) {
    return 1;
  } else {
    return 0;
  }
};

function urlify(text) {
  // replace urls in text with <a> html tag
  var aTagRegex = /(<a href.*?>.*?<\/a>)/g;
  var aSplits = text.split(aTagRegex);

  // regex extracted from https://www.codegrepper.com/code-examples/whatever/use+regex+to+get+urls+from+string
  // eslint-disable-next-line no-useless-escape
  var urlRegex = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])/igm;

  var cleanedText = "";

  for (let index = 0; index < aSplits.length; index++) {
    var split = aSplits[index];

    if (split.substring(0, 2) === '<a') {
      cleanedText += split;
    } else {
      cleanedText += split.replace(urlRegex, function (url) {
        if (url.startsWith('www')) {
          url = 'http://' + url;
        }

        return `<a href="${url}" target="_blank">${url}</a>`;
      });
    }
  }

  return cleanedText;
}