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,579 @@
1
+ <template>
2
+ <div class="kiwi-ircinput">
3
+ <div
4
+ ref="editor"
5
+ :placeholder="placeholder"
6
+ class="kiwi-ircinput-editor"
7
+ contenteditable="true"
8
+ role="textbox"
9
+ spellcheck="true"
10
+ @keypress="updateValueProps(); $emit('keypress', $event)"
11
+ @keydown="updateValueProps(); $emit('keydown', $event)"
12
+ @keyup="updateValueProps(); $emit('keyup', $event)"
13
+ @textInput="updateValueProps(); onTextInput($event); $emit('textInput', $event)"
14
+ @mouseup="updateValueProps();"
15
+ @click="$emit('click', $event)"
16
+ @paste="onPaste"
17
+ @drop="onDrop"
18
+ @focus="onFocus"
19
+ @blur="$emit('blur', $event)"
20
+ />
21
+ </div>
22
+ </template>
23
+
24
+ <script>
25
+ 'kiwi public';
26
+
27
+ import _ from 'lodash';
28
+ import * as htmlparser from 'htmlparser2';
29
+ import * as Colours from '@/helpers/Colours';
30
+ import * as Misc from '@/helpers/Misc';
31
+ import * as EmojiProvider from '@/libs/EmojiProvider';
32
+
33
+ let Vue = require('vue');
34
+
35
+ export default Vue.component('irc-input', {
36
+ props: ['placeholder'],
37
+ data() {
38
+ return {
39
+ last_known_value: '',
40
+ text_value: '',
41
+ current_el: null,
42
+ current_el_pos: 0,
43
+ default_colour: null,
44
+ code_map: Object.create(null),
45
+ };
46
+ },
47
+ computed: {
48
+ editor() {
49
+ return this.$refs.editor;
50
+ },
51
+ },
52
+ mounted() {
53
+ this.resetStyles();
54
+ },
55
+ methods: {
56
+ onTextInput(event) {
57
+ // Mobile devices trigger a textInput event for things such as autocompletion
58
+ // and suggested words. Unfortunately they end with a return character which
59
+ // is not what we expect, so prevent the original event from inserting anything
60
+ // and manually place it in with the current word.
61
+ if (event.data[event.data.length - 1] === '\n') {
62
+ event.preventDefault();
63
+ this.setCurrentWord(event.data.trim());
64
+ }
65
+ },
66
+ onPaste(event) {
67
+ event.preventDefault();
68
+ if (typeof event.clipboardData !== 'undefined') {
69
+ let ignoreThisPaste = false;
70
+ let clpData = event.clipboardData;
71
+ clpData.types.forEach((type) => {
72
+ let ignoreTypes = ['Files', 'image'];
73
+ ignoreTypes.forEach((ig) => {
74
+ if (type.indexOf(ig) > -1) {
75
+ ignoreThisPaste = true;
76
+ }
77
+ });
78
+ });
79
+
80
+ if (ignoreThisPaste) {
81
+ return;
82
+ }
83
+
84
+ document.execCommand('insertText', false, clpData.getData('text/plain'));
85
+ } else {
86
+ // IE11
87
+ let clpText = window.clipboardData.getData('Text');
88
+ if (!clpText) {
89
+ return;
90
+ }
91
+
92
+ let selection = window.getSelection();
93
+ let range = selection.getRangeAt(0);
94
+ if (range) {
95
+ range.deleteContents();
96
+ range.insertNode(document.createTextNode(clpText));
97
+ }
98
+ }
99
+
100
+ setTimeout(() => {
101
+ this.updateValueProps();
102
+ }, 0);
103
+ },
104
+ onDrop(event) {
105
+ // Allow dragging of single emoji's from the message list
106
+ const raw = event.dataTransfer.getData('text/html');
107
+ if (!raw) {
108
+ return;
109
+ }
110
+
111
+ let html = document.createElement('div');
112
+ html.innerHTML = raw;
113
+ if (html.childNodes.length === 0 || html.childNodes.length > 1) {
114
+ return;
115
+ }
116
+
117
+ let node = html.childNodes[0];
118
+ if (node instanceof HTMLImageElement && node.classList.contains('kiwi-messagelist-emoji')) {
119
+ event.preventDefault();
120
+ let emojis = EmojiProvider.getEmojis(node.alt.trim());
121
+ if (!emojis.length) {
122
+ return;
123
+ }
124
+ this.addImg(
125
+ emojis[0].ascii,
126
+ emojis[0].url,
127
+ emojis[0].imgProps,
128
+ );
129
+ }
130
+ },
131
+ onFocus(event) {
132
+ // Chrome sometimes focus' the element but does not add the cursor
133
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1125078
134
+ this.focus();
135
+
136
+ // when the input is empty there are no children to remember the current colour
137
+ // so upon regaining focus we must set the current colour again
138
+ if (!this.getRawText() && this.default_colour) {
139
+ this.setColour(this.default_colour.code, this.default_colour.colour);
140
+ }
141
+
142
+ this.$emit('focus', event);
143
+ },
144
+ updateSpacing() {
145
+ let editor = this.$refs.editor;
146
+ if (!editor) {
147
+ return;
148
+ }
149
+
150
+ // This will make sure any image nodes are padded with spaces
151
+ editor.childNodes.forEach((node) => {
152
+ if (node.nodeName !== 'IMG') {
153
+ return;
154
+ }
155
+
156
+ node.alt = node.alt.trim();
157
+
158
+ if (node.previousSibling) {
159
+ let text = node.previousSibling.textContent.replace(/\xA0/g, ' ');
160
+ let isEmpty = text.length === 0;
161
+ let isImg = node.previousSibling.nodeName === 'IMG';
162
+ let isText = ['#text', 'SPAN'].includes(node.previousSibling.nodeName);
163
+ if (isImg || (isText && (isEmpty || text[text.length - 1].indexOf(' ') === -1))) {
164
+ // The previous node is an image or text and does not end with a space
165
+ node.alt = ' ' + node.alt;
166
+ }
167
+ }
168
+
169
+ if (node.nextSibling) {
170
+ let text = node.nextSibling.textContent.replace(/\xA0/g, ' ');
171
+ let isNeeded = text.length !== 0 || !node.nextSibling.nextSibling;
172
+ let isText = ['#text', 'SPAN'].includes(node.nextSibling.nodeName);
173
+ if (isText && isNeeded && (text.length === 0 || text[0].indexOf(' ') === -1)) {
174
+ // The next node is text and does not start with a space
175
+ node.alt += ' ';
176
+ }
177
+ }
178
+ });
179
+ },
180
+ updateValueProps() {
181
+ if (!this.$el.contains(document.activeElement)) {
182
+ // Focused element is not a child of IrcInput
183
+ // selection would not be relevant
184
+ return;
185
+ }
186
+
187
+ let selection = window.getSelection();
188
+
189
+ if (selection.rangeCount === 0) {
190
+ return;
191
+ }
192
+
193
+ this.current_el_pos = selection.anchorOffset;
194
+ this.current_el = selection.anchorNode;
195
+
196
+ this.maybeEmitInput();
197
+ },
198
+ selectionToEnd() {
199
+ // Move the caret to the end
200
+ let el = this.$refs.editor.lastChild || this.$refs.editor;
201
+ this.current_el = el;
202
+
203
+ if (el.nodeType === 3) {
204
+ this.current_el_pos = el.length;
205
+ } else if (this.$el.contains(document.activeElement)) {
206
+ // IrcInput has focus select all content and collapse to end
207
+ document.execCommand('selectAll', false, null);
208
+ document.getSelection().collapseToEnd();
209
+ this.updateValueProps();
210
+ } else {
211
+ this.current_el_pos = 0;
212
+ }
213
+ },
214
+ setValue(newVal) {
215
+ this.value = newVal;
216
+ this.$refs.editor.innerHTML = newVal;
217
+ },
218
+ getValue() {
219
+ return this.$refs.editor.innerHTML;
220
+ },
221
+ maybeEmitInput() {
222
+ let currentHtml = this.$refs.editor.innerHTML;
223
+ if (this.last_known_value !== currentHtml) {
224
+ this.$emit('input', currentHtml);
225
+ this.last_known_value = currentHtml;
226
+ }
227
+ },
228
+ buildIrcText() {
229
+ this.updateSpacing();
230
+ let source = this.$refs.editor.innerHTML;
231
+ let textValue = '';
232
+
233
+ // Toggles are IRC style and colour codes that should be reset at the end of
234
+ // the current tag
235
+ let toggles = [];
236
+ function addToggle(t) {
237
+ toggles[toggles.length - 1] += t;
238
+ }
239
+ function getToggles() {
240
+ return toggles[toggles.length - 1];
241
+ }
242
+
243
+ let parser = new htmlparser.Parser({
244
+ onopentag: (name, attribs) => {
245
+ toggles.push('');
246
+ let codeLookup = '';
247
+ if (attribs.style) {
248
+ let match = attribs.style.match(/color: ([^;]+)/);
249
+ if (match) {
250
+ codeLookup = match[1];
251
+ let mappedCode = this.code_map[codeLookup];
252
+ if (!mappedCode) {
253
+ // If we didn't have an IRC code for this colour, convert the
254
+ // colour to its hex form and check if we have that instead
255
+ let m = codeLookup.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
256
+ if (m) {
257
+ let hex = Colours.rgb2hex({
258
+ r: parseInt(m[1], 10),
259
+ g: parseInt(m[2], 10),
260
+ b: parseInt(m[3], 10),
261
+ });
262
+ mappedCode = this.code_map[hex];
263
+ }
264
+ }
265
+
266
+ if (mappedCode) {
267
+ textValue += '\x03' + mappedCode;
268
+ addToggle('\x03' + mappedCode);
269
+ }
270
+ }
271
+
272
+ if (attribs.style.indexOf('bold') > -1) {
273
+ textValue += '\x02';
274
+ addToggle('\x02');
275
+ }
276
+ if (attribs.style.indexOf('italic') > -1) {
277
+ textValue += '\x1d';
278
+ addToggle('\x1d');
279
+ }
280
+ if (attribs.style.indexOf('underline') > -1) {
281
+ textValue += '\x1f';
282
+ addToggle('\x1f');
283
+ }
284
+
285
+ // Welcome to the IE/Edge sucks section, time to do crazy things
286
+ // IE11 doesnt support document.execCommand('styleWithCSS')
287
+ // so we have individual nodes instead, which are handled below
288
+ } else if (attribs.color) {
289
+ // IE likes to remove spaces from rgb(1, 2, 3)
290
+ // it also likes converting rgb to hex
291
+ let mappedCode = this.code_map[attribs.color] ||
292
+ this.code_map[attribs.color.replace(/,/g, ', ')] ||
293
+ this.code_map[Colours.hex2rgb(attribs.color)];
294
+
295
+ if (mappedCode) {
296
+ textValue += '\x03' + mappedCode;
297
+ addToggle('\x03' + mappedCode);
298
+ }
299
+ } else if (name === 'strong') {
300
+ textValue += '\x02';
301
+ addToggle('\x02');
302
+ } else if (name === 'em') {
303
+ textValue += '\x1d';
304
+ addToggle('\x1d');
305
+ } else if (name === 'u') {
306
+ textValue += '\x1f';
307
+ addToggle('\x1f');
308
+ } else if (name === 'div' || name === 'br') {
309
+ // divs and breaks are both considered newlines. For each line we need to
310
+ // close all current toggles and then reopen them for the next so that the
311
+ // styles continue .
312
+ textValue += getToggles();
313
+ textValue += '\n';
314
+ textValue += getToggles();
315
+ }
316
+
317
+ if (name === 'img' && attribs.alt) {
318
+ textValue += attribs.alt;
319
+ }
320
+ },
321
+ ontext: (text) => {
322
+ textValue += text;
323
+ },
324
+ onclosetag: (tagName) => {
325
+ textValue += getToggles();
326
+ toggles.pop();
327
+ },
328
+ }, {
329
+ decodeEntities: true,
330
+ });
331
+
332
+ parser.write(source);
333
+ parser.end();
334
+
335
+ // Firefox likes to add <br/> at the end (some times inside the span)
336
+ // fix by filtering out any lines that contain no content
337
+ return textValue.split(/\r?\n/).filter((line) => !!Misc.stripStyles(line)).join('\n');
338
+ },
339
+ reset(rawHtml, shouldFocus) {
340
+ this.$refs.editor.innerHTML = rawHtml || '';
341
+
342
+ this.current_el_pos = 0;
343
+ this.current_el = this.$refs.editor;
344
+
345
+ // Firefox inserts a <br> on empty contenteditables after it's been reset. But that
346
+ // fucks up the placeholder :empty CSS selector we use. So just remove it.
347
+ let br = this.$refs.editor.querySelector('br');
348
+ if (br) {
349
+ br.parentNode.removeChild(br);
350
+ }
351
+
352
+ if (shouldFocus) {
353
+ this.focus();
354
+
355
+ if (this.default_colour) {
356
+ this.setColour(this.default_colour.code, this.default_colour.colour);
357
+ }
358
+
359
+ this.updateValueProps();
360
+ } else {
361
+ this.maybeEmitInput();
362
+ }
363
+ },
364
+ resetStyles() {
365
+ this.focus();
366
+ document.execCommand('styleWithCSS', false, true);
367
+ document.execCommand('selectAll', false, null);
368
+ document.execCommand('removeFormat', false, null);
369
+ this.default_colour = null;
370
+ },
371
+ setColour(code, colour) {
372
+ // If no current text selection, set this colour as the default colour for
373
+ // future messages too
374
+ let range = window.getSelection().getRangeAt(0);
375
+ if (range && range.collapsed) {
376
+ this.default_colour = {
377
+ code,
378
+ colour,
379
+ };
380
+ }
381
+
382
+ this.focus();
383
+ document.execCommand('styleWithCSS', false, true);
384
+ document.execCommand('foreColor', false, colour);
385
+
386
+ this.code_map[colour] = code;
387
+ this.updateValueProps();
388
+ },
389
+ toggleBold() {
390
+ document.execCommand('bold', false, null);
391
+ this.updateValueProps();
392
+ },
393
+ toggleItalic() {
394
+ document.execCommand('italic', false, null);
395
+ this.updateValueProps();
396
+ },
397
+ toggleUnderline() {
398
+ document.execCommand('underline', false, null);
399
+ this.updateValueProps();
400
+ },
401
+ addImg(alt, url, props) {
402
+ this.focus();
403
+
404
+ let existingImages = [..._.values(this.$refs.editor.querySelectorAll('img'))];
405
+
406
+ document.execCommand('styleWithCSS', false, true);
407
+ document.execCommand('insertImage', false, url);
408
+
409
+ let newImg = null;
410
+ let images = [..._.values(this.$refs.editor.querySelectorAll('img'))];
411
+
412
+ // Find image that has just been inserted
413
+ images.forEach((img) => {
414
+ if (existingImages.indexOf(img) === -1) {
415
+ newImg = img;
416
+ }
417
+ });
418
+
419
+ newImg.alt = alt;
420
+ Object.assign(newImg, props);
421
+
422
+ // Find the position of this new image node
423
+ let prevElCnt = 0;
424
+ let el = newImg;
425
+ while (el) {
426
+ el = el.previousSibling;
427
+ prevElCnt++;
428
+ }
429
+
430
+ this.current_el = this.$refs.editor;
431
+ this.current_el_pos = prevElCnt;
432
+
433
+ this.updateValueProps();
434
+ this.focus();
435
+ },
436
+
437
+ // Insert some text at the current position
438
+ insertText(text) {
439
+ this.focus();
440
+ document.execCommand('insertText', false, text);
441
+ this.updateValueProps();
442
+ this.focus();
443
+ },
444
+
445
+ // Replace the word at the current position with another
446
+ setCurrentWord(text, keepPosition, toPosition) {
447
+ let el = this.current_el;
448
+ let pos = this.current_el_pos;
449
+ let val = el.textContent || '';
450
+
451
+ let startVal = val.substr(0, pos);
452
+ let space = startVal.replace(/\xA0/g, ' ').lastIndexOf(' ');
453
+ if (space === -1) {
454
+ space = 0;
455
+ } else {
456
+ // include the space after the word
457
+ space++;
458
+ }
459
+ let startPos = space;
460
+
461
+ let endVal = val.substr(pos);
462
+ space = endVal.replace(/\xA0/g, ' ').indexOf(' ');
463
+ if (space === -1) space = endVal.length;
464
+ let endPos = toPosition ? 0 : space;
465
+
466
+ el.textContent = startVal.substr(0, startPos) + text + endVal.substr(endPos);
467
+
468
+ // Move the caret after the word
469
+ let range = document.createRange();
470
+ if (keepPosition) {
471
+ range.setStart(el, pos);
472
+ range.setEnd(el, pos);
473
+ } else if (el.nodeType === 3) {
474
+ // TEXT_NODE
475
+ range.setStart(el, startPos + text.length);
476
+ range.setEnd(el, startPos + text.length);
477
+ } else {
478
+ // el is another type of node, so setStart/End() counts in nodes instead
479
+ // of text length
480
+ range.setStart(el, 1);
481
+ range.setEnd(el, 1);
482
+ }
483
+
484
+ // ensure the input has focus so the cursor position can be set correctly
485
+ this.$refs.editor.focus();
486
+
487
+ let sel = window.getSelection();
488
+ sel.removeAllRanges();
489
+ sel.addRange(range);
490
+ this.updateValueProps();
491
+ },
492
+
493
+ getCurrentWord(toPosition) {
494
+ let el = this.current_el;
495
+ let pos = this.current_el_pos;
496
+ let val = el.textContent;
497
+ let cleanVal = val.replace(/\xA0/g, ' ');
498
+
499
+ let startVal = cleanVal.substr(0, pos);
500
+ let space = startVal.lastIndexOf(' ');
501
+ if (space === -1) {
502
+ space = 0;
503
+ } else {
504
+ // include the space after the word
505
+ space++;
506
+ }
507
+ let startPos = space;
508
+
509
+ space = cleanVal.indexOf(' ', startPos);
510
+ if (space === -1) {
511
+ space = val.length;
512
+ }
513
+ let endPos = toPosition ? pos - startPos : space;
514
+
515
+ return {
516
+ word: val.substr(startPos, endPos),
517
+ position: pos - startPos,
518
+ };
519
+ },
520
+
521
+ getCaretIdx() {
522
+ let position = 0;
523
+ let selection = window.getSelection();
524
+ if (selection.rangeCount !== 0) {
525
+ let range = window.getSelection().getRangeAt(0);
526
+ let caretRange = range.cloneRange();
527
+ caretRange.selectNodeContents(this.$el);
528
+ caretRange.setEnd(range.endContainer, range.endOffset);
529
+ position = caretRange.toString().length;
530
+ }
531
+ return position;
532
+ },
533
+
534
+ getRawText() {
535
+ return this.$refs.editor.innerText;
536
+ },
537
+
538
+ // Focus the editable div and move the caret to the end
539
+ focus() {
540
+ let selection = window.getSelection();
541
+ let range = document.createRange();
542
+ range.setStart(this.current_el || this.$refs.editor, this.current_el_pos || 0);
543
+
544
+ selection.removeAllRanges();
545
+ selection.addRange(range);
546
+ },
547
+ },
548
+ });
549
+ </script>
550
+
551
+ <style>
552
+
553
+ .kiwi-ircinput {
554
+ box-sizing: border-box;
555
+ position: relative;
556
+ overflow: visible;
557
+ z-index: 1;
558
+ }
559
+
560
+ .kiwi-ircinput-editor {
561
+ overflow-x: hidden;
562
+ outline: none;
563
+ padding: 7px 0 12px 0;
564
+
565
+ /* When the contenteditable div is empty firefox makes its height 0px */
566
+ height: 100%;
567
+ }
568
+
569
+ .kiwi-ircinput-editor:empty:not(:focus)::before {
570
+ content: attr(placeholder);
571
+ cursor: text;
572
+ }
573
+
574
+ .kiwi-ircinput-editor img {
575
+ height: 1em;
576
+ vertical-align: -0.1em;
577
+ }
578
+
579
+ </style>
@@ -0,0 +1,26 @@
1
+ <template functional>
2
+ <div
3
+ v-rawElement="{
4
+ el: props.pluginElement,
5
+ props: {
6
+ kiwi: {
7
+ ...props.pluginProps,
8
+ }
9
+ }
10
+ }"
11
+ :class="{ [data.staticClass]: data.staticClass }"
12
+ />
13
+ </template>
14
+
15
+ <script>
16
+
17
+ 'kiwi public';
18
+
19
+ export default {
20
+ props: {
21
+ pluginElement: Element,
22
+ pluginProps: Object,
23
+ },
24
+ };
25
+
26
+ </script>