Source

frontend/helpers/sword_module_helper.js

  1. /* This file is part of Ezra Bible App.
  2. Copyright (C) 2019 - 2023 Ezra Bible App Development Team <contact@ezrabibleapp.net>
  3. Ezra Bible App is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. Ezra Bible App is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with Ezra Bible App. See the file LICENSE.
  13. If not, see <http://www.gnu.org/licenses/>. */
  14. /**
  15. * This module offers sword module related functionality
  16. * @module sword_module_helper
  17. * @category Helper
  18. */
  19. const i18nHelper = require('./i18n_helper.js');
  20. const PUBLIC_LICENSES = ['Public Domain', 'General public license for distribution for any purpose'];
  21. var _moduleVersificationCache = {};
  22. var _cachedModule;
  23. module.exports.getSwordModule = async function(moduleId, isRemote=false) {
  24. if (moduleId == null) {
  25. return null;
  26. }
  27. if (!_cachedModule || _cachedModule.name !== moduleId || (_cachedModule.name === moduleId && _cachedModule.remote !== isRemote)) {
  28. let swordModule = null;
  29. try {
  30. if (isRemote) {
  31. swordModule = await ipcNsi.getRepoModule(moduleId);
  32. } else {
  33. swordModule = await ipcNsi.getLocalModule(moduleId);
  34. }
  35. swordModule.remote = isRemote;
  36. _cachedModule = swordModule;
  37. } catch (e) {
  38. console.log(`Could not get ${isRemote ? 'remote' : 'local'} sword module for ${moduleId}`);
  39. }
  40. }
  41. return _cachedModule;
  42. };
  43. module.exports.resetModuleCache = function() {
  44. _cachedModule = null;
  45. };
  46. module.exports.getModuleDescription = async function(moduleId, isRemote=false) {
  47. const swordModule = await this.getSwordModule(moduleId, isRemote);
  48. if (!swordModule) {
  49. return "No info available!";
  50. }
  51. var moduleInfo = "";
  52. if (isRemote) {
  53. moduleInfo += "<b>" + swordModule.description + "</b><br><br>";
  54. }
  55. moduleInfo += await this.getModuleAbout(swordModule);
  56. moduleInfo = urlify(moduleInfo);
  57. return moduleInfo;
  58. };
  59. module.exports.getModuleInfo = async function(moduleId, isRemote=false, includeModuleDescription=true) {
  60. const swordModule = await this.getSwordModule(moduleId, isRemote);
  61. if (!swordModule) {
  62. return "No info available!";
  63. }
  64. var moduleInfo = "No info available!";
  65. try {
  66. moduleInfo = "";
  67. if (includeModuleDescription) {
  68. moduleInfo += await this.getModuleDescription(moduleId, isRemote);
  69. }
  70. var yes = i18n.t("general.yes");
  71. var no = i18n.t("general.no");
  72. if (includeModuleDescription) {
  73. moduleInfo += `<p style='margin-top: 1em; padding-top: 1em; border-top: 1px solid grey; font-weight: bold'>${i18n.t("general.sword-module-info")}</p>`;
  74. }
  75. let lastUpdate = swordModule.lastUpdate;
  76. if (lastUpdate != "") {
  77. try {
  78. lastUpdate = i18nHelper.getLocalizedDate(lastUpdate);
  79. } catch (e) {
  80. console.log(e);
  81. }
  82. }
  83. moduleInfo += "<table>";
  84. moduleInfo += "<tr><td style='width: 11em;'>" + i18n.t("general.module-name") + ":</td><td>" + swordModule.name + "</td></tr>";
  85. moduleInfo += "<tr><td>" + i18n.t("general.module-version") + ":</td><td>" + swordModule.version + "</td></tr>";
  86. moduleInfo += "<tr><td>" + i18n.t("general.module-last-update") + ":</td><td>" + lastUpdate + "</td></tr>";
  87. moduleInfo += "<tr><td>" + i18n.t("general.module-language") + ":</td><td>" + i18nHelper.getLanguageName(swordModule.language) + "</td></tr>";
  88. moduleInfo += "<tr><td>" + i18n.t("general.module-license") + ":</td><td>" + swordModule.distributionLicense + "</td></tr>";
  89. if (swordModule.type == 'Biblical Texts') {
  90. moduleInfo += "<tr><td>" + i18n.t("general.module-strongs") + ":</td><td>" + (swordModule.hasStrongs ? yes : no) + "</td></tr>";
  91. moduleInfo += "<tr><td>" + i18n.t("general.module-headings") + ":</td><td>" + (swordModule.hasHeadings ? yes : no) + "</td></tr>";
  92. moduleInfo += "<tr><td>" + i18n.t("general.module-footnotes") + ":</td><td>" + (swordModule.hasFootnotes ? yes : no) + "</td></tr>";
  93. moduleInfo += "<tr><td>" + i18n.t("general.module-xrefs") + ":</td><td>" + (swordModule.hasCrossReferences ? yes : no) + "</td></tr>";
  94. moduleInfo += "<tr><td>" + i18n.t("general.module-redletter") + ":</td><td>" + (swordModule.hasRedLetterWords ? yes : no) + "</td></tr>";
  95. }
  96. moduleInfo += "<tr><td>" + i18n.t("general.module-size") + ":</td><td>" + this.getModuleSize(swordModule) + "</td></tr>";
  97. if (swordModule.repository != '') {
  98. moduleInfo += "<tr><td>" + i18n.t("module-assistant.repository_singular") + ":</td><td>" + swordModule.repository + "</td></tr>";
  99. }
  100. if (!isRemote) {
  101. moduleInfo += "<tr><td>" + i18n.t("general.module-location") + ":</td><td>" + swordModule.location + "</td></tr>";
  102. }
  103. moduleInfo += "</table>";
  104. if (isRemote && swordModule.locked && swordModule.unlockInfo != "") {
  105. moduleInfo += "<p style='margin-top: 1em; padding-top: 1em; border-top: 1px solid grey; font-weight: bold'>" + i18n.t("general.sword-unlock-info") + "</p>";
  106. moduleInfo += "<p class='external'>" + swordModule.unlockInfo + "</p>";
  107. }
  108. } catch (ex) {
  109. console.error("Got exception while trying to get module info: " + ex);
  110. }
  111. return moduleInfo;
  112. };
  113. module.exports.getModuleAbout = async function(swordModuleOrId) {
  114. var swordModule;
  115. if (typeof swordModuleOrId === 'string') {
  116. swordModule = await this.getSwordModule(swordModuleOrId);
  117. } else {
  118. swordModule = swordModuleOrId;
  119. }
  120. if (!swordModule || !swordModule.about) {
  121. return '';
  122. }
  123. const about = `
  124. <p class="external">
  125. ${swordModule.about.replace(/\\pard/g, "").replace(/\\par/g, "<br>")}
  126. </p>`;
  127. return about;
  128. };
  129. module.exports.getModuleSize = function(swordModule) {
  130. return Math.round(swordModule.size / 1024) + " KB";
  131. };
  132. module.exports.moduleHasStrongs = async function(moduleId) {
  133. const swordModule = await this.getSwordModule(moduleId);
  134. if (swordModule != null) {
  135. return swordModule.hasStrongs;
  136. } else {
  137. return false;
  138. }
  139. };
  140. module.exports.moduleIsRTL = async function(moduleId) {
  141. const swordModule = await this.getSwordModule(moduleId);
  142. if (swordModule != null) {
  143. return swordModule.isRightToLeft;
  144. } else {
  145. return false;
  146. }
  147. };
  148. module.exports.bookHasHeaders = async function(moduleId, book, validate=true) {
  149. var hasHeaders = false;
  150. const swordModule = await this.getSwordModule(moduleId);
  151. if (swordModule != null) {
  152. hasHeaders = swordModule.hasHeadings;
  153. if (hasHeaders && validate) {
  154. const headerList = await ipcNsi.getBookHeaderList(moduleId, book);
  155. if (headerList.length == 0) {
  156. hasHeaders = false;
  157. }
  158. }
  159. }
  160. return hasHeaders;
  161. };
  162. module.exports.moduleHasApocryphalBooks = async function(moduleId) {
  163. return await ipcNsi.moduleHasApocryphalBooks(moduleId);
  164. };
  165. module.exports.getVersification = async function(moduleId) {
  166. if (moduleId == null) {
  167. return null;
  168. }
  169. if (moduleId in _moduleVersificationCache) {
  170. return _moduleVersificationCache[moduleId];
  171. }
  172. var versification = null;
  173. var psalm3Verses = [];
  174. var revelation12Verses = [];
  175. try {
  176. psalm3Verses = await ipcNsi.getChapterText(moduleId, 'Psa', 3);
  177. } catch (e) {
  178. console.log("TranslationController.getVersification: Could not retrieve chapter text for Psalm 3 of " + moduleId);
  179. }
  180. try {
  181. revelation12Verses = await ipcNsi.getChapterText(moduleId, 'Rev', 12);
  182. } catch (e) {
  183. console.log("TranslationController.getVersification: Could not retrieve chapter text for Revelation 12 of " + moduleId);
  184. }
  185. if (psalm3Verses.length == 8 || revelation12Verses.length == 17) { // ENGLISH versification
  186. versification = "ENGLISH";
  187. } else if (psalm3Verses.length == 9 || revelation12Verses.length == 18) { // HEBREW versification
  188. versification = "HEBREW";
  189. } else { // Unknown versification
  190. versification = "UNKNOWN";
  191. /*console.log("Unknown versification!");
  192. console.log("Psalm 3 has " + psalm3Verses.length + " verses.");
  193. console.log("Revelation 12 has " + revelation12Verses.length + " verses.");*/
  194. }
  195. _moduleVersificationCache[moduleId] = versification;
  196. return versification;
  197. };
  198. module.exports.getThreeLetterVersification = async function(moduleId) {
  199. var versification = (await this.getVersification(moduleId) == 'ENGLISH' ? 'eng' : 'heb');
  200. return versification;
  201. };
  202. module.exports.getModuleLanguage = async function(moduleId) {
  203. const swordModule = await this.getSwordModule(moduleId);
  204. if (swordModule != null) {
  205. return swordModule.language;
  206. } else {
  207. return false;
  208. }
  209. };
  210. module.exports.getModuleFullName = async function(moduleId) {
  211. const swordModule = await this.getSwordModule(moduleId);
  212. if (swordModule != null) {
  213. return swordModule.description;
  214. } else {
  215. return moduleId;
  216. }
  217. };
  218. module.exports.getModuleCopyright = async function(moduleId) {
  219. const swordModule = await this.getSwordModule(moduleId);
  220. if (swordModule != null) {
  221. return swordModule.shortCopyright || swordModule.copyright;
  222. } else {
  223. return false;
  224. }
  225. };
  226. module.exports.getModuleLicense = async function(moduleId) {
  227. const swordModule = await this.getSwordModule(moduleId);
  228. if (swordModule != null) {
  229. return swordModule.distributionLicense;
  230. } else {
  231. return false;
  232. }
  233. };
  234. module.exports.isPublicDomain = async function(moduleId) {
  235. const license = await this.getModuleLicense(moduleId);
  236. return !license || PUBLIC_LICENSES.includes(license);
  237. };
  238. module.exports.getLanguages = async function(moduleType='BIBLE', localModules=undefined) {
  239. if (localModules == undefined) {
  240. localModules = await ipcNsi.getAllLocalModules(moduleType);
  241. }
  242. if (localModules == null) {
  243. return [];
  244. }
  245. var languages = [];
  246. var languageCodes = [];
  247. for (let i = 0; i < localModules.length; i++) {
  248. const module = localModules[i];
  249. const languageName = i18nHelper.getLanguageName(module.language);
  250. if (!languageCodes.includes(module.language)) {
  251. languages.push({
  252. 'languageName': languageName,
  253. 'languageCode': module.language
  254. });
  255. languageCodes.push(module.language);
  256. }
  257. }
  258. return languages;
  259. };
  260. module.exports.isStrongsTranslationAvailable = async function() {
  261. var allTranslations = await ipcNsi.getAllLocalModules();
  262. if (allTranslations == null) {
  263. return false;
  264. }
  265. for (var dbTranslation of allTranslations) {
  266. if (dbTranslation.hasStrongs) {
  267. return true;
  268. }
  269. }
  270. return false;
  271. };
  272. module.exports.sortModules = function(a,b) {
  273. const isMobile = platformHelper.isMobile();
  274. let aDescription = isMobile ? a.name : a.description;
  275. aDescription = aDescription.toLowerCase();
  276. let bDescription = isMobile ? b.name : b.description;
  277. bDescription = bDescription.toLowerCase();
  278. if (aDescription < bDescription) {
  279. return -1;
  280. } else if (aDescription > bDescription) {
  281. return 1;
  282. } else {
  283. return 0;
  284. }
  285. };
  286. function urlify(text) {
  287. // replace urls in text with <a> html tag
  288. var aTagRegex = /(<a href.*?>.*?<\/a>)/g;
  289. var aSplits = text.split(aTagRegex);
  290. // regex extracted from https://www.codegrepper.com/code-examples/whatever/use+regex+to+get+urls+from+string
  291. // eslint-disable-next-line no-useless-escape
  292. var urlRegex = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])/igm;
  293. var cleanedText = "";
  294. for (let index = 0; index < aSplits.length; index++) {
  295. var split = aSplits[index];
  296. if (split.substring(0, 2) === '<a') {
  297. cleanedText += split;
  298. } else {
  299. cleanedText += split.replace(urlRegex, function (url) {
  300. if (url.startsWith('www')) {
  301. url = 'http://' + url;
  302. }
  303. return `<a href="${url}" target="_blank">${url}</a>`;
  304. });
  305. }
  306. }
  307. return cleanedText;
  308. }