BasePage.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. const he = require('he');
  2. const WebWorker = require('../WebWorker');//singleton
  3. const XmlParser = require('../xml/XmlParser');
  4. const spaceChar = String.fromCodePoint(0x00B7);
  5. const ruAlphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
  6. const enAlphabet = 'abcdefghijklmnopqrstuvwxyz';
  7. const enruArr = (ruAlphabet + enAlphabet).split('');
  8. const enru = new Set(enruArr);
  9. class BasePage {
  10. constructor(config) {
  11. this.config = config;
  12. this.webWorker = new WebWorker(config);
  13. this.rootTag = 'feed';
  14. this.opdsRoot = config.opdsRoot;
  15. }
  16. makeEntry(entry = {}) {
  17. if (!entry.id)
  18. throw new Error('makeEntry: no id');
  19. if (!entry.title)
  20. throw new Error('makeEntry: no title');
  21. entry.title = he.escape(entry.title);
  22. const result = {
  23. updated: (new Date()).toISOString().substring(0, 19) + 'Z',
  24. };
  25. return Object.assign(result, entry);
  26. }
  27. myEntry() {
  28. return this.makeEntry({
  29. id: this.id,
  30. title: this.title,
  31. link: this.navLink({rel: 'subsection', href: `/${this.id}`}),
  32. });
  33. }
  34. makeLink(attrs) {
  35. return {'*ATTRS': attrs};
  36. }
  37. navLink(attrs) {
  38. return this.makeLink({
  39. href: this.opdsRoot + (attrs.href || ''),
  40. rel: attrs.rel || '',
  41. type: 'application/atom+xml; profile=opds-catalog; kind=navigation',
  42. });
  43. }
  44. baseLinks() {
  45. return [
  46. this.navLink({rel: 'start'}),
  47. this.navLink({rel: 'self', href: (this.id ? `/${this.id}` : '')}),
  48. ];
  49. }
  50. makeBody(content) {
  51. const base = this.makeEntry({id: this.id, title: this.title});
  52. base['*ATTRS'] = {
  53. 'xmlns': 'http://www.w3.org/2005/Atom',
  54. 'xmlns:dc': 'http://purl.org/dc/terms/',
  55. 'xmlns:opds': 'http://opds-spec.org/2010/catalog',
  56. };
  57. if (!content.link)
  58. base.link = this.baseLinks();
  59. const xml = new XmlParser();
  60. const xmlObject = {};
  61. xmlObject[this.rootTag] = Object.assign(base, content);
  62. xml.fromObject(xmlObject);
  63. return xml.toString({format: true});
  64. }
  65. async body() {
  66. throw new Error('Body not implemented');
  67. }
  68. // -- stuff -------------------------------------------
  69. async search(from, query) {
  70. const result = [];
  71. const queryRes = await this.webWorker.search(from, query);
  72. for (const row of queryRes.found) {
  73. const rec = {
  74. id: row.id,
  75. title: '=' + (row[from] || 'Без имени'),
  76. q: `=${encodeURIComponent(row[from])}`,
  77. };
  78. result.push(rec);
  79. }
  80. return result;
  81. }
  82. async opdsQuery(from, query) {
  83. const result = [];
  84. const queryRes = await this.webWorker.opdsQuery(from, query);
  85. let count = 0;
  86. for (const row of queryRes.found)
  87. count += row.count;
  88. if (count <= query.limit)
  89. return await this.search(from, query);
  90. const names = new Set();
  91. const others = [];
  92. for (const row of queryRes.found) {
  93. const name = row.name.toUpperCase();
  94. if (!names.has(name)) {
  95. const rec = {
  96. id: row.id,
  97. title: name.replace(/ /g, spaceChar),
  98. q: encodeURIComponent(row.name.toLowerCase()),
  99. count: row.count,
  100. };
  101. if (query.depth > 1 || enru.has(row.name[0].toLowerCase())) {
  102. result.push(rec);
  103. } else {
  104. others.push(rec);
  105. }
  106. names.add(name);
  107. }
  108. }
  109. if (!query.others && query.depth == 1)
  110. result.push({id: 'other', title: 'Все остальные', q: '___others'});
  111. return (!query.others ? result : others);
  112. }
  113. }
  114. module.exports = BasePage;