invites_view.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. const { form, button, div, h2, p, section, ul, li, a, br, hr, input, span } = require("../server/node_modules/hyperaxe");
  2. const path = require("path");
  3. const fs = require('fs');
  4. const { template, i18n } = require('./main_views');
  5. const homedir = require('os').homedir();
  6. const gossipPath = path.join(homedir, ".ssb", "gossip.json");
  7. const unfollowedPath = path.join(homedir, ".ssb", "gossip_unfollowed.json");
  8. const encodePubLink = (key) => {
  9. let core = String(key).replace(/^@/, '').replace(/\.ed25519$/, '').replace(/-/g, '+').replace(/_/g, '/');
  10. if (!core.endsWith('=')) core += '=';
  11. return `/author/${encodeURIComponent('@' + core)}.ed25519`;
  12. };
  13. const invitesView = ({ invitesEnabled }) => {
  14. let pubs = [];
  15. let pubsValue = "false";
  16. let unfollowed = [];
  17. try {
  18. pubs = fs.readFileSync(gossipPath, "utf8");
  19. } catch {
  20. pubs = '[]';
  21. }
  22. try {
  23. pubs = JSON.parse(pubs);
  24. pubsValue = Array.isArray(pubs) && pubs.length > 0 ? "true" : "false";
  25. } catch {
  26. pubsValue = "false";
  27. pubs = [];
  28. }
  29. try {
  30. unfollowed = JSON.parse(fs.readFileSync(unfollowedPath, "utf8") || "[]");
  31. } catch {
  32. unfollowed = [];
  33. }
  34. const filteredPubs = pubsValue === "true"
  35. ? pubs.filter(pubItem => !unfollowed.find(u => u.key === pubItem.key))
  36. : [];
  37. const hasError = (pubItem) => pubItem && (pubItem.error || (typeof pubItem.failure === 'number' && pubItem.failure > 0));
  38. const unreachableLabel = i18n.currentlyUnreachable || i18n.currentlyUnrecheable || 'ERROR!';
  39. const pubItems = filteredPubs.filter(pubItem => !hasError(pubItem)).map(pubItem =>
  40. li(
  41. div(
  42. { class: 'pub-item' },
  43. h2('PUB: ', pubItem.host),
  44. h2(`${i18n.inhabitants}: ${pubItem.announcers || 0}`),
  45. a({ href: encodePubLink(pubItem.key), class: 'user-link' }, pubItem.key),
  46. form(
  47. { action: '/settings/invite/unfollow', method: 'post' },
  48. input({ type: 'hidden', name: 'key', value: pubItem.key }),
  49. button({ type: 'submit' }, i18n.invitesUnfollow)
  50. ),
  51. )
  52. )
  53. );
  54. const unfollowedItems = unfollowed.length
  55. ? unfollowed.map(pubItem =>
  56. li(
  57. div(
  58. { class: 'pub-item' },
  59. h2('PUB: ', pubItem.host),
  60. h2(`${i18n.inhabitants}: ${pubItem.announcers || 0}`),
  61. a({ href: encodePubLink(pubItem.key), class: 'user-link' }, pubItem.key),
  62. form(
  63. { action: '/settings/invite/follow', method: 'post' },
  64. input({ type: 'hidden', name: 'key', value: pubItem.key }),
  65. input({ type: 'hidden', name: 'host', value: pubItem.host || '' }),
  66. input({ type: 'hidden', name: 'port', value: String(pubItem.port || 8008) }),
  67. button({ type: 'submit', disabled: hasError(pubItem) }, i18n.invitesFollow)
  68. ),
  69. )
  70. )
  71. )
  72. : [];
  73. const unreachableItems = pubs.filter(hasError).map(pubItem =>
  74. li(
  75. div(
  76. { class: 'pub-item' },
  77. h2('PUB: ', pubItem.host),
  78. h2(`${i18n.inhabitants}: ${pubItem.announcers || 0}`),
  79. a({ href: encodePubLink(pubItem.key), class: 'user-link' }, pubItem.key),
  80. div(
  81. { class: 'error-box' },
  82. p({ class: 'error-title' }, i18n.errorDetails),
  83. p({ class: 'error-pre' }, String(pubItem.error || i18n.genericError))
  84. ),
  85. )
  86. )
  87. );
  88. const title = i18n.invites;
  89. const description = i18n.invitesDescription;
  90. return template(
  91. title,
  92. section(
  93. div({ class: 'tags-header' },
  94. h2(title),
  95. p(description)
  96. )
  97. ),
  98. section(
  99. div({ class: 'invites-tribes' },
  100. h2(i18n.invitesTribesTitle),
  101. form(
  102. { action: '/tribes/join-code', method: 'post' },
  103. input({ name: 'inviteCode', type: 'text', placeholder: i18n.invitesTribeInviteCodePlaceholder, autofocus: true, required: true }),
  104. br(),
  105. button({ type: 'submit' }, i18n.invitesTribeJoinButton)
  106. )
  107. )
  108. ),
  109. section(
  110. div({ class: 'pubs-section' },
  111. h2(i18n.invitesPubsTitle),
  112. form(
  113. { action: '/settings/invite/accept', method: 'post' },
  114. input({ name: 'invite', type: 'text', placeholder: i18n.invitesPubInviteCodePlaceholder, autofocus: true, required: true }),
  115. br(),
  116. button({ type: 'submit' }, i18n.invitesAcceptInvite)
  117. ),
  118. br,
  119. hr(),
  120. h2(`${i18n.invitesAcceptedInvites} (${pubItems.length})`),
  121. pubItems.length ? ul(pubItems) : p(i18n.invitesNoFederatedPubs),
  122. hr(),
  123. h2(`${i18n.invitesUnfollowedInvites} (${unfollowedItems.length})`),
  124. unfollowedItems.length ? ul(unfollowedItems) : p(i18n.invitesNoUnfollowed),
  125. hr(),
  126. h2(`${i18n.invitesUnreachablePubs} (${unreachableItems.length})`),
  127. unreachableItems.length ? ul(unreachableItems) : p(i18n.invitesNoUnreachablePubs)
  128. )
  129. )
  130. );
  131. };
  132. exports.invitesView = invitesView;