serverless-ircd 0.6.0 → 0.7.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 (247) hide show
  1. package/.github/workflows/ci.yml +6 -1
  2. package/.gitmodules +3 -0
  3. package/CHANGELOG.md +214 -0
  4. package/README.md +110 -23
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/cf-tcp-container/package.json +1 -1
  7. package/apps/cf-worker/package.json +1 -1
  8. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  9. package/apps/cf-worker/src/worker.ts +89 -9
  10. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  11. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  12. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  13. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  14. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  15. package/apps/cf-worker/wrangler.test.toml +23 -0
  16. package/apps/cf-worker/wrangler.toml +54 -0
  17. package/apps/local-cli/package.json +1 -1
  18. package/apps/web/landing/index.html +217 -0
  19. package/apps/web/package.json +28 -0
  20. package/apps/web/scripts/build.mjs +125 -0
  21. package/apps/web/src/build-env.ts +43 -0
  22. package/apps/web/src/config-schema.ts +138 -0
  23. package/apps/web/static/config.json +28 -0
  24. package/apps/web/static/config.prod.json +28 -0
  25. package/apps/web/static/config.staging.json +28 -0
  26. package/apps/web/tests/build-env.test.ts +63 -0
  27. package/apps/web/tests/build-smoke.test.ts +103 -0
  28. package/apps/web/tests/config-schema.test.ts +432 -0
  29. package/apps/web/tests/workspace.test.ts +12 -0
  30. package/apps/web/tsconfig.json +10 -0
  31. package/apps/web/upstream/.browserslistrc +3 -0
  32. package/apps/web/upstream/.editorconfig +9 -0
  33. package/apps/web/upstream/.eslintignore +3 -0
  34. package/apps/web/upstream/.eslintrc.js +90 -0
  35. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  36. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  37. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  38. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  39. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  40. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  41. package/apps/web/upstream/.prettierrc.js +9 -0
  42. package/apps/web/upstream/.stylelintignore +5 -0
  43. package/apps/web/upstream/.stylelintrc.js +21 -0
  44. package/apps/web/upstream/LICENSE +201 -0
  45. package/apps/web/upstream/README.md +76 -0
  46. package/apps/web/upstream/babel.config.js +25 -0
  47. package/apps/web/upstream/crowdin.yml +3 -0
  48. package/apps/web/upstream/index.html +35 -0
  49. package/apps/web/upstream/jest.config.js +7 -0
  50. package/apps/web/upstream/jsdoc.json +22 -0
  51. package/apps/web/upstream/package.json +80 -0
  52. package/apps/web/upstream/scripts/pre-commit +43 -0
  53. package/apps/web/upstream/src/components/App.vue +516 -0
  54. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  55. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  56. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  57. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  58. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  59. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  60. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  61. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  62. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  63. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  64. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  65. package/apps/web/upstream/src/components/Container.vue +401 -0
  66. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  67. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  68. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  69. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  70. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  71. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  72. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  73. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  74. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  75. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  76. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  77. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  78. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  79. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  80. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  81. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  82. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  83. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  84. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  85. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  86. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  87. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  88. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  89. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  90. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  91. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  92. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  93. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  94. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  95. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  96. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  97. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  98. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  99. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  100. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  101. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  102. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  103. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  104. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  105. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  106. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  107. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  108. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  109. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  110. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  111. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  112. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  113. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  114. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  115. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  116. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  117. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  118. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  119. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  120. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  121. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  122. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  123. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  124. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  125. package/apps/web/upstream/src/libs/IPC.js +51 -0
  126. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  127. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  128. package/apps/web/upstream/src/libs/Logger.js +71 -0
  129. package/apps/web/upstream/src/libs/Message.js +169 -0
  130. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  131. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  132. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  133. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  134. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  135. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  136. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  137. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  138. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  139. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  140. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  141. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  142. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  143. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  144. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  145. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  146. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  147. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  148. package/apps/web/upstream/src/libs/state/common.js +26 -0
  149. package/apps/web/upstream/src/libs/state.js +961 -0
  150. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  151. package/apps/web/upstream/src/main.js +566 -0
  152. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  153. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  154. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  155. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  156. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  157. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  158. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  159. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  160. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  161. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  162. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  163. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  164. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  165. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  166. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  167. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  168. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  169. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  170. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  171. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  172. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  173. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  174. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  175. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  176. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  177. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  186. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  191. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  199. package/apps/web/upstream/src/res/localesList.json +27 -0
  200. package/apps/web/upstream/src/res/logo.png +0 -0
  201. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  202. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  203. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  204. package/apps/web/upstream/static/config.json +28 -0
  205. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  206. package/apps/web/upstream/static/favicon.png +0 -0
  207. package/apps/web/upstream/static/highlight.mp3 +0 -0
  208. package/apps/web/upstream/static/highlight.ogg +0 -0
  209. package/apps/web/upstream/static/locales/.gitignore +2 -0
  210. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  211. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  212. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  213. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  214. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  215. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  216. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  217. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  218. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  219. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  220. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  221. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  222. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  223. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  224. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  225. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  226. package/apps/web/upstream/vue.config.js +150 -0
  227. package/apps/web/upstream/yarn.lock +9767 -0
  228. package/apps/web/vitest.config.ts +13 -0
  229. package/biome.json +1 -0
  230. package/docs/Home.md +1 -0
  231. package/docs/WebClientGuide.md +535 -0
  232. package/package.json +2 -2
  233. package/packages/aws-adapter/package.json +1 -1
  234. package/packages/cf-adapter/package.json +1 -1
  235. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  236. package/packages/in-memory-runtime/package.json +1 -1
  237. package/packages/irc-core/package.json +1 -1
  238. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  239. package/packages/irc-core/tests/batch.test.ts +15 -0
  240. package/packages/irc-core/tests/numerics.test.ts +13 -0
  241. package/packages/irc-core/tests/parser.test.ts +109 -0
  242. package/packages/irc-server/package.json +1 -1
  243. package/packages/irc-test-support/package.json +1 -1
  244. package/packages/irc-test-support/src/scenarios.ts +24 -2
  245. package/tools/ci-hardening/package.json +1 -1
  246. package/tools/load-test/package.json +1 -1
  247. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,1016 @@
1
+ <template>
2
+ <div
3
+ :class="{
4
+ 'kiwi-controlinput--focus': has_focus,
5
+ 'kiwi-controlinput--show-send': shouldShowSendButton,
6
+ 'kiwi-controlinput--show-tools': shouldShowTools,
7
+ 'kiwi-controlinput--show-tools--inline': shouldShowToolsInline,
8
+ 'kiwi-controlinput--selfuser-open': selfuser_open,
9
+ }"
10
+ class="kiwi-controlinput kiwi-theme-bg"
11
+ >
12
+ <div class="kiwi-controlinput-selfuser">
13
+ <transition name="kiwi-selfuser-trans">
14
+ <self-user
15
+ v-if="networkState==='connected'
16
+ && selfuser_open === true"
17
+ :network="network"
18
+ @close="selfuser_open=false"
19
+ />
20
+ </transition>
21
+ </div>
22
+
23
+ <div class="kiwi-controlinput-inner">
24
+ <away-status-indicator
25
+ v-if="network && network.state === 'connected'"
26
+ :network="network"
27
+ :user="network.currentUser()"
28
+ />
29
+ <div v-if="currentNick" class="kiwi-controlinput-user" @click="toggleSelfUser">
30
+ <span class="kiwi-controlinput-user-nick">{{ currentNick }}</span>
31
+ <i
32
+ :class="[selfuser_open ? 'fa-caret-down' : 'fa-caret-up']"
33
+ class="fa"
34
+ aria-hidden="true"
35
+ />
36
+ </div>
37
+ <form
38
+ class="kiwi-controlinput-form"
39
+ @submit.prevent="submitForm"
40
+ @click="maybeHidePlugins"
41
+ >
42
+ <auto-complete
43
+ v-if="autocomplete_open"
44
+ ref="autocomplete"
45
+ :items="autocomplete_items"
46
+ :filter="autocomplete_filter"
47
+ :buffer="buffer"
48
+ @temp="onAutocompleteTemp"
49
+ @selected="onAutocompleteSelected"
50
+ @cancel="onAutocompleteCancel"
51
+ />
52
+ <typing-users-list v-if="buffer.setting('share_typing')" :buffer="buffer" />
53
+ <div class="kiwi-controlinput-input-wrap">
54
+ <irc-input
55
+ ref="input"
56
+ :placeholder="$t('input_placeholder')"
57
+ class="kiwi-controlinput-input"
58
+ wrap="off"
59
+ @input="inputUpdate"
60
+ @keydown="inputKeyDown($event)"
61
+ @keyup="inputKeyUp($event)"
62
+ @click="closeToolsPlugins"
63
+ @focus="focusChanged"
64
+ @blur="focusChanged"
65
+ />
66
+ </div>
67
+ <div
68
+ v-if="shouldShowSendButton"
69
+ class="kiwi-controlinput-send-container kiwi-controlinput-tools"
70
+ >
71
+ <button
72
+ ref="sendButton"
73
+ type="submit"
74
+ class="kiwi-controlinput-button kiwi-controlinput-send fa fa-paper-plane"
75
+ />
76
+ </div>
77
+ </form>
78
+
79
+ <div
80
+ v-if="shouldShowTools"
81
+ ref="plugins"
82
+ class="kiwi-controlinput-tools kiwi-controlinput-tools-wrapper"
83
+ >
84
+ <div
85
+ v-if="!shouldShowToolsInline"
86
+ class="kiwi-controlinput-tools-expand kiwi-controlinput-button"
87
+ :class="{'kiwi-controlinput-tools-expand--closed': !showPlugins}"
88
+ @click="showPlugins=!showPlugins"
89
+ >
90
+ <i class="fa fa-bars" aria-hidden="true" />
91
+ </div>
92
+ <transition name="kiwi-plugin-ui-trans">
93
+ <div
94
+ v-if="showPlugins || shouldShowToolsInline"
95
+ class="kiwi-controlinput-tools-container"
96
+ >
97
+ <div
98
+ v-if="shouldShowColorPicker"
99
+ class="kiwi-controlinput-button"
100
+ @click.prevent="onToolClickTextStyle"
101
+ >
102
+ <i class="fa fa-adjust" aria-hidden="true" />
103
+ </div>
104
+ <div
105
+ v-if="shouldShowEmojiPicker"
106
+ class="kiwi-controlinput-button"
107
+ @click.prevent="onToolClickEmoji"
108
+ >
109
+ <i class="fa fa-smile-o" aria-hidden="true" />
110
+ </div>
111
+ <component
112
+ :is="plugin.component"
113
+ v-for="plugin in pluginUiElements"
114
+ :key="plugin.id"
115
+ :plugin-props="{
116
+ buffer: buffer,
117
+ controlinput: self,
118
+ }"
119
+ v-bind="plugin.props"
120
+ :network="network"
121
+ :buffer="buffer"
122
+ class="kiwi-controlinput-button"
123
+ />
124
+ </div>
125
+ </transition>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="kiwi-controlinput-active-tool">
130
+ <component :is="active_tool" v-bind="active_tool_props" />
131
+ </div>
132
+ </div>
133
+ </template>
134
+
135
+ <script>
136
+ 'kiwi public';
137
+
138
+ import _ from 'lodash';
139
+ import * as TextFormatting from '@/helpers/TextFormatting';
140
+ import * as settingTools from '@/libs/settingTools';
141
+ import autocompleteCommands from '@/res/autocompleteCommands';
142
+ import GlobalApi from '@/libs/GlobalApi';
143
+ import * as EmojiProvider from '@/libs/EmojiProvider';
144
+ import AutoComplete from './AutoComplete';
145
+ import ToolTextStyle from './inputtools/TextStyle';
146
+ import ToolEmoji from './inputtools/Emoji';
147
+ import SelfUser from './SelfUser';
148
+ import AwayStatusIndicator from './AwayStatusIndicator';
149
+ import TypingUsersList from './TypingUsersList';
150
+
151
+ export default {
152
+ components: {
153
+ AutoComplete,
154
+ AwayStatusIndicator,
155
+ SelfUser,
156
+ TypingUsersList,
157
+ },
158
+ props: ['network', 'buffer', 'sidebarState'],
159
+ data() {
160
+ return {
161
+ self: this,
162
+ selfuser_open: false,
163
+ autocomplete_open: false,
164
+ autocomplete_items: [],
165
+ autocomplete_filter: '',
166
+ // Not filtering through the autocomplete list means that the entire word is put
167
+ // in place when cycling through items. Just as with traditional IRC clients when
168
+ // tabbing through nicks.
169
+ // When filtering through the list, we keep typing more of the word we want as the
170
+ // autocomplete list filters its results to show us the relevant items, not replacing
171
+ // the current word until we select an item.
172
+ autocomplete_filtering: true,
173
+ active_tool: null,
174
+ active_tool_props: {},
175
+ pluginUiElements: GlobalApi.singleton().controlInputPlugins,
176
+ showPlugins: false,
177
+ current_input_value: '',
178
+ has_focus: false,
179
+ keep_focus: false,
180
+ };
181
+ },
182
+ computed: {
183
+ currentNick() {
184
+ let activeNetwork = this.$state.getActiveNetwork();
185
+ return activeNetwork ?
186
+ activeNetwork.nick :
187
+ '';
188
+ },
189
+ networkState() {
190
+ let activeNetwork = this.$state.getActiveNetwork();
191
+ return activeNetwork ?
192
+ activeNetwork.state :
193
+ '';
194
+ },
195
+ shouldShowSendButton() {
196
+ return this.$state.ui.is_touch || this.$state.setting('showSendButton');
197
+ },
198
+ shouldShowEmojiPicker() {
199
+ return (
200
+ this.$state.setting('forceShowEmojiPicker') ||
201
+ (this.$state.setting('showEmojiPicker') && !this.$state.ui.is_touch)
202
+ );
203
+ },
204
+ shouldShowColorPicker() {
205
+ return this.$state.setting('showColorPicker');
206
+ },
207
+ shouldShowTools() {
208
+ if (
209
+ this.pluginUiElements.length ||
210
+ this.shouldShowEmojiPicker ||
211
+ this.shouldShowColorPicker
212
+ ) {
213
+ return true;
214
+ }
215
+ return false;
216
+ },
217
+ shouldShowToolsInline() {
218
+ let toolCount = this.pluginUiElements.length;
219
+ if (this.shouldShowEmojiPicker) {
220
+ toolCount++;
221
+ }
222
+ if (this.shouldShowColorPicker) {
223
+ toolCount++;
224
+ }
225
+
226
+ if (toolCount === 1) {
227
+ // No point showing a menu button to replace one item
228
+ return true;
229
+ }
230
+
231
+ // Button size (36px)
232
+ // Total buttons width < 1/5 screen width
233
+ return (36 * toolCount < this.$state.ui.app_width / 5);
234
+ },
235
+ history() {
236
+ if (this.$state.setting('buffers.shared_input')) {
237
+ return this.$state.ui.input_history;
238
+ }
239
+ return this.buffer.input_history;
240
+ },
241
+ history_pos: {
242
+ get() {
243
+ if (this.$state.setting('buffers.shared_input')) {
244
+ return this.$state.ui.input_history_pos;
245
+ }
246
+ return this.buffer.input_history_pos;
247
+ },
248
+ set(newVal) {
249
+ if (this.$state.setting('buffers.shared_input')) {
250
+ this.$state.ui.input_history_pos = newVal;
251
+ } else {
252
+ this.buffer.input_history_pos = newVal;
253
+ }
254
+ },
255
+ },
256
+ },
257
+ watch: {
258
+ history_pos(newVal) {
259
+ let val = this.history[this.history_pos];
260
+ this.$refs.input.setValue(val || '');
261
+ },
262
+ buffer() {
263
+ if (!this.$state.setting('buffers.shared_input')) {
264
+ this.inputRestore();
265
+ }
266
+
267
+ this.autocomplete_open = false;
268
+ },
269
+ },
270
+ created() {
271
+ this.typingTimer = null;
272
+ this.lastTypingTime = 0;
273
+
274
+ this.listen(this.$state, 'document.keydown', (ev) => {
275
+ // No input box currently? Nothing to shift focus to
276
+ if (!this.$refs.input) {
277
+ return;
278
+ }
279
+
280
+ // If we're copying text, don't shift focus
281
+ if (ev.ctrlKey || ev.altKey || ev.metaKey) {
282
+ return;
283
+ }
284
+
285
+ // shift key on its own, don't shift focus we handle this below
286
+ if (ev.keyCode === 16) {
287
+ return;
288
+ }
289
+
290
+ // Firefox 66.0.3 on linux isn't consistently setting ev.ctrlKey === true when only
291
+ // the control key is pressed so add a specific check for this
292
+ // TODO: Remove this check once ff 66.0.3 is no longer around
293
+ if (ev.keyCode === 17) {
294
+ return;
295
+ }
296
+
297
+ // If we are using shift and arrow keys, don't shift focus
298
+ // this allows users to adjust text selection
299
+ let arrowKeyCodes = [37, 38, 39, 40];
300
+ if (ev.shiftKey && arrowKeyCodes.indexOf(ev.keyCode) !== -1) {
301
+ return;
302
+ }
303
+
304
+ // If we're typing into an input box somewhere, ignore
305
+ let elements = ['input', 'select', 'textarea', 'button', 'datalist', 'keygen'];
306
+ let doNotRefocus =
307
+ elements.indexOf(ev.target.tagName.toLowerCase()) > -1 ||
308
+ ev.target.getAttribute('contenteditable');
309
+
310
+ if (doNotRefocus) {
311
+ return;
312
+ }
313
+
314
+ this.$refs.input.focus();
315
+ });
316
+
317
+ this.listen(this.$state, 'input.insertnick', (nick) => {
318
+ if (!this.$refs.input) {
319
+ return;
320
+ }
321
+
322
+ let val = nick;
323
+ if (this.current_input_value === '') {
324
+ val += ': ';
325
+ } else {
326
+ val += ' ';
327
+ }
328
+
329
+ this.$refs.input.insertText(val);
330
+ });
331
+
332
+ this.listen(this.$state, 'input.tool', (toolComponent) => {
333
+ this.toggleInputTool(toolComponent);
334
+ });
335
+ },
336
+ mounted() {
337
+ this.inputRestore();
338
+ },
339
+ methods: {
340
+ inputUpdate(val) {
341
+ this.current_input_value = val;
342
+
343
+ if (this.$state.setting('buffers.shared_input')) {
344
+ this.$state.ui.current_input = val;
345
+ } else {
346
+ this.buffer.current_input = val;
347
+ }
348
+
349
+ this.maybeHidePlugins();
350
+ },
351
+ inputRestore() {
352
+ let currentInput = this.$state.setting('buffers.shared_input') ?
353
+ this.$state.ui.current_input :
354
+ this.buffer.current_input;
355
+
356
+ this.$refs.input.reset(currentInput, this.keep_focus);
357
+ this.$refs.input.selectionToEnd();
358
+ },
359
+ toggleSelfUser() {
360
+ if (this.networkState === 'connected') {
361
+ this.selfuser_open = !this.selfuser_open;
362
+ }
363
+ },
364
+ maybeHidePlugins() {
365
+ // Save some space if we're typing on a small screen
366
+ if (this.$state.ui.app_width < 500) {
367
+ this.showPlugins = false;
368
+ }
369
+ },
370
+ onToolClickTextStyle() {
371
+ this.toggleInputTool(ToolTextStyle);
372
+ },
373
+ onToolClickEmoji() {
374
+ this.toggleInputTool(ToolEmoji);
375
+ },
376
+ closeToolsPlugins() {
377
+ this.showPlugins = false;
378
+ this.closeInputTool();
379
+ },
380
+ closeInputTool() {
381
+ this.active_tool = null;
382
+ },
383
+ toggleInputTool(tool) {
384
+ if (!tool || this.active_tool === tool) {
385
+ this.active_tool = null;
386
+ } else {
387
+ this.active_tool_props = {
388
+ buffer: this.buffer,
389
+ ircinput: this.$refs.input,
390
+ };
391
+ this.active_tool = tool;
392
+ }
393
+ },
394
+ toggleBold() {
395
+ this.$refs.input.toggleBold();
396
+ },
397
+ toggleItalic() {
398
+ this.$refs.input.toggleItalic();
399
+ },
400
+ toggleUnderline() {
401
+ this.$refs.input.toggleUnderline();
402
+ },
403
+ onAutocompleteCancel() {
404
+ this.autocomplete_open = false;
405
+ },
406
+ onAutocompleteTemp(selectedValue, selectedItem) {
407
+ if (!this.autocomplete_filtering) {
408
+ this.$refs.input.setCurrentWord(selectedValue);
409
+ }
410
+ },
411
+ onAutocompleteSelected(selectedValue, selectedItem) {
412
+ let word = selectedValue;
413
+ if (word.length > 0) {
414
+ this.$refs.input.setCurrentWord(word);
415
+ }
416
+ this.autocomplete_open = false;
417
+ },
418
+ inputKeyDown(event) {
419
+ let meta = false;
420
+
421
+ if (navigator.appVersion.indexOf('Mac') !== -1) {
422
+ meta = event.metaKey;
423
+ } else {
424
+ meta = event.ctrlKey;
425
+ }
426
+
427
+ // If autocomplete has handled the event, don't also handle it here
428
+ if (this.autocomplete_open && this.$refs.autocomplete.handleOnKeyDown(event)) {
429
+ return;
430
+ }
431
+
432
+ // When not filtering, select the current autocomplete item so that we can type any
433
+ // character directly after a nick
434
+ if (this.autocomplete_open && !this.autocomplete_filtering) {
435
+ this.$refs.autocomplete.selectCurrentItem();
436
+ }
437
+
438
+ if (event.keyCode === 13 && (
439
+ (event.altKey && !event.shiftKey && !event.metaKey && !event.ctrlKey) ||
440
+ (event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey)
441
+ )) {
442
+ // Add new line when shift+enter or alt+enter is pressed
443
+ event.preventDefault();
444
+ this.$refs.input.insertText('\n');
445
+ } else if (event.keyCode === 13) {
446
+ // Send message when enter is pressed
447
+ event.preventDefault();
448
+ this.submitForm();
449
+ } else if (event.keyCode === 32) {
450
+ // Hitting space after just typing an ascii emoji will get it replaced with
451
+ // its image
452
+ if (this.$state.setting('buffers.show_emoticons')) {
453
+ let currentWord = this.$refs.input.getCurrentWord(true);
454
+ let emojis = EmojiProvider.getEmojis(currentWord.word);
455
+ if (emojis.length) {
456
+ event.preventDefault();
457
+ this.$refs.input.setCurrentWord('', false, true);
458
+ this.$refs.input.addImg(
459
+ emojis[0].ascii,
460
+ emojis[0].url,
461
+ emojis[0].imgProps,
462
+ );
463
+ }
464
+ }
465
+ } else if (event.keyCode === 38) {
466
+ // Up
467
+ if (this.$refs.input.getCaretIdx() > 0) {
468
+ // not at the start of input, allow normal input behaviour
469
+ return;
470
+ }
471
+
472
+ event.preventDefault();
473
+ this.historyBack();
474
+ } else if (event.keyCode === 40) {
475
+ // Down
476
+ let end = this.$refs.input.getRawText().replace(/\r?\n/g, '').length;
477
+ if (this.$refs.input.getCaretIdx() < end) {
478
+ // not at the end of input, allow normal input behaviour
479
+ return;
480
+ }
481
+ event.preventDefault();
482
+ this.historyForward();
483
+ this.$nextTick(() => {
484
+ this.$refs.input.selectionToEnd();
485
+ });
486
+ } else if (
487
+ event.keyCode === 9
488
+ && !event.shiftKey
489
+ && !event.altKey
490
+ && !event.metaKey
491
+ && !event.ctrlKey
492
+ ) {
493
+ // Tab and no other keys as tab+other is often a keyboard shortcut
494
+ // Tab key was just pressed, start general auto completion
495
+ let currentWord = this.$refs.input.getCurrentWord();
496
+ let currentToken = currentWord.word.substr(0, currentWord.position);
497
+ let inputText = this.$refs.input.getRawText();
498
+
499
+ let items = [];
500
+ if (inputText.indexOf('/set') === 0) {
501
+ items = this.buildAutoCompleteItems({ settings: true });
502
+ } else {
503
+ items = this.buildAutoCompleteItems({
504
+ users: true,
505
+ buffers: true,
506
+ });
507
+ }
508
+
509
+ this.openAutoComplete(items);
510
+ this.autocomplete_filter = currentToken;
511
+
512
+ // Disable filtering so that tabbing cycles through words more like
513
+ // traditional IRC clients.
514
+ this.autocomplete_filtering = false;
515
+ event.preventDefault();
516
+ } else if (meta && event.keyCode === 75) {
517
+ // meta + k
518
+ this.toggleInputTool(ToolTextStyle);
519
+ event.preventDefault();
520
+ } else if (meta && event.keyCode === 66) {
521
+ // meta + b
522
+ this.toggleBold();
523
+ event.preventDefault();
524
+ } else if (meta && event.keyCode === 73) {
525
+ // meta + i
526
+ this.toggleItalic();
527
+ event.preventDefault();
528
+ } else if (meta && event.keyCode === 85) {
529
+ // meta + u
530
+ this.toggleUnderline();
531
+ event.preventDefault();
532
+ }
533
+ },
534
+ inputKeyUp(event) {
535
+ let inputVal = this.$refs.input.getRawText();
536
+ let currentWord = this.$refs.input.getCurrentWord();
537
+ let currentToken = currentWord.word.substr(0, currentWord.position);
538
+ let autocompleteTokens = this.$state.setting('autocompleteTokens');
539
+
540
+ if (event.keyCode === 27 && this.autocomplete_open) {
541
+ this.autocomplete_open = false;
542
+ } else if (this.autocomplete_open && currentToken === '') {
543
+ this.autocomplete_open = false;
544
+ } else if (this.autocomplete_open) {
545
+ // @ is a shortcut to open the nicklist autocomplete. It's not part
546
+ // of the nick so strip it out before passing currentToken to the
547
+ // filter.
548
+ if (currentToken[0] === '@') {
549
+ currentToken = currentToken.substr(1);
550
+ }
551
+ } else if (currentToken === '@' && autocompleteTokens.includes('@')) {
552
+ // Just typed @ so start the nick auto completion
553
+ this.openAutoComplete(this.buildAutoCompleteItems({ users: true }));
554
+ this.autocomplete_filtering = true;
555
+ } else if (inputVal === '/' && autocompleteTokens.includes('/')) {
556
+ // Just typed / so start the command auto completion
557
+ this.openAutoComplete(this.buildAutoCompleteItems({ commands: true }));
558
+ this.autocomplete_filtering = true;
559
+ } else if (currentToken === '#' && autocompleteTokens.includes('#')) {
560
+ // Just typed # so start the command auto completion
561
+ this.openAutoComplete(this.buildAutoCompleteItems({ buffers: true }));
562
+ this.autocomplete_filtering = true;
563
+ } else if (
564
+ event.keyCode === 9
565
+ && !event.shiftKey
566
+ && !event.altKey
567
+ && !event.metaKey
568
+ && !event.ctrlKey
569
+ ) {
570
+ // Tab and no other keys as tab+other is often a keyboard shortcut
571
+ event.preventDefault();
572
+ } else if (!event.key.match(/^(Shift|Control|Alt|Enter)/)) {
573
+ if (inputVal[0] === '/') {
574
+ // Don't send typing status for commands
575
+ return;
576
+ }
577
+
578
+ if (inputVal.trim()) {
579
+ this.startTyping();
580
+ } else {
581
+ this.stopTyping(true);
582
+ }
583
+ }
584
+
585
+ if (this.autocomplete_open && this.autocomplete_filtering) {
586
+ this.autocomplete_filter = currentToken;
587
+ }
588
+ },
589
+ submitForm() {
590
+ let rawInput = this.$refs.input.getValue();
591
+ if (!rawInput) {
592
+ if (!this.has_focus && this.keep_focus) {
593
+ // Maybe triggered by the send button on empty input,
594
+ // put focus back into the input
595
+ this.$refs.input.focus();
596
+ }
597
+ return;
598
+ }
599
+
600
+ let ircText = this.$refs.input.buildIrcText();
601
+ this.$state.$emit('input.raw', ircText);
602
+
603
+ this.historyAdd(rawInput);
604
+
605
+ this.$refs.input.reset('', this.keep_focus);
606
+
607
+ this.stopTyping(false);
608
+ },
609
+ historyAdd(rawInput) {
610
+ // Add to history, keeping the history trimmed to the last 50 entries
611
+ this.history.push(rawInput);
612
+ this.history.splice(0, this.history.length - 50);
613
+ this.history_pos = this.history.length;
614
+ },
615
+ historyBack() {
616
+ let rawText = this.$refs.input.getRawText();
617
+ let rawInput = this.$refs.input.getValue();
618
+ if (rawText.trim() && this.history_pos === this.history.length) {
619
+ this.historyAdd(rawInput);
620
+ this.history_pos--;
621
+ }
622
+ if (this.history_pos > 0) {
623
+ this.history_pos--;
624
+ }
625
+ },
626
+ historyForward() {
627
+ // Purposely let history_pos go 1 index beyond the history length
628
+ // so that we can detect if we're not currently using a history value
629
+ if (this.history_pos < this.history.length) {
630
+ this.history_pos++;
631
+ }
632
+ },
633
+ focusChanged(event) {
634
+ this.has_focus = event.type === 'focus';
635
+
636
+ // iOS does not populate relatedTarget when the relatedTarget is the send button
637
+ // leaving it impossible to detect if returning focus is required
638
+ if (
639
+ event.type === 'blur' &&
640
+ event.relatedTarget &&
641
+ event.relatedTarget === this.$refs.sendButton
642
+ ) {
643
+ // new target is the send button, keep focus on reset
644
+ return;
645
+ }
646
+ this.keep_focus = event.type === 'focus';
647
+ },
648
+ openAutoComplete(items) {
649
+ if (this.$state.setting('showAutocomplete')) {
650
+ this.autocomplete_items = items;
651
+ this.autocomplete_open = true;
652
+ }
653
+ },
654
+ buildAutoCompleteItems(_opts) {
655
+ let opts = _opts || {};
656
+ let list = [];
657
+
658
+ if (opts.users) {
659
+ let userList = _.values(this.buffer.users).map((user) => {
660
+ let item = {
661
+ text: user.nick,
662
+ type: 'user',
663
+ };
664
+ return item;
665
+ });
666
+
667
+ if (this.buffer.isQuery()) {
668
+ userList.push({
669
+ text: this.buffer.name,
670
+ type: 'user',
671
+ });
672
+ }
673
+
674
+ list = list.concat(userList);
675
+ }
676
+
677
+ if (opts.buffers) {
678
+ let bufferList = [];
679
+ this.network.buffers.forEach((buffer) => {
680
+ if (buffer.isChannel()) {
681
+ bufferList.push({
682
+ text: buffer.name,
683
+ type: 'buffer',
684
+ });
685
+ }
686
+ });
687
+
688
+ list = list.concat(bufferList);
689
+ }
690
+
691
+ if (opts.commands) {
692
+ let commandList = [];
693
+ autocompleteCommands.forEach((command) => {
694
+ // allow descriptions to be translation keys or static strings
695
+ let desc = command.description.indexOf('locale_id_') === 0 ?
696
+ TextFormatting.t(command.description.substr(10)) :
697
+ command.description;
698
+ commandList.push({
699
+ text: '/' + command.command,
700
+ description: desc,
701
+ type: 'command',
702
+ // Each alias needs the / command prefix adding
703
+ alias: (command.alias || []).map((c) => '/' + c),
704
+ });
705
+ });
706
+
707
+ list = list.concat(commandList);
708
+ }
709
+
710
+ if (opts.settings) {
711
+ let out = {};
712
+ let base = [];
713
+ settingTools.buildTree(out, base, this.$state.getSetting('settings'), false);
714
+ settingTools.buildTree(out, base, this.$state.getSetting('user_settings'), true);
715
+
716
+ let settingList = [];
717
+ Object.keys(out).forEach((setting) => {
718
+ settingList.push({
719
+ text: setting,
720
+ type: 'setting',
721
+ });
722
+ });
723
+
724
+ list = list.concat(settingList);
725
+ }
726
+
727
+ return list;
728
+ },
729
+ startTyping() {
730
+ if (!this.network.ircClient.network.cap.isEnabled('message-tags')) {
731
+ return;
732
+ }
733
+ if (!this.buffer || !this.buffer.shouldShareTyping()) {
734
+ return;
735
+ }
736
+
737
+ if (this.typingTimer) {
738
+ clearTimeout(this.typingTimer);
739
+ this.typingTimer = null;
740
+ }
741
+ this.typingTimer = setTimeout(() => this.stopTyping(true), 3000);
742
+
743
+ if (Date.now() < this.lastTypingTime + 3000) {
744
+ return;
745
+ }
746
+
747
+ this.network.ircClient.typing.start(this.buffer.name);
748
+
749
+ this.lastTypingTime = Date.now();
750
+ },
751
+ stopTyping(sendStop) {
752
+ if (!this.network.ircClient.network.cap.isEnabled('message-tags')) {
753
+ return;
754
+ }
755
+ if (!this.buffer || !this.buffer.shouldShareTyping()) {
756
+ return;
757
+ }
758
+
759
+ if (this.typingTimer) {
760
+ clearTimeout(this.typingTimer);
761
+ this.typingTimer = null;
762
+ this.lastTypingTime = 0;
763
+ }
764
+
765
+ this.$refs.input.getRawText().trim() ?
766
+ this.network.ircClient.typing.pause(this.buffer.name) :
767
+ this.network.ircClient.typing.stop(this.buffer.name, sendStop);
768
+ },
769
+ },
770
+ };
771
+ </script>
772
+
773
+ <style lang="less">
774
+
775
+ .kiwi-controlinput {
776
+ z-index: 999;
777
+ position: relative;
778
+ border-top: 1px solid;
779
+ }
780
+
781
+ .kiwi-controlinput,
782
+ .kiwi-controlinput-inner {
783
+ padding: 0;
784
+ box-sizing: border-box;
785
+ transition: width 0.2s;
786
+ transition-delay: 0.2s;
787
+ }
788
+
789
+ .kiwi-controlinput-inner {
790
+ display: flex;
791
+ position: relative;
792
+ height: 100%;
793
+
794
+ .kiwi-awaystatusindicator {
795
+ margin-top: 16px;
796
+ margin-left: 10px;
797
+ margin-right: -2px;
798
+ }
799
+ }
800
+
801
+ .kiwi-controlinput-user {
802
+ height: 100%;
803
+ padding-left: 10px;
804
+ font-weight: bold;
805
+ text-align: center;
806
+ cursor: pointer;
807
+ line-height: 40px;
808
+ transition: width 0.2s;
809
+ transition-delay: 0.1s;
810
+
811
+ > i {
812
+ font-size: 120%;
813
+ margin-left: 8px;
814
+ }
815
+ }
816
+
817
+ .kiwi-controlinput--selfuser-open {
818
+ .kiwi-controlinput-user {
819
+ width: 296px;
820
+ transition: width 0.2s;
821
+ transition-delay: 0.1s;
822
+ }
823
+
824
+ .kiwi-controlinput-selfuser {
825
+ width: 324px;
826
+ max-height: 300px;
827
+ opacity: 1;
828
+ }
829
+ }
830
+
831
+ .kiwi-controlinput-form {
832
+ flex: 1;
833
+ overflow: hidden;
834
+ display: flex;
835
+ box-sizing: border-box;
836
+ }
837
+
838
+ .kiwi-controlinput-input {
839
+ text-align: left;
840
+ height: 100%;
841
+ outline: none;
842
+ border: none;
843
+ }
844
+
845
+ .kiwi-controlinput-input-wrap {
846
+ width: 100%;
847
+ height: 100%;
848
+ box-sizing: border-box;
849
+ overflow: hidden;
850
+ margin: 0 10px;
851
+ }
852
+
853
+ .kiwi-controlinput-active-tool {
854
+ position: absolute;
855
+ bottom: calc(100% + 1px);
856
+ right: 74px;
857
+ left: 0;
858
+ z-index: 1;
859
+ }
860
+
861
+ .kiwi-controlinput-selfuser {
862
+ position: absolute;
863
+ bottom: 0;
864
+ z-index: 10;
865
+ left: 0;
866
+ max-height: 0;
867
+ width: 324px;
868
+ box-sizing: border-box;
869
+ border-radius: 0 6px 0 0;
870
+ opacity: 0;
871
+ border-top: 1px solid;
872
+ border-right: 1px solid;
873
+ overflow: hidden;
874
+ }
875
+
876
+ .kiwi-selfuser-trans-enter,
877
+ .kiwi-selfuser-trans-leave-to {
878
+ opacity: 0;
879
+ height: 0;
880
+ }
881
+
882
+ .kiwi-selfuser-trans-enter-to,
883
+ .kiwi-selfuser-trans-leave {
884
+ opacity: 1;
885
+ }
886
+
887
+ .kiwi-selfuser-trans-enter-active,
888
+ .kiwi-selfuser-trans-leave-active {
889
+ transition: all 0.4s;
890
+ }
891
+
892
+ .kiwi-controlinput-tools {
893
+ border-radius: 8px;
894
+ padding: 1px;
895
+ height: 36px;
896
+ }
897
+
898
+ .kiwi-controlinput-tools-expand > i {
899
+ transition: transform 0.2s;
900
+ }
901
+
902
+ .kiwi-controlinput-tools-expand--closed > i {
903
+ transform: rotateZ(180deg);
904
+ }
905
+
906
+ .kiwi-controlinput-send {
907
+ padding: 1px 6px;
908
+ }
909
+
910
+ .kiwi-controlinput--show-send.kiwi-controlinput--show-tools {
911
+ // The send button and tools are visible, merge their borders
912
+ .kiwi-controlinput-tools-wrapper {
913
+ border-radius: 0 8px 8px 0;
914
+ padding: 1px 1px 1px 0;
915
+ }
916
+
917
+ .kiwi-controlinput-send-container {
918
+ border-radius: 8px 0 0 8px;
919
+ padding: 1px 0 1px 1px;
920
+ }
921
+ }
922
+
923
+ .kiwi-controlinput-tools-container {
924
+ display: flex;
925
+ flex-wrap: wrap-reverse;
926
+ flex-direction: row-reverse;
927
+ padding: 1px;
928
+ border-radius: 8px;
929
+ position: absolute;
930
+ bottom: calc(100% + 1px);
931
+ top: auto;
932
+ right: 0;
933
+ width: 72px;
934
+ }
935
+
936
+ .kiwi-controlinput--show-tools--inline {
937
+ .kiwi-controlinput-tools-container {
938
+ flex-direction: row;
939
+ position: relative;
940
+ width: auto;
941
+ top: 0;
942
+ padding: 0;
943
+ }
944
+
945
+ .kiwi-controlinput-active-tool {
946
+ right: 0;
947
+ }
948
+ }
949
+
950
+ .kiwi-controlinput-button {
951
+ display: inline-block;
952
+ width: 34px;
953
+ height: 34px;
954
+ margin: 1px;
955
+ text-align: center;
956
+ border-radius: 8px;
957
+ box-sizing: border-box;
958
+ cursor: pointer;
959
+
960
+ i {
961
+ font-size: 20px;
962
+ line-height: 32px;
963
+ margin: 0;
964
+ }
965
+ }
966
+
967
+ .kiwi-plugin-ui-trans-enter,
968
+ .kiwi-plugin-ui-trans-leave-to {
969
+ right: -100%;
970
+ }
971
+
972
+ .kiwi-plugin-ui-trans-enter-to,
973
+ .kiwi-plugin-ui-trans-leave {
974
+ right: 0;
975
+ }
976
+
977
+ .kiwi-plugin-ui-trans-enter-active,
978
+ .kiwi-plugin-ui-trans-leave-active {
979
+ transition: right 0.2s;
980
+ }
981
+
982
+ @media screen and (max-width: 500px) {
983
+ .kiwi-controlinput-user-nick {
984
+ display: none;
985
+ }
986
+
987
+ .kiwi-controlinput-user > i {
988
+ margin-left: 0;
989
+ }
990
+ }
991
+
992
+ @media screen and (max-width: 769px) {
993
+ .kiwi-controlinput--selfuser-open .kiwi-controlinput-selfuser {
994
+ width: 100%;
995
+ border-radius: 0;
996
+ border-right: 0;
997
+ }
998
+
999
+ .kiwi-wrap--statebrowser-drawopen .kiwi-controlinput {
1000
+ z-index: 0;
1001
+ }
1002
+
1003
+ // hide the control input on narrow screens when the self user box is open
1004
+ .kiwi-controlinput--selfuser-open .kiwi-controlinput-inner {
1005
+ display: none;
1006
+ }
1007
+
1008
+ .kiwi-controlinput-tools-container {
1009
+ width: 34px;
1010
+ }
1011
+
1012
+ .kiwi-controlinput-active-tool {
1013
+ right: 36px;
1014
+ }
1015
+ }
1016
+ </style>