rails_den 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +652 -0
  3. data/README.md +74 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascript/rails_den/attachments.js +733 -0
  6. data/app/assets/javascript/rails_den/bookmarks.js +58 -0
  7. data/app/assets/javascript/rails_den/cable.js +18 -0
  8. data/app/assets/javascript/rails_den/chat.js +3008 -0
  9. data/app/assets/javascript/rails_den/chat_3d_management.js +1230 -0
  10. data/app/assets/javascript/rails_den/composer.js +1785 -0
  11. data/app/assets/javascript/rails_den/content.js +1337 -0
  12. data/app/assets/javascript/rails_den/emoji_catalog.js +136 -0
  13. data/app/assets/javascript/rails_den/licenses/react-LICENSE.txt +21 -0
  14. data/app/assets/javascript/rails_den/licenses/react-dom-LICENSE.txt +21 -0
  15. data/app/assets/javascript/rails_den/licenses/scheduler-LICENSE.txt +21 -0
  16. data/app/assets/javascript/rails_den/licenses/three-LICENSE.txt +21 -0
  17. data/app/assets/javascript/rails_den/members.js +187 -0
  18. data/app/assets/javascript/rails_den/notifications.js +211 -0
  19. data/app/assets/javascript/rails_den/presence.js +757 -0
  20. data/app/assets/javascript/rails_den/private_messages.js +92 -0
  21. data/app/assets/javascript/rails_den/react_runtime.js +43 -0
  22. data/app/assets/javascript/rails_den/reactions.js +139 -0
  23. data/app/assets/javascript/rails_den/spatial.js +4338 -0
  24. data/app/assets/stylesheets/rails_den/advertising.css +91 -0
  25. data/app/assets/stylesheets/rails_den/application.css +1724 -0
  26. data/app/assets/stylesheets/rails_den/attachments.css +255 -0
  27. data/app/assets/stylesheets/rails_den/board_presence.css +38 -0
  28. data/app/assets/stylesheets/rails_den/bookmarks.css +36 -0
  29. data/app/assets/stylesheets/rails_den/chat.css +1555 -0
  30. data/app/assets/stylesheets/rails_den/chat_3d.css +1283 -0
  31. data/app/assets/stylesheets/rails_den/chat_3d_management.css +294 -0
  32. data/app/assets/stylesheets/rails_den/chat_reactions.css +79 -0
  33. data/app/assets/stylesheets/rails_den/community_presence.css +51 -0
  34. data/app/assets/stylesheets/rails_den/composer.css +358 -0
  35. data/app/assets/stylesheets/rails_den/emoji_catalog.css +33 -0
  36. data/app/assets/stylesheets/rails_den/member_profiles.css +662 -0
  37. data/app/assets/stylesheets/rails_den/moderation.css +561 -0
  38. data/app/assets/stylesheets/rails_den/notifications.css +222 -0
  39. data/app/assets/stylesheets/rails_den/onebox.css +126 -0
  40. data/app/assets/stylesheets/rails_den/post_links.css +37 -0
  41. data/app/assets/stylesheets/rails_den/post_presence.css +47 -0
  42. data/app/assets/stylesheets/rails_den/presence.css +96 -0
  43. data/app/assets/stylesheets/rails_den/presence_directory.css +217 -0
  44. data/app/assets/stylesheets/rails_den/presence_live.css +15 -0
  45. data/app/assets/stylesheets/rails_den/private_messages.css +41 -0
  46. data/app/assets/stylesheets/rails_den/reactions.css +53 -0
  47. data/app/assets/stylesheets/rails_den/search.css +100 -0
  48. data/app/assets/stylesheets/rails_den/tags.css +136 -0
  49. data/app/channels/rails_den/chat_spatial_channel.rb +203 -0
  50. data/app/channels/rails_den/chat_spatial_occupancy_channel.rb +69 -0
  51. data/app/channels/rails_den/chat_stream_channel.rb +108 -0
  52. data/app/channels/rails_den/notifications_channel.rb +30 -0
  53. data/app/channels/rails_den/presence_channel.rb +205 -0
  54. data/app/controllers/admin/rails_den/administrators_controller.rb +26 -0
  55. data/app/controllers/admin/rails_den/advertising_management_controller.rb +337 -0
  56. data/app/controllers/admin/rails_den/application_controller.rb +116 -0
  57. data/app/controllers/admin/rails_den/badges_controller.rb +106 -0
  58. data/app/controllers/admin/rails_den/board_access_controller.rb +143 -0
  59. data/app/controllers/admin/rails_den/boards_controller.rb +89 -0
  60. data/app/controllers/admin/rails_den/categories_controller.rb +89 -0
  61. data/app/controllers/admin/rails_den/member_badge_awards_controller.rb +92 -0
  62. data/app/controllers/admin/rails_den/members_controller.rb +495 -0
  63. data/app/controllers/admin/rails_den/plugins_controller.rb +25 -0
  64. data/app/controllers/admin/rails_den/profile_fields_controller.rb +104 -0
  65. data/app/controllers/admin/rails_den/reaction_types_controller.rb +127 -0
  66. data/app/controllers/admin/rails_den/report_reasons_controller.rb +15 -0
  67. data/app/controllers/admin/rails_den/roles_controller.rb +190 -0
  68. data/app/controllers/admin/rails_den/tags_controller.rb +11 -0
  69. data/app/controllers/admin/rails_den/themes_controller.rb +60 -0
  70. data/app/controllers/admin/rails_den/trust_levels_controller.rb +97 -0
  71. data/app/controllers/rails_den/advertising_controller.rb +14 -0
  72. data/app/controllers/rails_den/application_controller.rb +218 -0
  73. data/app/controllers/rails_den/attachments_controller.rb +128 -0
  74. data/app/controllers/rails_den/board_follows_controller.rb +119 -0
  75. data/app/controllers/rails_den/board_read_states_controller.rb +36 -0
  76. data/app/controllers/rails_den/boards_controller.rb +103 -0
  77. data/app/controllers/rails_den/bookmarks_controller.rb +91 -0
  78. data/app/controllers/rails_den/chat_channel_memberships_controller.rb +193 -0
  79. data/app/controllers/rails_den/chat_channel_read_states_controller.rb +46 -0
  80. data/app/controllers/rails_den/chat_channels_controller.rb +556 -0
  81. data/app/controllers/rails_den/chat_message_pins_controller.rb +78 -0
  82. data/app/controllers/rails_den/chat_messages_controller.rb +166 -0
  83. data/app/controllers/rails_den/chat_reaction_types_controller.rb +24 -0
  84. data/app/controllers/rails_den/community_read_states_controller.rb +12 -0
  85. data/app/controllers/rails_den/home_controller.rb +35 -0
  86. data/app/controllers/rails_den/member_profiles_controller.rb +286 -0
  87. data/app/controllers/rails_den/member_reports_controller.rb +22 -0
  88. data/app/controllers/rails_den/members_controller.rb +42 -0
  89. data/app/controllers/rails_den/mention_candidates_controller.rb +14 -0
  90. data/app/controllers/rails_den/moderation/audit_events_controller.rb +14 -0
  91. data/app/controllers/rails_den/moderation/member_histories_controller.rb +37 -0
  92. data/app/controllers/rails_den/moderation/member_notes_controller.rb +26 -0
  93. data/app/controllers/rails_den/moderation/report_notes_controller.rb +27 -0
  94. data/app/controllers/rails_den/moderation/reports_controller.rb +97 -0
  95. data/app/controllers/rails_den/moderation/restrictions_controller.rb +71 -0
  96. data/app/controllers/rails_den/moderation/topics_controller.rb +61 -0
  97. data/app/controllers/rails_den/moderation/warnings_controller.rb +50 -0
  98. data/app/controllers/rails_den/notification_preferences_controller.rb +116 -0
  99. data/app/controllers/rails_den/notifications_controller.rb +67 -0
  100. data/app/controllers/rails_den/post_bookmarks_controller.rb +29 -0
  101. data/app/controllers/rails_den/post_drafts_controller.rb +125 -0
  102. data/app/controllers/rails_den/post_reports_controller.rb +31 -0
  103. data/app/controllers/rails_den/posts_controller.rb +318 -0
  104. data/app/controllers/rails_den/presence_controller.rb +12 -0
  105. data/app/controllers/rails_den/presence_snapshots_controller.rb +83 -0
  106. data/app/controllers/rails_den/previews_controller.rb +10 -0
  107. data/app/controllers/rails_den/private_conversations_controller.rb +120 -0
  108. data/app/controllers/rails_den/private_messages_controller.rb +45 -0
  109. data/app/controllers/rails_den/reactions_controller.rb +124 -0
  110. data/app/controllers/rails_den/reports_controller.rb +63 -0
  111. data/app/controllers/rails_den/search_controller.rb +64 -0
  112. data/app/controllers/rails_den/tags_controller.rb +70 -0
  113. data/app/controllers/rails_den/topic_bookmarks_controller.rb +35 -0
  114. data/app/controllers/rails_den/topic_follows_controller.rb +117 -0
  115. data/app/controllers/rails_den/topic_read_positions_controller.rb +40 -0
  116. data/app/controllers/rails_den/topic_reports_controller.rb +18 -0
  117. data/app/controllers/rails_den/topics_controller.rb +583 -0
  118. data/app/dashboards/rails_den/administrator_dashboard.rb +51 -0
  119. data/app/dashboards/rails_den/board_dashboard.rb +57 -0
  120. data/app/dashboards/rails_den/category_dashboard.rb +50 -0
  121. data/app/dashboards/rails_den/profile_field_dashboard.rb +53 -0
  122. data/app/dashboards/rails_den/reaction_type_dashboard.rb +26 -0
  123. data/app/dashboards/rails_den/report_reason_dashboard.rb +57 -0
  124. data/app/dashboards/rails_den/tag_dashboard.rb +59 -0
  125. data/app/helpers/rails_den/application_helper.rb +39 -0
  126. data/app/helpers/rails_den/boards_helper.rb +4 -0
  127. data/app/helpers/rails_den/moderation/reports_helper.rb +42 -0
  128. data/app/helpers/rails_den/notifications_helper.rb +4 -0
  129. data/app/helpers/rails_den/posts_helper.rb +4 -0
  130. data/app/helpers/rails_den/topics_helper.rb +4 -0
  131. data/app/jobs/rails_den/application_job.rb +4 -0
  132. data/app/jobs/rails_den/expire_user_restriction_job.rb +9 -0
  133. data/app/jobs/rails_den/member_profile_stats_refresh_job.rb +48 -0
  134. data/app/jobs/rails_den/reconcile_user_restrictions_job.rb +9 -0
  135. data/app/jobs/rails_den/resolve_onebox_entry_job.rb +74 -0
  136. data/app/mailers/rails_den/application_mailer.rb +6 -0
  137. data/app/models/rails_den/administrator.rb +18 -0
  138. data/app/models/rails_den/advertising_configuration.rb +14 -0
  139. data/app/models/rails_den/application_record.rb +5 -0
  140. data/app/models/rails_den/badge.rb +142 -0
  141. data/app/models/rails_den/badge_award.rb +87 -0
  142. data/app/models/rails_den/board.rb +129 -0
  143. data/app/models/rails_den/board_follow.rb +18 -0
  144. data/app/models/rails_den/board_read_state.rb +60 -0
  145. data/app/models/rails_den/board_role_assignment.rb +45 -0
  146. data/app/models/rails_den/board_role_permission.rb +31 -0
  147. data/app/models/rails_den/board_role_requirement.rb +16 -0
  148. data/app/models/rails_den/board_sponsorship.rb +21 -0
  149. data/app/models/rails_den/bookmark.rb +31 -0
  150. data/app/models/rails_den/category.rb +81 -0
  151. data/app/models/rails_den/category_role_requirement.rb +16 -0
  152. data/app/models/rails_den/category_sponsorship.rb +21 -0
  153. data/app/models/rails_den/chat_channel.rb +44 -0
  154. data/app/models/rails_den/chat_channel_membership.rb +21 -0
  155. data/app/models/rails_den/chat_channel_read_state.rb +100 -0
  156. data/app/models/rails_den/chat_message.rb +37 -0
  157. data/app/models/rails_den/community_read_state.rb +52 -0
  158. data/app/models/rails_den/community_sponsorship.rb +15 -0
  159. data/app/models/rails_den/member_profile.rb +113 -0
  160. data/app/models/rails_den/mention.rb +20 -0
  161. data/app/models/rails_den/moderation_event.rb +16 -0
  162. data/app/models/rails_den/moderation_note.rb +11 -0
  163. data/app/models/rails_den/notification.rb +33 -0
  164. data/app/models/rails_den/notification_event.rb +40 -0
  165. data/app/models/rails_den/notification_mute.rb +9 -0
  166. data/app/models/rails_den/onebox_entry.rb +214 -0
  167. data/app/models/rails_den/post.rb +396 -0
  168. data/app/models/rails_den/post_draft.rb +63 -0
  169. data/app/models/rails_den/post_revision.rb +10 -0
  170. data/app/models/rails_den/private_conversation.rb +25 -0
  171. data/app/models/rails_den/private_conversation_participant.rb +16 -0
  172. data/app/models/rails_den/private_message.rb +35 -0
  173. data/app/models/rails_den/private_message_recipient.rb +44 -0
  174. data/app/models/rails_den/profile_field.rb +150 -0
  175. data/app/models/rails_den/profile_field_value.rb +136 -0
  176. data/app/models/rails_den/reaction.rb +56 -0
  177. data/app/models/rails_den/reaction_type.rb +23 -0
  178. data/app/models/rails_den/reader_visit.rb +61 -0
  179. data/app/models/rails_den/report.rb +127 -0
  180. data/app/models/rails_den/report_reason.rb +52 -0
  181. data/app/models/rails_den/reputation_event.rb +92 -0
  182. data/app/models/rails_den/role.rb +87 -0
  183. data/app/models/rails_den/role_assignment.rb +18 -0
  184. data/app/models/rails_den/role_permission.rb +41 -0
  185. data/app/models/rails_den/sluggable.rb +102 -0
  186. data/app/models/rails_den/sponsor.rb +20 -0
  187. data/app/models/rails_den/sponsorship.rb +87 -0
  188. data/app/models/rails_den/tag.rb +61 -0
  189. data/app/models/rails_den/theme_selection.rb +4 -0
  190. data/app/models/rails_den/topic.rb +307 -0
  191. data/app/models/rails_den/topic_follow.rb +18 -0
  192. data/app/models/rails_den/topic_read_state.rb +76 -0
  193. data/app/models/rails_den/topic_role_requirement.rb +16 -0
  194. data/app/models/rails_den/topic_tag.rb +16 -0
  195. data/app/models/rails_den/trust_level.rb +62 -0
  196. data/app/models/rails_den/trust_level_achievement.rb +21 -0
  197. data/app/models/rails_den/user_restriction.rb +54 -0
  198. data/app/models/rails_den/user_warning.rb +10 -0
  199. data/app/presenters/rails_den/member_profile_presenter.rb +285 -0
  200. data/app/queries/rails_den/topic_new_post_counts.rb +153 -0
  201. data/app/queries/rails_den/topic_unread_counts.rb +137 -0
  202. data/app/services/rails_den/board_following.rb +91 -0
  203. data/app/services/rails_den/bookmark_list.rb +109 -0
  204. data/app/services/rails_den/bookmark_state.rb +17 -0
  205. data/app/services/rails_den/built_in_role_provisioner.rb +98 -0
  206. data/app/services/rails_den/chat/broadcaster.rb +81 -0
  207. data/app/services/rails_den/chat/message_payload.rb +26 -0
  208. data/app/services/rails_den/chat/presence_snapshot.rb +269 -0
  209. data/app/services/rails_den/chat/spatial_broadcaster.rb +135 -0
  210. data/app/services/rails_den/chat/spatial_occupancy_snapshot.rb +35 -0
  211. data/app/services/rails_den/chat/spatial_registry.rb +682 -0
  212. data/app/services/rails_den/chat/spatial_room.rb +81 -0
  213. data/app/services/rails_den/chat/spatial_snapshot.rb +261 -0
  214. data/app/services/rails_den/chat/spatial_update.rb +243 -0
  215. data/app/services/rails_den/chat/subscription_token.rb +28 -0
  216. data/app/services/rails_den/chat/unread_counts.rb +79 -0
  217. data/app/services/rails_den/chat/whisper_delivery.rb +287 -0
  218. data/app/services/rails_den/chat_channel_access.rb +97 -0
  219. data/app/services/rails_den/default_report_reason_provisioner.rb +30 -0
  220. data/app/services/rails_den/emoji_catalog.rb +101 -0
  221. data/app/services/rails_den/gamification/activity_recorder.rb +69 -0
  222. data/app/services/rails_den/gamification/badge_evaluator.rb +55 -0
  223. data/app/services/rails_den/gamification/current_trust_level.rb +21 -0
  224. data/app/services/rails_den/gamification/metrics.rb +93 -0
  225. data/app/services/rails_den/gamification/reputation_ledger.rb +261 -0
  226. data/app/services/rails_den/gamification/trust_level_evaluator.rb +78 -0
  227. data/app/services/rails_den/global_moderation_policy.rb +32 -0
  228. data/app/services/rails_den/member_profile_bio_renderer.rb +28 -0
  229. data/app/services/rails_den/mentions/autocomplete.rb +69 -0
  230. data/app/services/rails_den/mentions/candidate.rb +19 -0
  231. data/app/services/rails_den/mentions/parser.rb +185 -0
  232. data/app/services/rails_den/mentions/reconciler.rb +46 -0
  233. data/app/services/rails_den/mentions/renderer.rb +165 -0
  234. data/app/services/rails_den/moderation_access.rb +42 -0
  235. data/app/services/rails_den/moderation_audit.rb +38 -0
  236. data/app/services/rails_den/moderation_event_scope.rb +12 -0
  237. data/app/services/rails_den/moderation_notification_delivery.rb +23 -0
  238. data/app/services/rails_den/notification_events/emitter.rb +226 -0
  239. data/app/services/rails_den/notification_preferences.rb +169 -0
  240. data/app/services/rails_den/notifications/broadcaster.rb +72 -0
  241. data/app/services/rails_den/notifications/materializer.rb +284 -0
  242. data/app/services/rails_den/notifications/subscription_token.rb +28 -0
  243. data/app/services/rails_den/permission_resolver.rb +252 -0
  244. data/app/services/rails_den/presence/board_summary.rb +131 -0
  245. data/app/services/rails_den/presence/broadcaster.rb +193 -0
  246. data/app/services/rails_den/presence/chat_room_registry.rb +263 -0
  247. data/app/services/rails_den/presence/community_summary.rb +166 -0
  248. data/app/services/rails_den/presence/directory.rb +499 -0
  249. data/app/services/rails_den/presence/last_seen.rb +23 -0
  250. data/app/services/rails_den/presence/page_map.rb +202 -0
  251. data/app/services/rails_den/presence/registry.rb +805 -0
  252. data/app/services/rails_den/presence/subscription_token.rb +28 -0
  253. data/app/services/rails_den/presence/visibility.rb +95 -0
  254. data/app/services/rails_den/private_messages/mailbox.rb +108 -0
  255. data/app/services/rails_den/private_messages/read_marker.rb +25 -0
  256. data/app/services/rails_den/private_messages/sender.rb +113 -0
  257. data/app/services/rails_den/private_messages/unread_count.rb +22 -0
  258. data/app/services/rails_den/reaction_summary.rb +54 -0
  259. data/app/services/rails_den/reaction_type_importer.rb +91 -0
  260. data/app/services/rails_den/reaction_type_search.rb +34 -0
  261. data/app/services/rails_den/readable_community_content.rb +156 -0
  262. data/app/services/rails_den/report_assignment.rb +31 -0
  263. data/app/services/rails_den/report_creator.rb +96 -0
  264. data/app/services/rails_den/report_delivery.rb +39 -0
  265. data/app/services/rails_den/report_disposition.rb +25 -0
  266. data/app/services/rails_den/report_recipients.rb +89 -0
  267. data/app/services/rails_den/report_review_policy.rb +66 -0
  268. data/app/services/rails_den/report_review_scope.rb +49 -0
  269. data/app/services/rails_den/restriction_resolver.rb +28 -0
  270. data/app/services/rails_den/role_resolver.rb +66 -0
  271. data/app/services/rails_den/search/adapters/basic.rb +35 -0
  272. data/app/services/rails_den/search/adapters/postgresql.rb +73 -0
  273. data/app/services/rails_den/search/adapters.rb +21 -0
  274. data/app/services/rails_den/search/query.rb +358 -0
  275. data/app/services/rails_den/topic_following.rb +91 -0
  276. data/app/services/rails_den/topic_merger.rb +27 -0
  277. data/app/services/rails_den/topic_moderation.rb +57 -0
  278. data/app/services/rails_den/topic_splitter.rb +36 -0
  279. data/app/services/rails_den/user_restriction_manager.rb +84 -0
  280. data/app/services/rails_den/user_warning_issuer.rb +19 -0
  281. data/app/views/admin/rails_den/advertising_management/_assignment_form.html.erb +340 -0
  282. data/app/views/admin/rails_den/advertising_management/_errors.html.erb +10 -0
  283. data/app/views/admin/rails_den/advertising_management/show.html.erb +324 -0
  284. data/app/views/admin/rails_den/badges/_form.html.erb +191 -0
  285. data/app/views/admin/rails_den/badges/edit.html.erb +14 -0
  286. data/app/views/admin/rails_den/badges/index.html.erb +98 -0
  287. data/app/views/admin/rails_den/badges/new.html.erb +14 -0
  288. data/app/views/admin/rails_den/board_access/show.html.erb +51 -0
  289. data/app/views/admin/rails_den/boards/_form.html.erb +5 -0
  290. data/app/views/admin/rails_den/categories/_form.html.erb +4 -0
  291. data/app/views/admin/rails_den/members/index.html.erb +121 -0
  292. data/app/views/admin/rails_den/members/show.html.erb +398 -0
  293. data/app/views/admin/rails_den/plugins/index.html.erb +49 -0
  294. data/app/views/admin/rails_den/profile_fields/_form.html.erb +169 -0
  295. data/app/views/admin/rails_den/reaction_types/_catalog_search.html.erb +22 -0
  296. data/app/views/admin/rails_den/reaction_types/_errors.html.erb +3 -0
  297. data/app/views/admin/rails_den/reaction_types/index.html.erb +66 -0
  298. data/app/views/admin/rails_den/reaction_types/new.html.erb +23 -0
  299. data/app/views/admin/rails_den/reaction_types/selected.html.erb +15 -0
  300. data/app/views/admin/rails_den/roles/_form.html.erb +64 -0
  301. data/app/views/admin/rails_den/roles/edit.html.erb +18 -0
  302. data/app/views/admin/rails_den/roles/index.html.erb +49 -0
  303. data/app/views/admin/rails_den/roles/new.html.erb +6 -0
  304. data/app/views/admin/rails_den/shared/_access_form.html.erb +59 -0
  305. data/app/views/admin/rails_den/themes/index.html.erb +129 -0
  306. data/app/views/admin/rails_den/trust_levels/_form.html.erb +80 -0
  307. data/app/views/admin/rails_den/trust_levels/edit.html.erb +14 -0
  308. data/app/views/admin/rails_den/trust_levels/index.html.erb +86 -0
  309. data/app/views/admin/rails_den/trust_levels/new.html.erb +14 -0
  310. data/app/views/administrate/application/_index_header.html.erb +68 -0
  311. data/app/views/administrate/application/new.html.erb +42 -0
  312. data/app/views/administrate/application/show.html.erb +89 -0
  313. data/app/views/layouts/administrate/application/_navigation.html.erb +61 -0
  314. data/app/views/layouts/administrate/application.html.erb +40 -0
  315. data/app/views/layouts/rails_den/_navbar.html.erb +293 -0
  316. data/app/views/layouts/rails_den/application.html.erb +198 -0
  317. data/app/views/rails_den/advertising/show.html.erb +13 -0
  318. data/app/views/rails_den/boards/_follow_button.html.erb +45 -0
  319. data/app/views/rails_den/boards/show.html.erb +150 -0
  320. data/app/views/rails_den/bookmarks/_post_control.html.erb +12 -0
  321. data/app/views/rails_den/bookmarks/_topic_control.html.erb +12 -0
  322. data/app/views/rails_den/bookmarks/index.html.erb +78 -0
  323. data/app/views/rails_den/chat_channels/_form.html.erb +174 -0
  324. data/app/views/rails_den/chat_channels/edit.html.erb +51 -0
  325. data/app/views/rails_den/chat_channels/index.html.erb +402 -0
  326. data/app/views/rails_den/chat_channels/new.html.erb +31 -0
  327. data/app/views/rails_den/chat_channels/show.html.erb +476 -0
  328. data/app/views/rails_den/home/index.html.erb +142 -0
  329. data/app/views/rails_den/member_profiles/_avatar.html.erb +18 -0
  330. data/app/views/rails_den/member_profiles/_custom_fields.html.erb +39 -0
  331. data/app/views/rails_den/member_profiles/_followed_boards.html.erb +15 -0
  332. data/app/views/rails_den/member_profiles/_followed_topics.html.erb +15 -0
  333. data/app/views/rails_den/member_profiles/_header.html.erb +103 -0
  334. data/app/views/rails_den/member_profiles/_recent_posts.html.erb +19 -0
  335. data/app/views/rails_den/member_profiles/_recent_topics.html.erb +15 -0
  336. data/app/views/rails_den/member_profiles/_stats.html.erb +11 -0
  337. data/app/views/rails_den/member_profiles/edit.html.erb +265 -0
  338. data/app/views/rails_den/member_profiles/show.html.erb +97 -0
  339. data/app/views/rails_den/member_reports/new.html.erb +1 -0
  340. data/app/views/rails_den/moderation/audit_events/index.html.erb +11 -0
  341. data/app/views/rails_den/moderation/member_histories/show.html.erb +314 -0
  342. data/app/views/rails_den/moderation/reports/index.html.erb +47 -0
  343. data/app/views/rails_den/moderation/reports/show.html.erb +344 -0
  344. data/app/views/rails_den/moderation/restrictions/new.html.erb +14 -0
  345. data/app/views/rails_den/moderation/topics/show.html.erb +14 -0
  346. data/app/views/rails_den/moderation/warnings/new.html.erb +10 -0
  347. data/app/views/rails_den/notifications/_empty_state.html.erb +13 -0
  348. data/app/views/rails_den/notifications/index.html.erb +308 -0
  349. data/app/views/rails_den/passwords/edit.html.erb +55 -0
  350. data/app/views/rails_den/passwords/new.html.erb +49 -0
  351. data/app/views/rails_den/passwords_mailer/reset.html.erb +22 -0
  352. data/app/views/rails_den/passwords_mailer/reset.text.erb +14 -0
  353. data/app/views/rails_den/post_reports/new.html.erb +1 -0
  354. data/app/views/rails_den/posts/_attachments.html.erb +94 -0
  355. data/app/views/rails_den/posts/edit.html.erb +95 -0
  356. data/app/views/rails_den/posts/history.html.erb +123 -0
  357. data/app/views/rails_den/presence/_directory_body.html.erb +155 -0
  358. data/app/views/rails_den/presence/_summary.html.erb +26 -0
  359. data/app/views/rails_den/presence/index.html.erb +29 -0
  360. data/app/views/rails_den/private_conversations/_composer.html.erb +28 -0
  361. data/app/views/rails_den/private_conversations/_conversation_row.html.erb +19 -0
  362. data/app/views/rails_den/private_conversations/_mailbox_nav.html.erb +7 -0
  363. data/app/views/rails_den/private_conversations/_message.html.erb +4 -0
  364. data/app/views/rails_den/private_conversations/_pagination.html.erb +6 -0
  365. data/app/views/rails_den/private_conversations/index.html.erb +11 -0
  366. data/app/views/rails_den/private_conversations/new.html.erb +5 -0
  367. data/app/views/rails_den/private_conversations/sent.html.erb +11 -0
  368. data/app/views/rails_den/private_conversations/show.html.erb +23 -0
  369. data/app/views/rails_den/reactions/_picker_options.html.erb +14 -0
  370. data/app/views/rails_den/reactions/_post_reactions.html.erb +87 -0
  371. data/app/views/rails_den/reactions/_reactor_list.html.erb +16 -0
  372. data/app/views/rails_den/reactions/index.html.erb +34 -0
  373. data/app/views/rails_den/reactions/picker.html.erb +14 -0
  374. data/app/views/rails_den/registrations/edit.html.erb +95 -0
  375. data/app/views/rails_den/registrations/new.html.erb +77 -0
  376. data/app/views/rails_den/reports/_form.html.erb +54 -0
  377. data/app/views/rails_den/search/index.html.erb +235 -0
  378. data/app/views/rails_den/sessions/new.html.erb +64 -0
  379. data/app/views/rails_den/shared/_ad_network_placement.html.erb +5 -0
  380. data/app/views/rails_den/shared/_composer.html.erb +266 -0
  381. data/app/views/rails_den/shared/_sponsor_placement.html.erb +44 -0
  382. data/app/views/rails_den/tags/show.html.erb +91 -0
  383. data/app/views/rails_den/topic_reports/new.html.erb +1 -0
  384. data/app/views/rails_den/topics/_author_badges.html.erb +15 -0
  385. data/app/views/rails_den/topics/_follow_button.html.erb +45 -0
  386. data/app/views/rails_den/topics/_summary.html.erb +121 -0
  387. data/app/views/rails_den/topics/_tags.html.erb +47 -0
  388. data/app/views/rails_den/topics/new.html.erb +181 -0
  389. data/app/views/rails_den/topics/show.html.erb +430 -0
  390. data/config/locales/rails_den.en.yml +65 -0
  391. data/config/routes.rb +442 -0
  392. data/db/migrate/20260820181527_create_rails_den_administrators.rb +12 -0
  393. data/db/migrate/20260820205806_create_rails_den_categories.rb +16 -0
  394. data/db/migrate/20260820212200_create_rails_den_boards.rb +23 -0
  395. data/db/migrate/20260820234758_create_rails_den_topics.rb +24 -0
  396. data/db/migrate/20260821001937_create_rails_den_posts.rb +11 -0
  397. data/db/migrate/20260822004306_add_soft_deletion_to_rails_den_posts.rb +9 -0
  398. data/db/migrate/20260823183944_create_rails_den_onebox_entries.rb +65 -0
  399. data/db/migrate/20260824025007_add_reply_to_to_rails_den_posts.rb +8 -0
  400. data/db/migrate/20260824092300_create_rails_den_post_revisions.rb +22 -0
  401. data/db/migrate/20260824103358_create_rails_den_post_drafts.rb +36 -0
  402. data/db/migrate/20260824142522_create_rails_den_topic_read_states.rb +32 -0
  403. data/db/migrate/20260824214941_create_rails_den_reader_visits.rb +16 -0
  404. data/db/migrate/20260824225733_create_rails_den_board_read_states.rb +34 -0
  405. data/db/migrate/20260824232113_create_rails_den_community_read_states.rb +26 -0
  406. data/db/migrate/20260825121109_create_rails_den_topic_follows.rb +36 -0
  407. data/db/migrate/20260825232623_create_rails_den_board_follows.rb +36 -0
  408. data/db/migrate/20260826003155_create_rails_den_mentions.rb +27 -0
  409. data/db/migrate/20260826080109_create_rails_den_member_profiles.rb +32 -0
  410. data/db/migrate/20260826092936_add_quoted_post_to_rails_den_posts_and_post_drafts.rb +17 -0
  411. data/db/migrate/20260826101755_create_rails_den_notification_events.rb +19 -0
  412. data/db/migrate/20260826121710_create_rails_den_notifications.rb +28 -0
  413. data/db/migrate/20260826140846_create_rails_den_notification_mutes.rb +25 -0
  414. data/db/migrate/20260826175426_create_rails_den_tags.rb +31 -0
  415. data/db/migrate/20260826175428_create_rails_den_topic_tags.rb +26 -0
  416. data/db/migrate/20260827021133_create_rails_den_roles.rb +16 -0
  417. data/db/migrate/20260827021135_create_rails_den_role_assignments.rb +22 -0
  418. data/db/migrate/20260827031134_create_rails_den_role_permissions.rb +28 -0
  419. data/db/migrate/20260827031136_create_rails_den_board_role_permissions.rb +35 -0
  420. data/db/migrate/20260827033116_provision_rails_den_built_in_roles_and_permissions.rb +108 -0
  421. data/db/migrate/20260827040515_create_rails_den_board_role_assignments.rb +33 -0
  422. data/db/migrate/20260827041710_create_rails_den_board_role_requirements.rb +27 -0
  423. data/db/migrate/20260827063901_create_rails_den_category_role_requirements.rb +23 -0
  424. data/db/migrate/20260827063909_create_rails_den_topic_role_requirements.rb +23 -0
  425. data/db/migrate/20260827203747_create_rails_den_report_reasons.rb +19 -0
  426. data/db/migrate/20260827203752_create_rails_den_reports.rb +24 -0
  427. data/db/migrate/20260827203807_provision_rails_den_default_report_reasons.rb +32 -0
  428. data/db/migrate/20260828030352_create_rails_den_moderation_events.rb +26 -0
  429. data/db/migrate/20260828030354_create_rails_den_moderation_notes.rb +13 -0
  430. data/db/migrate/20260828030357_create_rails_den_user_warnings.rb +17 -0
  431. data/db/migrate/20260828030359_create_rails_den_user_restrictions.rb +30 -0
  432. data/db/migrate/20260828030404_add_moderation_fields_to_rails_den_reports.rb +55 -0
  433. data/db/migrate/20260828030407_add_moderation_fields_to_rails_den_topics.rb +14 -0
  434. data/db/migrate/20260828031955_add_opening_post_to_rails_den_topics.rb +25 -0
  435. data/db/migrate/20260828083552_create_rails_den_reaction_types.rb +15 -0
  436. data/db/migrate/20260828083554_create_rails_den_reactions.rb +20 -0
  437. data/db/migrate/20260828083556_provision_rails_den_default_reaction_type.rb +18 -0
  438. data/db/migrate/20260828151420_create_rails_den_bookmarks.rb +20 -0
  439. data/db/migrate/20260828165545_create_rails_den_profile_fields.rb +37 -0
  440. data/db/migrate/20260828165547_create_rails_den_profile_field_values.rb +31 -0
  441. data/db/migrate/20260828191644_add_presence_fields_to_rails_den_member_profiles.rb +22 -0
  442. data/db/migrate/20260829003137_create_rails_den_private_conversations.rb +13 -0
  443. data/db/migrate/20260829003139_create_rails_den_private_conversation_participants.rb +35 -0
  444. data/db/migrate/20260829003141_create_rails_den_private_messages.rb +19 -0
  445. data/db/migrate/20260829003143_create_rails_den_private_message_recipients.rb +26 -0
  446. data/db/migrate/20260829003435_provision_rails_den_message_capability_defaults.rb +39 -0
  447. data/db/migrate/20260829013557_create_rails_den_chat_channels.rb +17 -0
  448. data/db/migrate/20260829014413_create_rails_den_chat_channel_memberships.rb +26 -0
  449. data/db/migrate/20260829014523_create_rails_den_chat_messages.rb +27 -0
  450. data/db/migrate/20260830155021_create_rails_den_chat_channel_read_states.rb +37 -0
  451. data/db/migrate/20260830155023_add_pinned_to_rails_den_chat_messages.rb +16 -0
  452. data/db/migrate/20260830185149_create_rails_den_badges.rb +44 -0
  453. data/db/migrate/20260830185151_create_rails_den_badge_awards.rb +43 -0
  454. data/db/migrate/20260830190053_create_rails_den_reputation_events.rb +52 -0
  455. data/db/migrate/20260830190055_create_rails_den_trust_levels.rb +52 -0
  456. data/db/migrate/20260830190057_create_rails_den_trust_level_achievements.rb +39 -0
  457. data/db/migrate/20260830190059_add_reputation_points_to_rails_den_member_profiles.rb +17 -0
  458. data/db/migrate/20260831120000_create_rails_den_advertising_and_sponsorships.rb +55 -0
  459. data/db/migrate/20260831120001_add_description_to_rails_den_sponsors.rb +5 -0
  460. data/db/migrate/20260831120002_add_advertising_page_to_rails_den_advertising_configurations.rb +7 -0
  461. data/db/migrate/20260831120003_add_destination_url_to_rails_den_sponsors.rb +5 -0
  462. data/db/migrate/20260831120004_move_destination_url_to_rails_den_sponsorships.rb +138 -0
  463. data/db/migrate/20260831204539_create_rails_den_theme_selections.rb +9 -0
  464. data/exe/rails_den +5 -0
  465. data/lib/generators/rails_den/install/install_generator.rb +42 -0
  466. data/lib/generators/rails_den/install/templates/rails_den.rb +75 -0
  467. data/lib/generators/rails_den/install/views/views_generator.rb +56 -0
  468. data/lib/rails/commands/rails_den/import/discourse/discourse_command.rb +48 -0
  469. data/lib/rails_den/cli.rb +174 -0
  470. data/lib/rails_den/configuration.rb +173 -0
  471. data/lib/rails_den/engine.rb +10 -0
  472. data/lib/rails_den/extensions/descriptor.rb +138 -0
  473. data/lib/rails_den/extensions/registry.rb +65 -0
  474. data/lib/rails_den/extensions.rb +49 -0
  475. data/lib/rails_den/formatters/markdown.rb +30 -0
  476. data/lib/rails_den/formatters/plain_text.rb +18 -0
  477. data/lib/rails_den/formatting.rb +130 -0
  478. data/lib/rails_den/gamification.rb +77 -0
  479. data/lib/rails_den/import/discourse/backup.rb +169 -0
  480. data/lib/rails_den/import/discourse/finalizer.rb +617 -0
  481. data/lib/rails_den/import/discourse/importer.rb +266 -0
  482. data/lib/rails_den/import/discourse/source_database.rb +320 -0
  483. data/lib/rails_den/import/discourse/staff_roles.rb +173 -0
  484. data/lib/rails_den/import/discourse/transformer.rb +940 -0
  485. data/lib/rails_den/import/discourse.rb +9 -0
  486. data/lib/rails_den/licensing/client.rb +128 -0
  487. data/lib/rails_den/licensing/result.rb +57 -0
  488. data/lib/rails_den/licensing.rb +28 -0
  489. data/lib/rails_den/metadata.rb +343 -0
  490. data/lib/rails_den/onebox/address_guard.rb +108 -0
  491. data/lib/rails_den/onebox/body_renderer.rb +135 -0
  492. data/lib/rails_den/onebox/embed_policy.rb +179 -0
  493. data/lib/rails_den/onebox/errors.rb +24 -0
  494. data/lib/rails_den/onebox/extractor.rb +130 -0
  495. data/lib/rails_den/onebox/fetcher.rb +199 -0
  496. data/lib/rails_den/onebox/http_transport.rb +164 -0
  497. data/lib/rails_den/onebox/metadata.rb +343 -0
  498. data/lib/rails_den/onebox/oembed/client.rb +46 -0
  499. data/lib/rails_den/onebox/oembed/discovery.rb +296 -0
  500. data/lib/rails_den/onebox/oembed/response.rb +375 -0
  501. data/lib/rails_den/onebox/oembed.rb +18 -0
  502. data/lib/rails_den/onebox/providers/base.rb +23 -0
  503. data/lib/rails_den/onebox/providers/bluesky.rb +402 -0
  504. data/lib/rails_den/onebox/providers/generic.rb +310 -0
  505. data/lib/rails_den/onebox/providers/giphy.rb +281 -0
  506. data/lib/rails_den/onebox/providers/github.rb +300 -0
  507. data/lib/rails_den/onebox/providers/mastodon.rb +505 -0
  508. data/lib/rails_den/onebox/providers/soundcloud.rb +199 -0
  509. data/lib/rails_den/onebox/providers/spotify.rb +196 -0
  510. data/lib/rails_den/onebox/providers/tenor.rb +296 -0
  511. data/lib/rails_den/onebox/providers/tiktok.rb +292 -0
  512. data/lib/rails_den/onebox/providers/twitch.rb +414 -0
  513. data/lib/rails_den/onebox/providers/vimeo.rb +295 -0
  514. data/lib/rails_den/onebox/providers/youtube.rb +201 -0
  515. data/lib/rails_den/onebox/registry.rb +41 -0
  516. data/lib/rails_den/onebox/renderer.rb +403 -0
  517. data/lib/rails_den/onebox/result.rb +86 -0
  518. data/lib/rails_den/onebox/synchronizer.rb +161 -0
  519. data/lib/rails_den/onebox/url.rb +35 -0
  520. data/lib/rails_den/onebox.rb +176 -0
  521. data/lib/rails_den/presence.rb +162 -0
  522. data/lib/rails_den/standalone_template.rb +700 -0
  523. data/lib/rails_den/themes.rb +80 -0
  524. data/lib/rails_den/version.rb +3 -0
  525. data/lib/rails_den/view_installer.rb +129 -0
  526. data/lib/rails_den.rb +113 -0
  527. data/lib/tasks/rails_den_tasks.rake +4 -0
  528. metadata +735 -0
@@ -0,0 +1,1337 @@
1
+ (() => {
2
+ const ONEBOX_POLL_INTERVAL_MS = 500
3
+ const ONEBOX_POLL_TIMEOUT_MS = 30000
4
+ const DRAFT_SAVE_DELAY_MS = 750
5
+
6
+ const DRAFT_COMPOSER_SELECTOR =
7
+ "[data-rails-den-draft-url]"
8
+
9
+ const DRAFT_INPUT_SELECTOR =
10
+ "[data-rails-den-composer-input]"
11
+
12
+ const DRAFT_STATUS_SELECTOR =
13
+ "[data-rails-den-draft-status]"
14
+
15
+ const READ_POSITION_SELECTOR =
16
+ "[data-rails-den-read-position-url]"
17
+
18
+ const BOARD_FOLLOW_SELECTOR =
19
+ "[data-rails-den-board-follow]"
20
+
21
+ const TOPIC_FOLLOW_SELECTOR =
22
+ "[data-rails-den-topic-follow]"
23
+
24
+ const draftAutosaves =
25
+ new Map()
26
+
27
+ function readPositionHeaders() {
28
+ const csrfToken =
29
+ document.querySelector(
30
+ 'meta[name="csrf-token"]'
31
+ )?.content
32
+
33
+ const headers = {
34
+ "Accept":
35
+ "application/json",
36
+ "Content-Type":
37
+ "application/x-www-form-urlencoded;charset=UTF-8"
38
+ }
39
+
40
+ if (csrfToken) {
41
+ headers["X-CSRF-Token"] =
42
+ csrfToken
43
+ }
44
+
45
+ return headers
46
+ }
47
+
48
+ function initializeReadPosition(
49
+ element
50
+ ) {
51
+ if (
52
+ element.dataset
53
+ .railsDenReadPositionReady ===
54
+ "true"
55
+ ) {
56
+ return
57
+ }
58
+
59
+ const url =
60
+ element.dataset
61
+ .railsDenReadPositionUrl
62
+
63
+ const postId =
64
+ element.dataset
65
+ .railsDenReadPositionPostId
66
+
67
+ if (!url || !postId) {
68
+ return
69
+ }
70
+
71
+ element.dataset
72
+ .railsDenReadPositionReady =
73
+ "true"
74
+
75
+ fetch(
76
+ url,
77
+ {
78
+ method: "PATCH",
79
+ credentials: "same-origin",
80
+ headers: readPositionHeaders(),
81
+ body:
82
+ new URLSearchParams({
83
+ post_id: postId
84
+ })
85
+ }
86
+ ).catch(
87
+ () => {
88
+ // Read-state persistence must not
89
+ // interfere with reading the topic.
90
+ }
91
+ )
92
+ }
93
+
94
+ function initializeReadPositions(
95
+ root = document
96
+ ) {
97
+ root
98
+ .querySelectorAll(
99
+ READ_POSITION_SELECTOR
100
+ )
101
+ .forEach(
102
+ initializeReadPosition
103
+ )
104
+ }
105
+
106
+ function initializeSpoilers(root = document) {
107
+ root
108
+ .querySelectorAll(
109
+ ".spoiler:not([data-rails-den-spoiler-ready])"
110
+ )
111
+ .forEach(
112
+ (spoiler) => {
113
+ spoiler.dataset
114
+ .railsDenSpoilerReady =
115
+ "true"
116
+
117
+ spoiler.tabIndex = 0
118
+
119
+ spoiler.setAttribute(
120
+ "role",
121
+ "button"
122
+ )
123
+
124
+ spoiler.setAttribute(
125
+ "aria-expanded",
126
+ "false"
127
+ )
128
+
129
+ spoiler.setAttribute(
130
+ "aria-label",
131
+ "Spoiler. Activate to reveal."
132
+ )
133
+
134
+ function toggle() {
135
+ const revealed =
136
+ spoiler.classList.toggle(
137
+ "is-revealed"
138
+ )
139
+
140
+ spoiler.setAttribute(
141
+ "aria-expanded",
142
+ revealed
143
+ ? "true"
144
+ : "false"
145
+ )
146
+
147
+ spoiler.setAttribute(
148
+ "aria-label",
149
+ revealed
150
+ ? "Spoiler revealed. Activate to hide."
151
+ : "Spoiler. Activate to reveal."
152
+ )
153
+ }
154
+
155
+ spoiler.addEventListener(
156
+ "click",
157
+ (event) => {
158
+ if (
159
+ event.target.closest("a")
160
+ ) {
161
+ return
162
+ }
163
+
164
+ toggle()
165
+ }
166
+ )
167
+
168
+ spoiler.addEventListener(
169
+ "keydown",
170
+ (event) => {
171
+ if (
172
+ event.key !== "Enter" &&
173
+ event.key !== " "
174
+ ) {
175
+ return
176
+ }
177
+
178
+ event.preventDefault()
179
+ toggle()
180
+ }
181
+ )
182
+ }
183
+ )
184
+ }
185
+
186
+ function initializeOneboxPolling(
187
+ root = document
188
+ ) {
189
+ root
190
+ .querySelectorAll(
191
+ [
192
+ "[data-rails-den-onebox-poll-url]",
193
+ ":not([data-rails-den-onebox-polling-ready])"
194
+ ].join("")
195
+ )
196
+ .forEach(
197
+ (element) => {
198
+ element.dataset
199
+ .railsDenOneboxPollingReady =
200
+ "true"
201
+
202
+ const url =
203
+ element.dataset
204
+ .railsDenOneboxPollUrl
205
+
206
+ if (!url) {
207
+ return
208
+ }
209
+
210
+ const startedAt = Date.now()
211
+
212
+ function pollingExpired() {
213
+ return (
214
+ Date.now() - startedAt >=
215
+ ONEBOX_POLL_TIMEOUT_MS
216
+ )
217
+ }
218
+
219
+ function elementStillPresent() {
220
+ return document.documentElement
221
+ .contains(element)
222
+ }
223
+
224
+ function scheduleNextCheck() {
225
+ if (
226
+ pollingExpired() ||
227
+ !elementStillPresent()
228
+ ) {
229
+ return
230
+ }
231
+
232
+ window.setTimeout(
233
+ checkStatus,
234
+ ONEBOX_POLL_INTERVAL_MS
235
+ )
236
+ }
237
+
238
+ async function checkStatus() {
239
+ if (
240
+ pollingExpired() ||
241
+ !elementStillPresent()
242
+ ) {
243
+ return
244
+ }
245
+
246
+ try {
247
+ const response =
248
+ await fetch(
249
+ url,
250
+ {
251
+ credentials: "same-origin",
252
+ headers: {
253
+ Accept: "application/json"
254
+ }
255
+ }
256
+ )
257
+
258
+ if (!response.ok) {
259
+ scheduleNextCheck()
260
+ return
261
+ }
262
+
263
+ const status =
264
+ await response.json()
265
+
266
+ if (status.pending === false) {
267
+ window.location.reload()
268
+ return
269
+ }
270
+ } catch (_error) {
271
+ // A temporary polling failure should not
272
+ // interfere with reading or using the topic.
273
+ }
274
+
275
+ scheduleNextCheck()
276
+ }
277
+
278
+ scheduleNextCheck()
279
+ }
280
+ )
281
+ }
282
+
283
+ function draftHeaders() {
284
+ const csrfToken =
285
+ document.querySelector(
286
+ 'meta[name="csrf-token"]'
287
+ )?.content
288
+
289
+ const headers = {
290
+ "Accept":
291
+ "application/json",
292
+ "Content-Type":
293
+ "application/x-www-form-urlencoded;charset=UTF-8"
294
+ }
295
+
296
+ if (csrfToken) {
297
+ headers["X-CSRF-Token"] =
298
+ csrfToken
299
+ }
300
+
301
+ return headers
302
+ }
303
+
304
+ function draftParameters(
305
+ composer,
306
+ value
307
+ ) {
308
+ const parameters =
309
+ new URLSearchParams()
310
+
311
+ parameters.set(
312
+ "rails_den_post_draft[body]",
313
+ value
314
+ )
315
+
316
+ const replyToId =
317
+ composer.dataset
318
+ .railsDenDraftReplyToId
319
+
320
+ if (replyToId) {
321
+ parameters.set(
322
+ "rails_den_post_draft[reply_to_id]",
323
+ replyToId
324
+ )
325
+ }
326
+
327
+ const quotedPostId =
328
+ composer.dataset
329
+ .railsDenDraftQuotedPostId
330
+
331
+ if (quotedPostId) {
332
+ parameters.set(
333
+ "rails_den_post_draft[quoted_post_id]",
334
+ quotedPostId
335
+ )
336
+ }
337
+
338
+ return parameters
339
+ }
340
+
341
+ function currentDraftInput(state) {
342
+ return state.composer
343
+ .querySelector(
344
+ DRAFT_INPUT_SELECTOR
345
+ )
346
+ }
347
+
348
+ function setDraftStatus(
349
+ state,
350
+ message
351
+ ) {
352
+ if (!state.status) {
353
+ return
354
+ }
355
+
356
+ state.status.textContent =
357
+ message
358
+ }
359
+
360
+ function cancelDraftSave(state) {
361
+ if (state.timer) {
362
+ window.clearTimeout(
363
+ state.timer
364
+ )
365
+
366
+ state.timer = null
367
+ }
368
+
369
+ if (state.controller) {
370
+ state.controller.abort()
371
+ state.controller = null
372
+ }
373
+ }
374
+
375
+ async function saveDraft(state) {
376
+ const input =
377
+ currentDraftInput(state)
378
+
379
+ if (!input) {
380
+ return
381
+ }
382
+
383
+ const value =
384
+ input.value
385
+
386
+ if (
387
+ value ===
388
+ state.lastSavedValue
389
+ ) {
390
+ state.dirty = false
391
+
392
+ setDraftStatus(
393
+ state,
394
+ value.trim()
395
+ ? "Draft saved"
396
+ : "Draft autosave on"
397
+ )
398
+
399
+ return
400
+ }
401
+
402
+ if (state.controller) {
403
+ state.controller.abort()
404
+ }
405
+
406
+ const controller =
407
+ new AbortController()
408
+
409
+ state.controller =
410
+ controller
411
+
412
+ setDraftStatus(
413
+ state,
414
+ "Saving…"
415
+ )
416
+
417
+ try {
418
+ const response =
419
+ await fetch(
420
+ state.composer.dataset
421
+ .railsDenDraftUrl,
422
+ {
423
+ method: "PUT",
424
+ credentials:
425
+ "same-origin",
426
+ headers:
427
+ draftHeaders(),
428
+ body:
429
+ draftParameters(
430
+ state.composer,
431
+ value
432
+ ),
433
+ signal:
434
+ controller.signal
435
+ }
436
+ )
437
+
438
+ if (!response.ok) {
439
+ throw new Error(
440
+ `Draft save failed with ${response.status}`
441
+ )
442
+ }
443
+
444
+ if (
445
+ state.controller !==
446
+ controller
447
+ ) {
448
+ return
449
+ }
450
+
451
+ state.lastSavedValue =
452
+ value
453
+
454
+ state.dirty =
455
+ false
456
+
457
+ setDraftStatus(
458
+ state,
459
+ value.trim()
460
+ ? "Draft saved"
461
+ : "Draft cleared"
462
+ )
463
+ } catch (error) {
464
+ if (
465
+ error.name ===
466
+ "AbortError"
467
+ ) {
468
+ return
469
+ }
470
+
471
+ setDraftStatus(
472
+ state,
473
+ "Draft save failed"
474
+ )
475
+ } finally {
476
+ if (
477
+ state.controller ===
478
+ controller
479
+ ) {
480
+ state.controller =
481
+ null
482
+ }
483
+ }
484
+ }
485
+
486
+ function scheduleDraftSave(state) {
487
+ const input =
488
+ currentDraftInput(state)
489
+
490
+ if (!input) {
491
+ return
492
+ }
493
+
494
+ const value =
495
+ input.value
496
+
497
+ state.dirty =
498
+ value !==
499
+ state.lastSavedValue
500
+
501
+ if (!state.dirty) {
502
+ cancelDraftSave(state)
503
+
504
+ setDraftStatus(
505
+ state,
506
+ value.trim()
507
+ ? "Draft saved"
508
+ : "Draft autosave on"
509
+ )
510
+
511
+ return
512
+ }
513
+
514
+ cancelDraftSave(state)
515
+
516
+ setDraftStatus(
517
+ state,
518
+ "Saving…"
519
+ )
520
+
521
+ state.timer =
522
+ window.setTimeout(
523
+ () => {
524
+ state.timer = null
525
+ saveDraft(state)
526
+ },
527
+ DRAFT_SAVE_DELAY_MS
528
+ )
529
+ }
530
+
531
+ function flushDraftSave(state) {
532
+ if (
533
+ state.submitting ||
534
+ !state.dirty
535
+ ) {
536
+ return
537
+ }
538
+
539
+ const input =
540
+ currentDraftInput(state)
541
+
542
+ if (!input) {
543
+ return
544
+ }
545
+
546
+ const value =
547
+ input.value
548
+
549
+ fetch(
550
+ state.composer.dataset
551
+ .railsDenDraftUrl,
552
+ {
553
+ method: "PUT",
554
+ credentials:
555
+ "same-origin",
556
+ headers:
557
+ draftHeaders(),
558
+ body:
559
+ draftParameters(
560
+ state.composer,
561
+ value
562
+ ),
563
+ keepalive: true
564
+ }
565
+ ).catch(
566
+ () => {
567
+ // Navigation should not be blocked by
568
+ // a last-chance draft save failure.
569
+ }
570
+ )
571
+ }
572
+
573
+ function initializeDraftAutosave(
574
+ composer
575
+ ) {
576
+ if (
577
+ composer.dataset
578
+ .railsDenDraftAutosaveReady ===
579
+ "true"
580
+ ) {
581
+ return
582
+ }
583
+
584
+ const input =
585
+ composer.querySelector(
586
+ DRAFT_INPUT_SELECTOR
587
+ )
588
+
589
+ if (!input) {
590
+ return
591
+ }
592
+
593
+ const status =
594
+ composer.parentElement
595
+ ?.querySelector(
596
+ DRAFT_STATUS_SELECTOR
597
+ )
598
+
599
+ const form =
600
+ composer.closest(
601
+ "form"
602
+ )
603
+
604
+ const state = {
605
+ composer,
606
+ status,
607
+ form,
608
+ timer: null,
609
+ controller: null,
610
+ dirty: false,
611
+ submitting: false,
612
+ lastSavedValue:
613
+ input.value
614
+ }
615
+
616
+ function handleInput() {
617
+ scheduleDraftSave(state)
618
+ }
619
+
620
+ function handleComposerClick() {
621
+ window.requestAnimationFrame(
622
+ () => {
623
+ scheduleDraftSave(state)
624
+ }
625
+ )
626
+ }
627
+
628
+ function handleSubmit() {
629
+ state.submitting = true
630
+ cancelDraftSave(state)
631
+ }
632
+
633
+ composer.addEventListener(
634
+ "input",
635
+ handleInput
636
+ )
637
+
638
+ composer.addEventListener(
639
+ "click",
640
+ handleComposerClick
641
+ )
642
+
643
+ form?.addEventListener(
644
+ "submit",
645
+ handleSubmit
646
+ )
647
+
648
+ composer.dataset
649
+ .railsDenDraftAutosaveReady =
650
+ "true"
651
+
652
+ setDraftStatus(
653
+ state,
654
+ "Draft autosave on"
655
+ )
656
+
657
+ draftAutosaves.set(
658
+ composer,
659
+ {
660
+ state,
661
+ handleInput,
662
+ handleComposerClick,
663
+ handleSubmit
664
+ }
665
+ )
666
+ }
667
+
668
+ function initializeDraftAutosaves(
669
+ root = document
670
+ ) {
671
+ root
672
+ .querySelectorAll(
673
+ DRAFT_COMPOSER_SELECTOR
674
+ )
675
+ .forEach(
676
+ initializeDraftAutosave
677
+ )
678
+ }
679
+
680
+ function boardFollowHeaders() {
681
+ const csrfToken =
682
+ document.querySelector(
683
+ 'meta[name="csrf-token"]'
684
+ )?.content
685
+
686
+ const headers = {
687
+ "Accept": "application/json"
688
+ }
689
+
690
+ if (csrfToken) {
691
+ headers["X-CSRF-Token"] =
692
+ csrfToken
693
+ }
694
+
695
+ return headers
696
+ }
697
+
698
+ function setBoardFollowMethod(
699
+ form,
700
+ following
701
+ ) {
702
+ let methodInput =
703
+ form.querySelector(
704
+ 'input[name="_method"]'
705
+ )
706
+
707
+ if (following) {
708
+ if (!methodInput) {
709
+ methodInput =
710
+ document.createElement(
711
+ "input"
712
+ )
713
+
714
+ methodInput.type = "hidden"
715
+ methodInput.name = "_method"
716
+ form.appendChild(methodInput)
717
+ }
718
+
719
+ methodInput.value = "delete"
720
+ } else {
721
+ methodInput?.remove()
722
+ }
723
+ }
724
+
725
+ function setBoardFollowState(
726
+ form,
727
+ following
728
+ ) {
729
+ const button =
730
+ form.querySelector(
731
+ "[data-rails-den-board-follow-button]"
732
+ )
733
+
734
+ const label =
735
+ form.querySelector(
736
+ "[data-rails-den-board-follow-label]"
737
+ )
738
+
739
+ form.dataset.railsDenFollowing =
740
+ following ? "true" : "false"
741
+
742
+ button?.setAttribute(
743
+ "aria-pressed",
744
+ following ? "true" : "false"
745
+ )
746
+
747
+ if (button) {
748
+ button.title =
749
+ following
750
+ ? form.dataset
751
+ .railsDenUnfollowLabel
752
+ : form.dataset
753
+ .railsDenFollowLabel
754
+ }
755
+
756
+ if (label) {
757
+ label.textContent =
758
+ following
759
+ ? `★ ${form.dataset.railsDenFollowingLabel}`
760
+ : `☆ ${form.dataset.railsDenFollowLabel}`
761
+ }
762
+
763
+ setBoardFollowMethod(
764
+ form,
765
+ following
766
+ )
767
+ }
768
+
769
+ function setBoardFollowStatus(
770
+ form,
771
+ message,
772
+ error = false
773
+ ) {
774
+ const status =
775
+ form.querySelector(
776
+ "[data-rails-den-board-follow-status]"
777
+ )
778
+
779
+ if (!status) {
780
+ return
781
+ }
782
+
783
+ status.textContent = message
784
+ status.classList.toggle(
785
+ "is-error",
786
+ error
787
+ )
788
+ }
789
+
790
+ function initializeBoardFollow(form) {
791
+ if (
792
+ form.dataset
793
+ .railsDenBoardFollowReady ===
794
+ "true"
795
+ ) {
796
+ return
797
+ }
798
+
799
+ let requestSequence = 0
800
+
801
+ form.addEventListener(
802
+ "submit",
803
+ async (event) => {
804
+ const button =
805
+ form.querySelector(
806
+ "[data-rails-den-board-follow-button]"
807
+ )
808
+
809
+ const label =
810
+ form.querySelector(
811
+ "[data-rails-den-board-follow-label]"
812
+ )
813
+
814
+ const url =
815
+ form.dataset
816
+ .railsDenFollowUrl
817
+
818
+ if (!button || !label || !url) {
819
+ return
820
+ }
821
+
822
+ event.preventDefault()
823
+
824
+ const wasFollowing =
825
+ form.dataset
826
+ .railsDenFollowing ===
827
+ "true"
828
+
829
+ const sequence =
830
+ ++requestSequence
831
+
832
+ button.disabled = true
833
+ button.setAttribute(
834
+ "aria-busy",
835
+ "true"
836
+ )
837
+
838
+ label.textContent =
839
+ wasFollowing
840
+ ? form.dataset
841
+ .railsDenUnfollowingBusy
842
+ : form.dataset
843
+ .railsDenFollowingBusy
844
+
845
+ setBoardFollowStatus(
846
+ form,
847
+ ""
848
+ )
849
+
850
+ try {
851
+ const response =
852
+ await fetch(
853
+ url,
854
+ {
855
+ method:
856
+ wasFollowing
857
+ ? "DELETE"
858
+ : "POST",
859
+ credentials:
860
+ "same-origin",
861
+ headers:
862
+ boardFollowHeaders()
863
+ }
864
+ )
865
+
866
+ if (response.redirected) {
867
+ window.location.href =
868
+ response.url
869
+ return
870
+ }
871
+
872
+ const contentType =
873
+ response.headers.get(
874
+ "content-type"
875
+ ) || ""
876
+
877
+ if (
878
+ !response.ok ||
879
+ !contentType.includes(
880
+ "application/json"
881
+ )
882
+ ) {
883
+ throw new Error(
884
+ `Board follow update failed with ${response.status}`
885
+ )
886
+ }
887
+
888
+ const result =
889
+ await response.json()
890
+
891
+ if (
892
+ typeof result.following !==
893
+ "boolean" ||
894
+ typeof result.changed !==
895
+ "boolean"
896
+ ) {
897
+ throw new Error(
898
+ "Board follow response was invalid"
899
+ )
900
+ }
901
+
902
+ if (sequence !== requestSequence) {
903
+ return
904
+ }
905
+
906
+ setBoardFollowState(
907
+ form,
908
+ result.following
909
+ )
910
+
911
+ setBoardFollowStatus(
912
+ form,
913
+ result.following
914
+ ? form.dataset
915
+ .railsDenFollowedNotice
916
+ : form.dataset
917
+ .railsDenUnfollowedNotice
918
+ )
919
+ } catch (_error) {
920
+ if (sequence !== requestSequence) {
921
+ return
922
+ }
923
+
924
+ setBoardFollowState(
925
+ form,
926
+ wasFollowing
927
+ )
928
+
929
+ setBoardFollowStatus(
930
+ form,
931
+ form.dataset
932
+ .railsDenUpdateFailed,
933
+ true
934
+ )
935
+ } finally {
936
+ if (sequence === requestSequence) {
937
+ button.disabled = false
938
+ button.removeAttribute(
939
+ "aria-busy"
940
+ )
941
+ button.focus()
942
+ }
943
+ }
944
+ }
945
+ )
946
+
947
+ form.dataset
948
+ .railsDenBoardFollowReady =
949
+ "true"
950
+ }
951
+
952
+ function initializeBoardFollows(
953
+ root = document
954
+ ) {
955
+ root
956
+ .querySelectorAll(
957
+ BOARD_FOLLOW_SELECTOR
958
+ )
959
+ .forEach(
960
+ initializeBoardFollow
961
+ )
962
+ }
963
+
964
+ function topicFollowHeaders() {
965
+ const csrfToken =
966
+ document.querySelector(
967
+ 'meta[name="csrf-token"]'
968
+ )?.content
969
+
970
+ const headers = {
971
+ "Accept": "application/json"
972
+ }
973
+
974
+ if (csrfToken) {
975
+ headers["X-CSRF-Token"] =
976
+ csrfToken
977
+ }
978
+
979
+ return headers
980
+ }
981
+
982
+ function setTopicFollowMethod(
983
+ form,
984
+ following
985
+ ) {
986
+ let methodInput =
987
+ form.querySelector(
988
+ 'input[name="_method"]'
989
+ )
990
+
991
+ if (following) {
992
+ if (!methodInput) {
993
+ methodInput =
994
+ document.createElement(
995
+ "input"
996
+ )
997
+
998
+ methodInput.type = "hidden"
999
+ methodInput.name = "_method"
1000
+ form.appendChild(methodInput)
1001
+ }
1002
+
1003
+ methodInput.value = "delete"
1004
+ } else {
1005
+ methodInput?.remove()
1006
+ }
1007
+ }
1008
+
1009
+ function setTopicFollowState(
1010
+ form,
1011
+ following
1012
+ ) {
1013
+ const button =
1014
+ form.querySelector(
1015
+ "[data-rails-den-topic-follow-button]"
1016
+ )
1017
+
1018
+ const label =
1019
+ form.querySelector(
1020
+ "[data-rails-den-topic-follow-label]"
1021
+ )
1022
+
1023
+ form.dataset.railsDenFollowing =
1024
+ following ? "true" : "false"
1025
+
1026
+ button?.setAttribute(
1027
+ "aria-pressed",
1028
+ following ? "true" : "false"
1029
+ )
1030
+
1031
+ if (button) {
1032
+ button.title =
1033
+ following
1034
+ ? form.dataset
1035
+ .railsDenUnfollowLabel
1036
+ : form.dataset
1037
+ .railsDenFollowLabel
1038
+ }
1039
+
1040
+ if (label) {
1041
+ label.textContent =
1042
+ following
1043
+ ? `★ ${form.dataset.railsDenFollowingLabel}`
1044
+ : `☆ ${form.dataset.railsDenFollowLabel}`
1045
+ }
1046
+
1047
+ setTopicFollowMethod(
1048
+ form,
1049
+ following
1050
+ )
1051
+ }
1052
+
1053
+ function setTopicFollowStatus(
1054
+ form,
1055
+ message,
1056
+ error = false
1057
+ ) {
1058
+ const status =
1059
+ form.querySelector(
1060
+ "[data-rails-den-topic-follow-status]"
1061
+ )
1062
+
1063
+ if (!status) {
1064
+ return
1065
+ }
1066
+
1067
+ status.textContent = message
1068
+ status.classList.toggle(
1069
+ "is-error",
1070
+ error
1071
+ )
1072
+ }
1073
+
1074
+ function initializeTopicFollow(form) {
1075
+ if (
1076
+ form.dataset
1077
+ .railsDenTopicFollowReady ===
1078
+ "true"
1079
+ ) {
1080
+ return
1081
+ }
1082
+
1083
+ let requestSequence = 0
1084
+
1085
+ form.addEventListener(
1086
+ "submit",
1087
+ async (event) => {
1088
+ event.preventDefault()
1089
+
1090
+ const button =
1091
+ form.querySelector(
1092
+ "[data-rails-den-topic-follow-button]"
1093
+ )
1094
+
1095
+ const label =
1096
+ form.querySelector(
1097
+ "[data-rails-den-topic-follow-label]"
1098
+ )
1099
+
1100
+ const url =
1101
+ form.dataset
1102
+ .railsDenFollowUrl
1103
+
1104
+ if (!button || !label || !url) {
1105
+ form.submit()
1106
+ return
1107
+ }
1108
+
1109
+ const wasFollowing =
1110
+ form.dataset
1111
+ .railsDenFollowing ===
1112
+ "true"
1113
+
1114
+ const sequence =
1115
+ ++requestSequence
1116
+
1117
+ button.disabled = true
1118
+ button.setAttribute(
1119
+ "aria-busy",
1120
+ "true"
1121
+ )
1122
+
1123
+ label.textContent =
1124
+ wasFollowing
1125
+ ? form.dataset
1126
+ .railsDenUnfollowingBusy
1127
+ : form.dataset
1128
+ .railsDenFollowingBusy
1129
+
1130
+ setTopicFollowStatus(
1131
+ form,
1132
+ ""
1133
+ )
1134
+
1135
+ try {
1136
+ const response =
1137
+ await fetch(
1138
+ url,
1139
+ {
1140
+ method:
1141
+ wasFollowing
1142
+ ? "DELETE"
1143
+ : "POST",
1144
+ credentials:
1145
+ "same-origin",
1146
+ headers:
1147
+ topicFollowHeaders()
1148
+ }
1149
+ )
1150
+
1151
+ if (response.redirected) {
1152
+ window.location.href =
1153
+ response.url
1154
+ return
1155
+ }
1156
+
1157
+ const contentType =
1158
+ response.headers.get(
1159
+ "content-type"
1160
+ ) || ""
1161
+
1162
+ if (
1163
+ !response.ok ||
1164
+ !contentType.includes(
1165
+ "application/json"
1166
+ )
1167
+ ) {
1168
+ throw new Error(
1169
+ `Follow update failed with ${response.status}`
1170
+ )
1171
+ }
1172
+
1173
+ const result =
1174
+ await response.json()
1175
+
1176
+ if (
1177
+ typeof result.following !==
1178
+ "boolean" ||
1179
+ typeof result.changed !==
1180
+ "boolean"
1181
+ ) {
1182
+ throw new Error(
1183
+ "Follow response was invalid"
1184
+ )
1185
+ }
1186
+
1187
+ if (sequence !== requestSequence) {
1188
+ return
1189
+ }
1190
+
1191
+ setTopicFollowState(
1192
+ form,
1193
+ result.following
1194
+ )
1195
+
1196
+ setTopicFollowStatus(
1197
+ form,
1198
+ result.following
1199
+ ? form.dataset
1200
+ .railsDenFollowedNotice
1201
+ : form.dataset
1202
+ .railsDenUnfollowedNotice
1203
+ )
1204
+ } catch (_error) {
1205
+ if (sequence !== requestSequence) {
1206
+ return
1207
+ }
1208
+
1209
+ setTopicFollowState(
1210
+ form,
1211
+ wasFollowing
1212
+ )
1213
+
1214
+ setTopicFollowStatus(
1215
+ form,
1216
+ form.dataset
1217
+ .railsDenUpdateFailed,
1218
+ true
1219
+ )
1220
+ } finally {
1221
+ if (sequence === requestSequence) {
1222
+ button.disabled = false
1223
+ button.removeAttribute(
1224
+ "aria-busy"
1225
+ )
1226
+ }
1227
+ }
1228
+ }
1229
+ )
1230
+
1231
+ form.dataset
1232
+ .railsDenTopicFollowReady =
1233
+ "true"
1234
+ }
1235
+
1236
+ function initializeTopicFollows(
1237
+ root = document
1238
+ ) {
1239
+ root
1240
+ .querySelectorAll(
1241
+ TOPIC_FOLLOW_SELECTOR
1242
+ )
1243
+ .forEach(
1244
+ initializeTopicFollow
1245
+ )
1246
+ }
1247
+
1248
+ function teardownDraftAutosaves() {
1249
+ draftAutosaves.forEach(
1250
+ (
1251
+ {
1252
+ state,
1253
+ handleInput,
1254
+ handleComposerClick,
1255
+ handleSubmit
1256
+ },
1257
+ composer
1258
+ ) => {
1259
+ flushDraftSave(state)
1260
+ cancelDraftSave(state)
1261
+
1262
+ composer.removeEventListener(
1263
+ "input",
1264
+ handleInput
1265
+ )
1266
+
1267
+ composer.removeEventListener(
1268
+ "click",
1269
+ handleComposerClick
1270
+ )
1271
+
1272
+ state.form?.removeEventListener(
1273
+ "submit",
1274
+ handleSubmit
1275
+ )
1276
+
1277
+ delete composer.dataset
1278
+ .railsDenDraftAutosaveReady
1279
+ }
1280
+ )
1281
+
1282
+ draftAutosaves.clear()
1283
+ }
1284
+
1285
+ function flushDraftAutosaves() {
1286
+ draftAutosaves.forEach(
1287
+ ({ state }) => {
1288
+ flushDraftSave(state)
1289
+ }
1290
+ )
1291
+ }
1292
+
1293
+ function initializeContent(
1294
+ root = document
1295
+ ) {
1296
+ initializeReadPositions(root)
1297
+ initializeSpoilers(root)
1298
+ initializeOneboxPolling(root)
1299
+ initializeDraftAutosaves(root)
1300
+ initializeBoardFollows(root)
1301
+ initializeTopicFollows(root)
1302
+ }
1303
+
1304
+ document.addEventListener(
1305
+ "DOMContentLoaded",
1306
+ () => {
1307
+ initializeContent()
1308
+ }
1309
+ )
1310
+
1311
+ document.addEventListener(
1312
+ "turbo:load",
1313
+ () => {
1314
+ initializeContent()
1315
+ }
1316
+ )
1317
+
1318
+ document.addEventListener(
1319
+ "turbo:before-cache",
1320
+ teardownDraftAutosaves
1321
+ )
1322
+
1323
+ window.addEventListener(
1324
+ "pagehide",
1325
+ flushDraftAutosaves
1326
+ )
1327
+
1328
+ document.addEventListener(
1329
+ "rails-den:content-updated",
1330
+ (event) => {
1331
+ initializeContent(
1332
+ event.detail?.root ||
1333
+ document
1334
+ )
1335
+ }
1336
+ )
1337
+ })()