serverless-ircd 0.6.0 → 0.8.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 (350) hide show
  1. package/.github/workflows/ci.yml +9 -4
  2. package/.gitmodules +4 -1
  3. package/CHANGELOG.md +480 -22
  4. package/README.md +244 -57
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/aws-stack/src/aws-stack.ts +186 -18
  7. package/apps/aws-stack/tests/stack.test.ts +400 -56
  8. package/apps/cf-tcp-container/package.json +1 -1
  9. package/apps/cf-worker/package.json +1 -1
  10. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  11. package/apps/cf-worker/src/worker.ts +89 -9
  12. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  13. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  14. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  15. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  16. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  17. package/apps/cf-worker/wrangler.test.toml +23 -0
  18. package/apps/cf-worker/wrangler.toml +56 -0
  19. package/apps/local-cli/package.json +1 -1
  20. package/apps/local-cli/src/config-loader.ts +8 -0
  21. package/apps/local-cli/src/main.ts +16 -0
  22. package/apps/local-cli/src/server.ts +1 -0
  23. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  24. package/apps/web/landing/index.html +215 -0
  25. package/apps/web/package.json +28 -0
  26. package/apps/web/scripts/build.mjs +132 -0
  27. package/apps/web/src/build-env.ts +43 -0
  28. package/apps/web/src/config-schema.ts +138 -0
  29. package/apps/web/static/config.json +28 -0
  30. package/apps/web/static/config.prod.json +28 -0
  31. package/apps/web/static/config.staging.json +28 -0
  32. package/apps/web/tests/build-env.test.ts +63 -0
  33. package/apps/web/tests/build-smoke.test.ts +103 -0
  34. package/apps/web/tests/config-schema.test.ts +432 -0
  35. package/apps/web/tests/workspace.test.ts +12 -0
  36. package/apps/web/tsconfig.json +10 -0
  37. package/apps/web/upstream/.browserslistrc +3 -0
  38. package/apps/web/upstream/.editorconfig +9 -0
  39. package/apps/web/upstream/.eslintignore +3 -0
  40. package/apps/web/upstream/.eslintrc.js +90 -0
  41. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  42. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  43. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  44. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  45. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  46. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  47. package/apps/web/upstream/.prettierrc.js +9 -0
  48. package/apps/web/upstream/.stylelintignore +5 -0
  49. package/apps/web/upstream/.stylelintrc.js +21 -0
  50. package/apps/web/upstream/LICENSE +201 -0
  51. package/apps/web/upstream/README.md +76 -0
  52. package/apps/web/upstream/babel.config.js +25 -0
  53. package/apps/web/upstream/crowdin.yml +3 -0
  54. package/apps/web/upstream/index.html +35 -0
  55. package/apps/web/upstream/jest.config.js +7 -0
  56. package/apps/web/upstream/jsdoc.json +22 -0
  57. package/apps/web/upstream/package.json +80 -0
  58. package/apps/web/upstream/scripts/pre-commit +43 -0
  59. package/apps/web/upstream/src/components/App.vue +516 -0
  60. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  61. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  62. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  63. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  64. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  65. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  66. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  67. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  68. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  69. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  70. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  71. package/apps/web/upstream/src/components/Container.vue +401 -0
  72. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  73. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  74. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  75. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  76. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  77. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  78. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  79. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  80. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  81. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  82. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  83. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  84. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  85. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  86. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  87. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  88. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  89. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  90. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  91. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  92. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  93. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  94. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  95. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  96. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  97. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  98. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  99. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  100. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  101. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  102. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  103. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  104. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  105. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  106. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  107. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  108. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  109. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  110. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  111. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  112. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  113. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  114. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  115. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  116. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  117. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  118. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  119. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  120. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  121. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  122. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  123. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  124. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  125. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  126. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  127. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  128. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  129. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  130. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  131. package/apps/web/upstream/src/libs/IPC.js +51 -0
  132. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  133. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  134. package/apps/web/upstream/src/libs/Logger.js +71 -0
  135. package/apps/web/upstream/src/libs/Message.js +169 -0
  136. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  137. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  138. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  139. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  140. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  141. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  142. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  143. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  144. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  145. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  146. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  147. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  148. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  149. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  150. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  151. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  152. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  153. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  154. package/apps/web/upstream/src/libs/state/common.js +26 -0
  155. package/apps/web/upstream/src/libs/state.js +961 -0
  156. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  157. package/apps/web/upstream/src/main.js +566 -0
  158. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  159. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  160. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  161. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  162. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  163. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  164. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  165. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  166. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  167. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  168. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  169. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  170. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  171. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  172. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  173. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  174. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  175. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  176. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  177. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  186. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  191. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  205. package/apps/web/upstream/src/res/localesList.json +27 -0
  206. package/apps/web/upstream/src/res/logo.png +0 -0
  207. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  208. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  209. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  210. package/apps/web/upstream/static/config.json +28 -0
  211. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  212. package/apps/web/upstream/static/favicon.png +0 -0
  213. package/apps/web/upstream/static/highlight.mp3 +0 -0
  214. package/apps/web/upstream/static/highlight.ogg +0 -0
  215. package/apps/web/upstream/static/locales/.gitignore +2 -0
  216. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  217. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  218. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  219. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  220. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  221. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  222. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  223. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  224. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  225. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  226. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  227. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  228. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  229. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  230. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  231. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  232. package/apps/web/upstream/vue.config.js +150 -0
  233. package/apps/web/upstream/yarn.lock +9767 -0
  234. package/apps/web/vitest.config.ts +13 -0
  235. package/biome.json +1 -0
  236. package/docs/AWS-Deployment.md +21 -8
  237. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  238. package/docs/Home.md +1 -0
  239. package/docs/PlanExtensions.md +113 -3
  240. package/docs/Release-Process.md +23 -13
  241. package/docs/Services.md +546 -0
  242. package/docs/WebClientGuide.md +536 -0
  243. package/package.json +2 -2
  244. package/packages/aws-adapter/package.json +1 -1
  245. package/packages/aws-adapter/src/aws-runtime.ts +5 -0
  246. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  247. package/packages/aws-adapter/src/config-loader.ts +11 -0
  248. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  249. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  250. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  251. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  252. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  253. package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
  254. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  255. package/packages/aws-adapter/src/index.ts +4 -0
  256. package/packages/aws-adapter/src/stats.ts +6 -1
  257. package/packages/aws-adapter/src/tables.ts +34 -4
  258. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  259. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  260. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  261. package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
  262. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  263. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  264. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  265. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  266. package/packages/cf-adapter/package.json +1 -1
  267. package/packages/cf-adapter/src/config-loader.ts +11 -0
  268. package/packages/cf-adapter/src/connection-do.ts +112 -2
  269. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  270. package/packages/cf-adapter/src/env.ts +8 -0
  271. package/packages/cf-adapter/src/index.ts +5 -0
  272. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  273. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  274. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  275. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  276. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  277. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  278. package/packages/in-memory-runtime/package.json +1 -1
  279. package/packages/irc-core/package.json +1 -1
  280. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  281. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  282. package/packages/irc-core/src/commands/account-auth.ts +172 -0
  283. package/packages/irc-core/src/commands/chanserv.ts +882 -0
  284. package/packages/irc-core/src/commands/hostserv.ts +487 -0
  285. package/packages/irc-core/src/commands/index.ts +12 -0
  286. package/packages/irc-core/src/commands/join.ts +164 -8
  287. package/packages/irc-core/src/commands/markread.ts +202 -0
  288. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  289. package/packages/irc-core/src/commands/mode.ts +96 -4
  290. package/packages/irc-core/src/commands/nickserv.ts +390 -0
  291. package/packages/irc-core/src/commands/oper.ts +18 -1
  292. package/packages/irc-core/src/commands/operserv.ts +346 -0
  293. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  294. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  295. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  296. package/packages/irc-core/src/commands/registration.ts +61 -6
  297. package/packages/irc-core/src/commands/sasl.ts +18 -49
  298. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  299. package/packages/irc-core/src/commands/topic.ts +37 -0
  300. package/packages/irc-core/src/config.ts +36 -5
  301. package/packages/irc-core/src/effects.ts +56 -1
  302. package/packages/irc-core/src/ports.ts +1653 -84
  303. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  304. package/packages/irc-core/src/state/channel.ts +21 -1
  305. package/packages/irc-core/src/state/connection.ts +25 -1
  306. package/packages/irc-core/src/types.ts +48 -12
  307. package/packages/irc-core/tests/batch.test.ts +15 -0
  308. package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
  309. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  310. package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
  311. package/packages/irc-core/tests/commands/join.test.ts +393 -1
  312. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  313. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  314. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  315. package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
  316. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  317. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  318. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  319. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  320. package/packages/irc-core/tests/commands/registration.test.ts +788 -14
  321. package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
  322. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  323. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  324. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  325. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  326. package/packages/irc-core/tests/config.test.ts +49 -5
  327. package/packages/irc-core/tests/effects.test.ts +19 -0
  328. package/packages/irc-core/tests/message-store.test.ts +63 -0
  329. package/packages/irc-core/tests/numerics.test.ts +13 -0
  330. package/packages/irc-core/tests/parser.test.ts +109 -0
  331. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  332. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  333. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  334. package/packages/irc-server/package.json +1 -1
  335. package/packages/irc-server/src/actor.ts +71 -16
  336. package/packages/irc-server/src/dispatch.ts +94 -7
  337. package/packages/irc-server/src/routing.ts +19 -0
  338. package/packages/irc-server/tests/actor.test.ts +623 -12
  339. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  340. package/packages/irc-server/tests/routing.test.ts +6 -0
  341. package/packages/irc-test-support/package.json +1 -1
  342. package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
  343. package/packages/irc-test-support/src/index.ts +1 -0
  344. package/packages/irc-test-support/src/scenarios.ts +24 -2
  345. package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
  346. package/tools/ci-hardening/package.json +1 -1
  347. package/tools/load-test/package.json +1 -1
  348. package/tools/tcp-ws-forwarder/package.json +1 -1
  349. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
  350. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
@@ -0,0 +1,1028 @@
1
+ <template>
2
+ <div
3
+ :key="'messagelist-' + buffer.name"
4
+ ref="scroller"
5
+ v-resizeobserver="onListResize"
6
+ class="kiwi-messagelist"
7
+ :class="{'kiwi-messagelist--smoothscroll': smooth_scroll}"
8
+ @click.self="onListClick"
9
+ >
10
+ <div v-resizeobserver="onListResize">
11
+ <div
12
+ v-if="shouldShowChathistoryTools"
13
+ class="kiwi-messagelist-scrollback"
14
+ >
15
+ <a
16
+ v-if="!buffer.flag('is_requesting_chathistory')"
17
+ class="u-link"
18
+ @click="buffer.requestScrollback()"
19
+ >
20
+ {{ $t('messages_load') }}
21
+ </a>
22
+ <a v-else>{{ $t('messages_loading') }}</a>
23
+ </div>
24
+
25
+ <transition-group tag="div">
26
+ <template v-for="day in filteredMessagesGroupedDay">
27
+ <div
28
+ v-if="filteredMessagesGroupedDay.length > 1 && day.messages.length > 0"
29
+ :key="'msgdatemarker' + day.dayNum"
30
+ class="kiwi-messagelist-seperator"
31
+ >
32
+ <span>{{ (new Date(day.messages[0].time)).toDateString() }}</span>
33
+ </div>
34
+ <transition-group :key="day.dayNum" tag="div">
35
+ <template v-for="message in day.messages">
36
+ <div
37
+ v-if="shouldShowUnreadMarker(message)"
38
+ :key="'msgunreadmarker' + message.id"
39
+ class="kiwi-messagelist-seperator"
40
+ >
41
+ <span>{{ $t('unread_messages') }}</span>
42
+ </div>
43
+
44
+ <div
45
+ :key="'msg' + message.id"
46
+ :class="[
47
+ 'kiwi-messagelist-item',
48
+ selectedMessages[message.id] ?
49
+ 'kiwi-messagelist-item--selected' :
50
+ ''
51
+ ]"
52
+ >
53
+ <!-- message.template is checked first for a custom component,
54
+ then each message layout checks for a message.bodyTemplate
55
+ custom component to apply only to the body area
56
+ -->
57
+ <div
58
+ v-if="message.render() &&
59
+ message.template &&
60
+ message.template.$el &&
61
+ isTemplateVue(message.template)"
62
+ v-rawElement="message.template.$el"
63
+ />
64
+ <component
65
+ :is="message.template"
66
+ v-else-if="message.render() && message.template"
67
+ v-bind="message.templateProps"
68
+ :buffer="buffer"
69
+ :message="message"
70
+ :idx="filteredMessages.indexOf(message)"
71
+ :ml="thisMl"
72
+ />
73
+ <message-list-message-modern
74
+ v-else-if="listType === 'modern'"
75
+ :message="message"
76
+ :idx="filteredMessages.indexOf(message)"
77
+ :ml="thisMl"
78
+ />
79
+ <message-list-message-inline
80
+ v-else-if="listType === 'inline'"
81
+ :message="message"
82
+ :idx="filteredMessages.indexOf(message)"
83
+ :ml="thisMl"
84
+ />
85
+ <message-list-message-compact
86
+ v-else-if="listType === 'compact'"
87
+ :message="message"
88
+ :idx="filteredMessages.indexOf(message)"
89
+ :ml="thisMl"
90
+ />
91
+ </div>
92
+ </template>
93
+ </transition-group>
94
+ </template>
95
+ </transition-group>
96
+
97
+ <transition name="kiwi-messagelist-joinloadertrans">
98
+ <div v-if="shouldShowJoiningLoader" class="kiwi-messagelist-joinloader">
99
+ <LoadingAnimation />
100
+ </div>
101
+ </transition>
102
+
103
+ <buffer-key
104
+ v-if="shouldRequestChannelKey"
105
+ :buffer="buffer"
106
+ :network="buffer.getNetwork()"
107
+ />
108
+ </div>
109
+ </div>
110
+ </template>
111
+
112
+ <script>
113
+ 'kiwi public';
114
+
115
+ import Vue from 'vue';
116
+ import strftime from 'strftime';
117
+ import Logger from '@/libs/Logger';
118
+ import * as bufferTools from '@/libs/bufferTools';
119
+ import BufferKey from './BufferKey';
120
+ import MessageListMessageCompact from './MessageListMessageCompact';
121
+ import MessageListMessageModern from './MessageListMessageModern';
122
+ import MessageListMessageInline from './MessageListMessageInline';
123
+ import LoadingAnimation from './LoadingAnimation.vue';
124
+
125
+ require('@/libs/polyfill/Element.closest');
126
+
127
+ let log = Logger.namespace('MessageList.vue');
128
+
129
+ // If we're scrolled up more than this many pixels, don't auto scroll down to the bottom
130
+ // of the message list
131
+ const BOTTOM_SCROLL_MARGIN = 60;
132
+
133
+ export default {
134
+ components: {
135
+ BufferKey,
136
+ MessageListMessageModern,
137
+ MessageListMessageCompact,
138
+ MessageListMessageInline,
139
+ LoadingAnimation,
140
+ },
141
+ props: ['buffer'],
142
+ data() {
143
+ return {
144
+ smooth_scroll: false,
145
+ auto_scroll: true,
146
+ force_smooth_scroll: null,
147
+ chathistoryAvailable: true,
148
+ hover_nick: '',
149
+ message_info_open: null,
150
+ timeToClose: false,
151
+ startClosing: false,
152
+ selectedMessages: Object.create(null),
153
+ };
154
+ },
155
+ computed: {
156
+ thisMl() {
157
+ return this;
158
+ },
159
+ shouldAutoEmbed() {
160
+ if (this.buffer.isChannel() && this.buffer.setting('inline_link_auto_previews')) {
161
+ return true;
162
+ }
163
+ if (this.buffer.isQuery() && this.buffer.setting('inline_link_auto_previews_query')) {
164
+ return true;
165
+ }
166
+ return false;
167
+ },
168
+ listType() {
169
+ if (this.$state.setting('messageLayout')) {
170
+ log.info('Deprecation Warning: The config option \'messageLayout\' has been moved to buffers.messageLayout');
171
+ }
172
+ return this.buffer.setting('messageLayout') || this.$state.setting('messageLayout');
173
+ },
174
+ useExtraFormatting() {
175
+ // Enables simple markdown formatting
176
+ return this.buffer.setting('extra_formatting');
177
+ },
178
+ shouldShowChathistoryTools() {
179
+ // Only show it if we're connected
180
+ if (this.buffer.getNetwork().state !== 'connected') {
181
+ return false;
182
+ }
183
+
184
+ let isCorrectBufferType = (this.buffer.isChannel() || this.buffer.isQuery());
185
+ let isSupported = !!this.buffer.getNetwork().ircClient.chathistory.isSupported();
186
+ return isCorrectBufferType && isSupported && this.buffer.flags.chathistory_available;
187
+ },
188
+ shouldRequestChannelKey() {
189
+ return this.buffer.getNetwork().state === 'connected' &&
190
+ this.buffer.isChannel() &&
191
+ this.buffer.flags.channel_badkey;
192
+ },
193
+ ourNick() {
194
+ return this.buffer ?
195
+ this.buffer.getNetwork().nick :
196
+ '';
197
+ },
198
+ filteredMessagesGroupedDay() {
199
+ // Group messages by day
200
+
201
+ const days = [];
202
+ let lastDay = null;
203
+ this.filteredMessages.forEach((message) => {
204
+ if (!lastDay || message.day_num !== lastDay) {
205
+ days.push({ dayNum: message.day_num, messages: [] });
206
+ lastDay = message.day_num;
207
+ }
208
+
209
+ days[days.length - 1].messages.push(message);
210
+ });
211
+
212
+ return days;
213
+ },
214
+ filteredMessages() {
215
+ return bufferTools.orderedMessages(this.buffer);
216
+ },
217
+ shouldShowJoiningLoader() {
218
+ return this.buffer.isChannel() &&
219
+ this.buffer.enabled &&
220
+ !this.buffer.joined &&
221
+ this.buffer.getNetwork().state === 'connected';
222
+ },
223
+ },
224
+ watch: {
225
+ filteredMessages() {
226
+ // Data has changed and now preparing to update the DOM.
227
+ // Check our scrolling state before the DOM updates so that we know if we're scrolled
228
+ // at the bottom before new messages are added
229
+ this.checkScrollingState();
230
+
231
+ // Wait until after the DOM has updated before possibly scrolling down based on the
232
+ // previous check
233
+ this.$nextTick(() => {
234
+ this.maybeScrollToBottom();
235
+ });
236
+ },
237
+ buffer(newBuffer, oldBuffer) {
238
+ if (oldBuffer) {
239
+ oldBuffer.isMessageTrimming = true;
240
+ }
241
+
242
+ if (!newBuffer) {
243
+ return;
244
+ }
245
+
246
+ this.message_info_open = null;
247
+
248
+ if (this.buffer.getNetwork().state === 'connected') {
249
+ newBuffer.flags.has_opened = true;
250
+ }
251
+
252
+ this.auto_scroll = true;
253
+ this.force_smooth_scroll = false;
254
+ this.$nextTick(() => {
255
+ this.scrollToBottom();
256
+ });
257
+ },
258
+ },
259
+ mounted() {
260
+ this.addCopyListeners();
261
+
262
+ this.$nextTick(() => {
263
+ this.scrollToBottom();
264
+ // this.smooth_scroll = true;
265
+ });
266
+
267
+ this.listen(this.$state, 'mediaviewer.opened', () => {
268
+ this.$nextTick(this.maybeScrollToBottom.apply(this));
269
+ });
270
+
271
+ this.listen(this.$state, 'messagelist.scrollto', (opt) => {
272
+ if (opt && opt.id) {
273
+ this.maybeScrollToId(opt.id);
274
+ }
275
+ });
276
+ },
277
+ methods: {
278
+ isTemplateVue(template) {
279
+ const isVue = template instanceof Vue;
280
+ if (isVue && !window.kiwi_deprecations_messageTemplate) {
281
+ window.kiwi_deprecations_messageTemplate = true;
282
+ // eslint-disable-next-line no-console
283
+ console.warn('deprecated message.template or message.bodyTemplate, please use `message.template = kiwi.Vue.extend(component object)`');
284
+ }
285
+ return isVue;
286
+ },
287
+ isHoveringOverMessage(message) {
288
+ return message.nick && message.nick.toLowerCase() === this.hover_nick.toLowerCase();
289
+ },
290
+ toggleMessageInfo(message) {
291
+ if (!message) {
292
+ this.message_info_open = null;
293
+ } else if (this.message_info_open === message) {
294
+ // It's already open, so don't do anything
295
+ } else if (this.canShowInfoForMessage(message)) {
296
+ // If in the process of selecting text, don't show the info box
297
+ let sel = window.getSelection();
298
+ if (sel.rangeCount > 0) {
299
+ let range = sel.getRangeAt(0);
300
+ if (range && !range.collapsed) {
301
+ return;
302
+ }
303
+ }
304
+
305
+ this.message_info_open = message;
306
+ this.$nextTick(() => this.maybeScrollToId(message.id));
307
+ }
308
+ },
309
+ shouldShowUnreadMarker(message) {
310
+ let idx = this.filteredMessages.indexOf(message);
311
+ let previous = this.filteredMessages[idx - 1];
312
+ let current = this.filteredMessages[idx];
313
+ let lastRead = this.buffer.last_read;
314
+
315
+ if (!lastRead) {
316
+ return false;
317
+ }
318
+
319
+ if (!current) {
320
+ return false;
321
+ }
322
+
323
+ // If the last message has been read, and this message not read
324
+ if (previous && previous.time < lastRead && current.time > lastRead) {
325
+ return true;
326
+ }
327
+
328
+ return false;
329
+ },
330
+ shouldShowDateChangeMarker(idx) {
331
+ let previous = this.filteredMessages[idx - 1];
332
+ let current = this.filteredMessages[idx];
333
+
334
+ if (!previous && (new Date(current.time)).getDay() !== (new Date()).getDay()) {
335
+ // The first message of the lsit and it's not today
336
+ return true;
337
+ } else if (!previous) {
338
+ // The first message of the lsit but it's today
339
+ return false;
340
+ }
341
+
342
+ // If the last message has been read, and this message not read
343
+ if ((new Date(previous.time)).getDay() !== (new Date(current.time)).getDay()) {
344
+ return true;
345
+ }
346
+
347
+ return false;
348
+ },
349
+ canShowInfoForMessage(message) {
350
+ let showInfoForTypes = ['privmsg', 'notice', 'action'];
351
+ return showInfoForTypes.indexOf(message.type) > -1;
352
+ },
353
+ bufferSetting(key) {
354
+ return this.buffer.setting(key);
355
+ },
356
+ formatTime(time) {
357
+ return strftime(this.buffer.setting('timestamp_format') || '%T', new Date(time));
358
+ },
359
+ formatTimeFull(time) {
360
+ let format = this.buffer.setting('timestamp_full_format');
361
+ return format ?
362
+ strftime(format, new Date(time)) :
363
+ (new Date(time)).toLocaleString();
364
+ },
365
+ formatMessage(message) {
366
+ return message.toHtml(this);
367
+ },
368
+ isMessageHighlight(message) {
369
+ // Highlighting ourselves when we join or leave a channel is silly
370
+ if (message.type === 'traffic') {
371
+ return false;
372
+ }
373
+
374
+ return message.isHighlight;
375
+ },
376
+ userColour(user) {
377
+ if (user && this.bufferSetting('colour_nicknames_in_messages')) {
378
+ return user.getColour();
379
+ }
380
+ return '';
381
+ },
382
+ openUserBox(nick) {
383
+ let user = this.$state.getUser(this.buffer.networkid, nick);
384
+ if (user) {
385
+ this.$state.$emit('userbox.show', user, {
386
+ buffer: this.buffer,
387
+ });
388
+ }
389
+ },
390
+ onListClick(event) {
391
+ this.toggleMessageInfo();
392
+ },
393
+ onMessageDblClick(event, message) {
394
+ clearTimeout(this.messageClickTmr);
395
+
396
+ let dataNick = event.target.getAttribute('data-nick');
397
+ if (!dataNick) {
398
+ return;
399
+ }
400
+
401
+ let network = this.buffer.getNetwork();
402
+ let user = network.userByName(dataNick);
403
+ // The user might have left use dataNick as fallback
404
+ let nick = (user && user.nick) ?
405
+ user.nick :
406
+ dataNick;
407
+ this.$state.$emit('input.insertnick', nick);
408
+ },
409
+ onMessageClick(event, message, delay) {
410
+ // Delaying the click for 200ms allows us to check for a second click. ie. double click
411
+ // Quick hack as we only need double click for nicks, nothing else
412
+ if (delay && event.target.getAttribute('data-nick')) {
413
+ clearTimeout(this.messageClickTmr);
414
+ this.messageClickTmr = setTimeout(this.onMessageClick, 200, event, message, false);
415
+ return;
416
+ }
417
+
418
+ let isLink = event.target.tagName === 'A';
419
+
420
+ let channelName = event.target.getAttribute('data-channel-name');
421
+ if (channelName && isLink) {
422
+ let network = this.buffer.getNetwork();
423
+ this.$state.addBuffer(this.buffer.networkid, channelName);
424
+ network.ircClient.join(channelName);
425
+ this.$state.setActiveBuffer(this.buffer.networkid, channelName);
426
+ return;
427
+ }
428
+
429
+ let userNick = event.target.getAttribute('data-nick');
430
+ if (userNick && isLink) {
431
+ this.openUserBox(userNick);
432
+ return;
433
+ }
434
+
435
+ let url = event.target.getAttribute('data-url');
436
+ if (url && isLink) {
437
+ if (this.$state.setting('buffers.inline_link_auto_previews')) {
438
+ message.embed.type = 'url';
439
+ message.embed.payload = url;
440
+ } else {
441
+ this.$state.$emit('mediaviewer.show', url);
442
+ }
443
+ }
444
+
445
+ if (this.message_info_open && this.message_info_open !== message) {
446
+ // Clicking on another message while another info is open, just close the info
447
+ this.toggleMessageInfo();
448
+ event.preventDefault();
449
+ return;
450
+ }
451
+
452
+ if (this.$state.ui.is_touch && this.$state.setting('buffers.show_message_info')) {
453
+ if (this.canShowInfoForMessage(message) && event.target.nodeName === 'A') {
454
+ // We show message info boxes on touch screen devices so that the user has an
455
+ // option to preview the links or do other stuff.
456
+ event.preventDefault();
457
+ }
458
+
459
+ this.toggleMessageInfo(message);
460
+ }
461
+ },
462
+ checkScrollingState() {
463
+ let el = this.$el;
464
+ let scrolledUpByPx = el.scrollHeight - (el.offsetHeight + el.scrollTop);
465
+
466
+ // We need to know at this point (before the DOM has updated with new messages) if we
467
+ // are at the bottom of the messagelist or not, otherwise once the DOM has updated then
468
+ // it is too late to determine if we should auto scroll down
469
+ if (scrolledUpByPx > BOTTOM_SCROLL_MARGIN) {
470
+ this.auto_scroll = false;
471
+ this.buffer.isMessageTrimming = false;
472
+ } else {
473
+ this.auto_scroll = true;
474
+ this.buffer.isMessageTrimming = true;
475
+ }
476
+
477
+ if (this.force_smooth_scroll !== null) {
478
+ this.smooth_scroll = this.force_smooth_scroll;
479
+ this.force_smooth_scroll = null;
480
+ // TODO: Enabling smooth_scroll breaks the auto-scroll-to-bottom on fast buffers as
481
+ // it takes time to scroll down and it looks like we're scrolled too far up when
482
+ // detecting if were scrolled up or not. Look into ways around this so that we
483
+ // can enable it as it does look a lot better.
484
+ // } else if (scrolledUpByPx < BOTTOM_SCROLL_MARGIN) {
485
+ // this.smooth_scroll = true;
486
+ } else {
487
+ this.smooth_scroll = false;
488
+ }
489
+ },
490
+ onListResize(e) {
491
+ // The messagelist or interface has resized or had new content added
492
+ // check if we should auto scroll down to the bottom
493
+ this.maybeScrollToBottom();
494
+ },
495
+ scrollToBottom() {
496
+ this.$el.scrollTop = this.$el.scrollHeight;
497
+ },
498
+ maybeScrollToBottom() {
499
+ if (this.auto_scroll) {
500
+ this.scrollToBottom();
501
+ }
502
+ },
503
+ maybeScrollToId(id, position = 'middle') {
504
+ let msgEl = this.$el.querySelector('.kiwi-messagelist-message[data-message-id="' + id + '"]');
505
+ if (!msgEl) {
506
+ return;
507
+ }
508
+
509
+ let newTop = 0;
510
+ if (position === 'top') {
511
+ // There maybe a sticky unread marker at the top
512
+ newTop = msgEl.offsetTop;
513
+ } else if (position === 'bottom') {
514
+ newTop = Math.floor(
515
+ msgEl.offsetTop - this.$refs.scroller.offsetHeight + msgEl.offsetHeight
516
+ );
517
+ } else {
518
+ newTop = Math.floor(
519
+ msgEl.offsetTop - ((this.$refs.scroller.offsetHeight - msgEl.offsetHeight) / 2)
520
+ );
521
+ }
522
+
523
+ this.auto_scroll = false;
524
+ this.$refs.scroller.scrollTo({ top: newTop, behavior: 'smooth' });
525
+ },
526
+ getSelectedMessages() {
527
+ let sel = document.getSelection();
528
+ let r = sel.getRangeAt(0);
529
+ let messageEls = [...this.$el.querySelectorAll('.kiwi-messagelist-message')];
530
+ let selectedMessageEls = messageEls.filter((el) => r.intersectsNode(el));
531
+ return selectedMessageEls;
532
+ },
533
+ restrictTextSelection() { // Prevents the selection cursor escaping the message list.
534
+ document.querySelector('body').classList.add('kiwi-unselectable');
535
+ this.$el.style.userSelect = 'text';
536
+ },
537
+ unrestrictTextSelection() { // Allows all page elements to be selected again.
538
+ document.querySelector('body').classList.remove('kiwi-unselectable');
539
+ this.$el.style.userSelect = 'auto';
540
+ },
541
+ removeSelections(removeNative = false) {
542
+ this.selectedMessages = Object.create(null);
543
+
544
+ let selection = document.getSelection();
545
+ if (removeNative && selection) {
546
+ // stops the native browser selection being left behind after ctrl+c
547
+ selection.removeAllRanges();
548
+ }
549
+ },
550
+ addCopyListeners() { // Better copy pasting
551
+ const LogFormatter = (msg) => {
552
+ let text = '';
553
+
554
+ switch (msg.type) {
555
+ case 'privmsg':
556
+ text = `<${msg.nick}> ${msg.message}`;
557
+ break;
558
+ case 'nick':
559
+ case 'mode':
560
+ case 'action':
561
+ case 'traffic':
562
+ text = `${msg.message}`;
563
+ break;
564
+ default:
565
+ text = msg.message;
566
+ }
567
+ if (text.length) {
568
+ return `[${(new Date(msg.time)).toLocaleTimeString({ hour: '2-digit', minute: '2-digit', second: '2-digit' })}] ${text}`;
569
+ }
570
+ return null;
571
+ };
572
+
573
+ let copyData = '';
574
+ let selecting = false;
575
+ let selectionChangeOff = null;
576
+
577
+ this.listen(document, 'selectstart', (e) => {
578
+ if (!this.$el.contains(e.target)) {
579
+ // Selected elsewhere on the page
580
+ copyData = '';
581
+ this.removeSelections();
582
+ return;
583
+ }
584
+
585
+ this.removeSelections();
586
+ selectionChangeOff = this.listen(document, 'selectionchange', onSelectionChange);
587
+ });
588
+
589
+ this.listen(document, 'mouseup', (e) => {
590
+ selectionChangeOff && selectionChangeOff();
591
+ this.unrestrictTextSelection();
592
+ if (selecting) {
593
+ e.preventDefault();
594
+ }
595
+ selecting = false;
596
+ });
597
+
598
+ let onSelectionChange = (e) => {
599
+ if (!this.$el) {
600
+ return true;
601
+ }
602
+
603
+ copyData = ''; // Store the text data to be copied in this.
604
+
605
+ let selection = document.getSelection();
606
+
607
+ if (!selection
608
+ || !selection.anchorNode
609
+ || !selection.anchorNode.parentNode.closest('.' + this.$el.className)) {
610
+ this.unrestrictTextSelection();
611
+ this.removeSelections();
612
+ return true;
613
+ }
614
+
615
+ this.removeSelections();
616
+ // Prevent the selection escaping the message list
617
+ this.restrictTextSelection();
618
+ if (selection.rangeCount > 0) {
619
+ selecting = true;
620
+
621
+ let selectedMesssageEls = this.getSelectedMessages();
622
+ let selectedMessages = [];
623
+ selectedMesssageEls.forEach((el) => {
624
+ let m = this.buffer.messagesObj.messageIds[el.dataset.messageId];
625
+ if (m) {
626
+ selectedMessages.push(m);
627
+ }
628
+ });
629
+
630
+ // If only 1 message is selected then treat the selection as native text
631
+ // selection. Most likely copying part of a message only.
632
+ if (selectedMessages.length === 1) {
633
+ selectedMessages = [];
634
+ }
635
+
636
+ this.selectedMessages = Object.create(null);
637
+ selectedMessages.forEach((m) => {
638
+ this.selectedMessages[m.id] = m;
639
+ });
640
+
641
+ // Iterate through the selected messages, format and store as a
642
+ // string to be used in the copy handler
643
+ copyData = selectedMessages
644
+ .sort((a, b) => (a.time > b.time ? 1 : -1))
645
+ .filter((m) => m.message.trim().length)
646
+ .map(LogFormatter)
647
+ .join('\r\n');
648
+ } else {
649
+ this.unrestrictTextSelection();
650
+ }
651
+ return false;
652
+ };
653
+
654
+ this.listen(document, 'copy', (e) => {
655
+ if (!copyData || !copyData.length) { // Just do a normal copy if no special data
656
+ return true;
657
+ }
658
+
659
+ if (navigator.clipboard) { // Supports Clipboard API
660
+ navigator.clipboard.writeText(copyData);
661
+ } else {
662
+ let input = document.createElement('textarea');
663
+ document.body.appendChild(input);
664
+ input.innerHTML = copyData;
665
+ input.select();
666
+ document.execCommand('copy');
667
+ document.body.removeChild(input);
668
+ }
669
+
670
+ return true;
671
+ });
672
+ },
673
+ // Move a messages embeded content to the main media preview
674
+ openEmbedInPreview(message) {
675
+ // First open the embed in the main media preview
676
+ let embed = message.embed;
677
+ if (embed.type === 'url') {
678
+ this.$state.$emit('mediaviewer.show', embed.payload);
679
+ } else if (embed.type === 'component') {
680
+ this.$state.$emit('mediaviewer.show', {
681
+ component: embed.payload,
682
+ });
683
+ }
684
+
685
+ // Remove the embed from the message
686
+ embed.payload = null;
687
+ },
688
+ },
689
+ };
690
+ </script>
691
+
692
+ <style lang="less">
693
+
694
+ .kiwi-unselectable * {
695
+ -webkit-touch-callout: none;
696
+ -webkit-user-select: none;
697
+ -moz-user-select: none;
698
+ -ms-user-select: none;
699
+ user-select: none;
700
+ }
701
+
702
+ div.kiwi-messagelist-item.kiwi-messagelist-item--selected {
703
+ border-left: 7px solid var(--brand-primary);
704
+ transform: translateX(20px);
705
+ transition: transform 0.1s;
706
+ }
707
+
708
+ div.kiwi-messagelist-item.kiwi-messagelist-item--selected .kiwi-messagelist-message {
709
+ border-left-width: 0;
710
+ }
711
+
712
+ .kiwi-messagelist-item.kiwi-messagelist-item--selected .kiwi-messagelist-message *::selection {
713
+ background-color: unset;
714
+ color: unset;
715
+ }
716
+
717
+ .kiwi-unselectable .kiwi-messagelist-scrollback {
718
+ -webkit-touch-callout: none;
719
+ -webkit-user-select: none;
720
+ -moz-user-select: none;
721
+ -ms-user-select: none;
722
+ user-select: none;
723
+ }
724
+
725
+ .kiwi-messagelist {
726
+ overflow-y: auto;
727
+ overflow-x: hidden;
728
+ box-sizing: border-box;
729
+ margin-bottom: 25px;
730
+ position: relative;
731
+ }
732
+
733
+ .kiwi-messagelist--smoothscroll {
734
+ scroll-behavior: smooth;
735
+ }
736
+
737
+ .kiwi-messagelist * {
738
+ user-select: text;
739
+ }
740
+
741
+ .kiwi-messagelist::-webkit-scrollbar-track {
742
+ border-radius: 10px;
743
+ background: transparent;
744
+ }
745
+
746
+ .kiwi-messagelist::-webkit-scrollbar {
747
+ width: 8px;
748
+ background: transparent;
749
+ }
750
+
751
+ .kiwi-messagelist::-webkit-scrollbar-thumb {
752
+ border-radius: 3px;
753
+ }
754
+
755
+ .kiwi-messagelist-item {
756
+ /* Allow child elements to make use of margins+padding within messagelist items */
757
+ overflow: hidden;
758
+ }
759
+
760
+ .kiwi-messagelist-message {
761
+ padding: 0 10px;
762
+
763
+ /* some message highlights add a left border so add a default invisble one in preperation */
764
+ border-left: 3px solid transparent;
765
+ overflow: hidden;
766
+ line-height: 1.5em;
767
+ margin: 0;
768
+ }
769
+
770
+ .kiwi-wrap--monospace .kiwi-messagelist-message,
771
+ .kiwi-messagelist-message.kiwi-messagelist-message-help {
772
+ font-family: Consolas, monaco, monospace;
773
+ font-size: 80%;
774
+ }
775
+
776
+ /* Remove the styling for none user messages, as they make the page look bloated */
777
+ .kiwi-messagelist-message-mode,
778
+ .kiwi-messagelist-message-traffic {
779
+ padding-top: 5px;
780
+ padding-bottom: 5px;
781
+ min-height: 0;
782
+ line-height: normal;
783
+ text-align: left;
784
+ }
785
+
786
+ /* Remove the min height from the message, as again, makes the page look bloated */
787
+ .kiwi-messagelist-body {
788
+ min-height: 0;
789
+ text-align: left;
790
+ line-height: 1.5em;
791
+ font-size: 1.05em;
792
+ margin: 0;
793
+ padding: 0;
794
+ }
795
+
796
+ /* Channel messages - e.g 'server on #testing22 ' message and such */
797
+ .kiwi-messagelist-message-mode,
798
+ .kiwi-messagelist-message-traffic,
799
+ .kiwi-messagelist-message-nick {
800
+ margin: 10px 0;
801
+ opacity: 0.85;
802
+ text-align: center;
803
+ border: none;
804
+
805
+ &:hover {
806
+ opacity: 1;
807
+ }
808
+ }
809
+
810
+ /* Absolute position the time on these messages so it's not above the message, it looks awful */
811
+ .kiwi-messagelist-message-mode .kiwi-messagelist-time,
812
+ .kiwi-messagelist-message-traffic .kiwi-messagelist-time {
813
+ position: absolute;
814
+ top: 1px;
815
+ right: 10px;
816
+ }
817
+
818
+ .kiwi-messagelist-message--authorrepeat {
819
+ border-top: none;
820
+ }
821
+
822
+ .kiwi-messagelist-message--authorrepeat .kiwi-messagelist-nick,
823
+ .kiwi-messagelist-message--authorrepeat .kiwi-messagelist-time {
824
+ /* Setting the opacity instead visible:none ensures it's still selectable when copying text */
825
+ opacity: 0;
826
+ cursor: default;
827
+ }
828
+
829
+ .kiwi-container--sidebar-drawn .kiwi-messagelist::after {
830
+ content: '';
831
+ z-index: 3;
832
+ left: 0;
833
+ top: 0;
834
+ width: 100%;
835
+ height: 100%;
836
+ opacity: 0.5;
837
+ position: fixed;
838
+ pointer-events: none;
839
+ }
840
+
841
+ .kiwi-container--sidebar-drawn.kiwi-container--no-sidebar .kiwi-messagelist::after {
842
+ width: 0;
843
+ height: 0;
844
+ display: none;
845
+ pointer-events: inherit;
846
+ position: static;
847
+ z-index: 0;
848
+ }
849
+
850
+ .kiwi-messagelist-scrollback {
851
+ text-align: center;
852
+ padding: 5px;
853
+ }
854
+
855
+ .kiwi-messagelist-seperator + .kiwi-messagelist-message {
856
+ border-top: none;
857
+ }
858
+
859
+ .kiwi-messagelist-message--blur {
860
+ opacity: 0.3;
861
+ }
862
+
863
+ .kiwi-messagelist-nick {
864
+ text-align: right;
865
+ font-weight: bold;
866
+ text-overflow: ellipsis;
867
+ overflow: hidden;
868
+ vertical-align: top;
869
+ cursor: pointer;
870
+ padding: 2px 4px;
871
+ word-break: break-all;
872
+ }
873
+
874
+ .kiwi-messagelist-message-traffic .kiwi-messagelist-nick {
875
+ display: none;
876
+ }
877
+
878
+ .kiwi-messagelist-seperator {
879
+ text-align: center;
880
+ display: block;
881
+ margin: 1em auto;
882
+ position: sticky;
883
+ top: -1px;
884
+ z-index: 1;
885
+ }
886
+
887
+ .kiwi-messagelist-seperator > span {
888
+ display: inline-block;
889
+ position: relative;
890
+ z-index: 1;
891
+ padding: 0 1em;
892
+ user-select: none;
893
+ }
894
+
895
+ /** Displaying an emoji in a message */
896
+ .kiwi-messagelist-emoji {
897
+ width: 1.3em;
898
+ display: inline-block;
899
+ vertical-align: middle;
900
+ }
901
+
902
+ @keyframes emojiIn {
903
+ 0% {
904
+ transform: scale(0);
905
+ }
906
+
907
+ 100% {
908
+ transform: scale(1);
909
+ }
910
+ }
911
+
912
+ .kiwi-messagelist-emoji--single {
913
+ animation: 0.1s ease-in-out 0s 1 emojiIn;
914
+ font-size: 2em;
915
+ }
916
+
917
+ /** Message structure */
918
+ .kiwi-messagelist-body .kiwi-nick {
919
+ cursor: pointer;
920
+ }
921
+
922
+ .kiwi-messagelist-nick:hover {
923
+ overflow: visible;
924
+ width: auto;
925
+ }
926
+
927
+ /* Topic changes */
928
+ .kiwi-messagelist-message-topic {
929
+ border-radius: 5px;
930
+ margin: 18px;
931
+ margin-left: 0;
932
+ padding: 5px;
933
+ text-align: center;
934
+ position: relative;
935
+ min-height: 0;
936
+ display: block;
937
+ }
938
+
939
+ .kiwi-messagelist-message-topic .kiwi-messagelist-body {
940
+ min-height: 0;
941
+ margin: 0;
942
+
943
+ &::before {
944
+ display: none;
945
+ }
946
+ }
947
+
948
+ .kiwi-messagelist-message-topic.kiwi-messagelist-message-topic .kiwi-messagelist-time {
949
+ display: none;
950
+ }
951
+
952
+ .kiwi-messagelist-message-topic.kiwi-messagelist-message-topic .kiwi-messagelist-nick {
953
+ display: none;
954
+ }
955
+
956
+ /* Actions */
957
+ .kiwi-messagelist-message-action .kiwi-messagelist-message-body {
958
+ font-style: italic;
959
+ }
960
+
961
+ /* Traffic (joins, parts, quits, kicks) */
962
+ .kiwi-messagelist-message-traffic.kiwi-messagelist-message-traffic .kiwi-messagelist-nick {
963
+ display: none;
964
+ }
965
+
966
+ .kiwi-messagelist-message-traffic .kiwi-messagelist-body {
967
+ font-style: italic;
968
+ }
969
+
970
+ .kiwi-messagelist-message-action.kiwi-messagelist-message-action .kiwi-messagelist-nick {
971
+ display: none;
972
+ }
973
+
974
+ /* MOTD */
975
+ .kiwi-messagelist-message-motd {
976
+ font-family: monospace;
977
+ }
978
+
979
+ .kiwi-messagelist-message.kiwi-messagelist-message--hover,
980
+ .kiwi-messagelist-message.kiwi-messagelist-message--highlight,
981
+ .kiwi-messagelist-message.kiwi-messagelist-message-traffic--hover {
982
+ position: relative;
983
+ }
984
+
985
+ /* Links */
986
+ .kiwi-messagelist-message-linkhandle {
987
+ margin-left: 4px;
988
+ font-size: 0.8em;
989
+ }
990
+
991
+ .kiwi-wrap--touch .kiwi-messagelist-message-linkhandle {
992
+ display: none;
993
+ }
994
+
995
+ .kiwi-messagelist-joinloader {
996
+ margin: 1em auto;
997
+ width: 100px;
998
+
999
+ /* the magic number below is the exact ratio of the kiwi logo height/width */
1000
+ height: calc (100px * 0.85987261146496815286624203821656);
1001
+ overflow: hidden;
1002
+ }
1003
+
1004
+ .kiwi-messagelist-joinloadertrans-enter,
1005
+ .kiwi-messagelist-joinloadertrans-leave-to {
1006
+ height: 0;
1007
+ opacity: 0;
1008
+ }
1009
+
1010
+ .kiwi-messagelist-joinloadertrans-enter-to,
1011
+ .kiwi-messagelist-joinloadertrans-leave {
1012
+ height: 150px;
1013
+ opacity: 1;
1014
+ }
1015
+
1016
+ .kiwi-messagelist-joinloadertrans-enter-active,
1017
+ .kiwi-messagelist-joinloadertrans-leave-active {
1018
+ transition: height 0.5s, opacity 0.5s;
1019
+ }
1020
+
1021
+ @media screen and (max-width: 700px) {
1022
+ .kiwi-messagelist-message,
1023
+ .kiwi-messageinfo {
1024
+ margin: 0;
1025
+ }
1026
+ }
1027
+
1028
+ </style>