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.
- package/.github/workflows/ci.yml +6 -1
- package/.gitmodules +3 -0
- package/CHANGELOG.md +214 -0
- package/README.md +110 -23
- package/apps/aws-stack/package.json +1 -1
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/origin-allowlist.ts +99 -0
- package/apps/cf-worker/src/worker.ts +89 -9
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
- package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
- package/apps/cf-worker/tests/smoke.test.ts +28 -2
- package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +54 -0
- package/apps/local-cli/package.json +1 -1
- package/apps/web/landing/index.html +217 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +125 -0
- package/apps/web/src/build-env.ts +43 -0
- package/apps/web/src/config-schema.ts +138 -0
- package/apps/web/static/config.json +28 -0
- package/apps/web/static/config.prod.json +28 -0
- package/apps/web/static/config.staging.json +28 -0
- package/apps/web/tests/build-env.test.ts +63 -0
- package/apps/web/tests/build-smoke.test.ts +103 -0
- package/apps/web/tests/config-schema.test.ts +432 -0
- package/apps/web/tests/workspace.test.ts +12 -0
- package/apps/web/tsconfig.json +10 -0
- package/apps/web/upstream/.browserslistrc +3 -0
- package/apps/web/upstream/.editorconfig +9 -0
- package/apps/web/upstream/.eslintignore +3 -0
- package/apps/web/upstream/.eslintrc.js +90 -0
- package/apps/web/upstream/.github/FUNDING.yml +12 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/apps/web/upstream/.github/workflows/push.yml +26 -0
- package/apps/web/upstream/.github/workflows/release.yml +33 -0
- package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
- package/apps/web/upstream/.prettierrc.js +9 -0
- package/apps/web/upstream/.stylelintignore +5 -0
- package/apps/web/upstream/.stylelintrc.js +21 -0
- package/apps/web/upstream/LICENSE +201 -0
- package/apps/web/upstream/README.md +76 -0
- package/apps/web/upstream/babel.config.js +25 -0
- package/apps/web/upstream/crowdin.yml +3 -0
- package/apps/web/upstream/index.html +35 -0
- package/apps/web/upstream/jest.config.js +7 -0
- package/apps/web/upstream/jsdoc.json +22 -0
- package/apps/web/upstream/package.json +80 -0
- package/apps/web/upstream/scripts/pre-commit +43 -0
- package/apps/web/upstream/src/components/App.vue +516 -0
- package/apps/web/upstream/src/components/AppSettings.vue +587 -0
- package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
- package/apps/web/upstream/src/components/Avatar.vue +105 -0
- package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
- package/apps/web/upstream/src/components/BufferKey.vue +121 -0
- package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
- package/apps/web/upstream/src/components/Captcha.vue +85 -0
- package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
- package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
- package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
- package/apps/web/upstream/src/components/ChannelList.vue +345 -0
- package/apps/web/upstream/src/components/Container.vue +401 -0
- package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
- package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
- package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
- package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
- package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
- package/apps/web/upstream/src/components/MessageList.vue +1028 -0
- package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
- package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
- package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
- package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
- package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
- package/apps/web/upstream/src/components/Nicklist.vue +309 -0
- package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
- package/apps/web/upstream/src/components/NotConnected.vue +271 -0
- package/apps/web/upstream/src/components/SelfUser.vue +283 -0
- package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
- package/apps/web/upstream/src/components/ServerView.vue +126 -0
- package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
- package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
- package/apps/web/upstream/src/components/Sidebar.vue +429 -0
- package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
- package/apps/web/upstream/src/components/SidebarState.vue +145 -0
- package/apps/web/upstream/src/components/StartupError.vue +35 -0
- package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
- package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
- package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
- package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
- package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
- package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
- package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
- package/apps/web/upstream/src/components/UserBox.vue +728 -0
- package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
- package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
- package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
- package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
- package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
- package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
- package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
- package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
- package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
- package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
- package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
- package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
- package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
- package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
- package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
- package/apps/web/upstream/src/helpers/Colours.js +128 -0
- package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
- package/apps/web/upstream/src/helpers/Md5.js +193 -0
- package/apps/web/upstream/src/helpers/Misc.js +401 -0
- package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
- package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
- package/apps/web/upstream/src/libs/AudioManager.js +60 -0
- package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
- package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
- package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
- package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
- package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
- package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
- package/apps/web/upstream/src/libs/IPC.js +51 -0
- package/apps/web/upstream/src/libs/InputHandler.js +921 -0
- package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
- package/apps/web/upstream/src/libs/Logger.js +71 -0
- package/apps/web/upstream/src/libs/Message.js +169 -0
- package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
- package/apps/web/upstream/src/libs/MessageParser.js +249 -0
- package/apps/web/upstream/src/libs/Notifications.js +78 -0
- package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
- package/apps/web/upstream/src/libs/ServerSession.js +102 -0
- package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
- package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
- package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
- package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
- package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
- package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
- package/apps/web/upstream/src/libs/bufferTools.js +177 -0
- package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
- package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
- package/apps/web/upstream/src/libs/settingTools.js +31 -0
- package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
- package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
- package/apps/web/upstream/src/libs/state/UserState.js +89 -0
- package/apps/web/upstream/src/libs/state/common.js +26 -0
- package/apps/web/upstream/src/libs/state.js +961 -0
- package/apps/web/upstream/src/libs/storage/Local.js +51 -0
- package/apps/web/upstream/src/main.js +566 -0
- package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
- package/apps/web/upstream/src/res/configTemplates.js +373 -0
- package/apps/web/upstream/src/res/globalStyle.css +277 -0
- package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
- package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
- package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
- package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
- package/apps/web/upstream/src/res/localesList.json +27 -0
- package/apps/web/upstream/src/res/logo.png +0 -0
- package/apps/web/upstream/src/thirdparty/about.html +43 -0
- package/apps/web/upstream/src/thirdparty/index.js +5 -0
- package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
- package/apps/web/upstream/static/config.json +28 -0
- package/apps/web/upstream/static/emoticons/smile.png +0 -0
- package/apps/web/upstream/static/favicon.png +0 -0
- package/apps/web/upstream/static/highlight.mp3 +0 -0
- package/apps/web/upstream/static/highlight.ogg +0 -0
- package/apps/web/upstream/static/locales/.gitignore +2 -0
- package/apps/web/upstream/static/plugins/customise.html.example +10 -0
- package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
- package/apps/web/upstream/static/themes/common/base.css +1064 -0
- package/apps/web/upstream/static/themes/dark/theme.css +196 -0
- package/apps/web/upstream/static/themes/default/theme.css +92 -0
- package/apps/web/upstream/static/themes/elite/theme.css +248 -0
- package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
- package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
- package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
- package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
- package/apps/web/upstream/static/themes/sky/theme.css +107 -0
- package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
- package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
- package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
- package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
- package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
- package/apps/web/upstream/vue.config.js +150 -0
- package/apps/web/upstream/yarn.lock +9767 -0
- package/apps/web/vitest.config.ts +13 -0
- package/biome.json +1 -0
- package/docs/Home.md +1 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/reports/mutation/mutation.html +342 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
'kiwi public';
|
|
2
|
+
|
|
3
|
+
/** @module */
|
|
4
|
+
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import Vue from 'vue';
|
|
7
|
+
import strftime from 'strftime';
|
|
8
|
+
import PluginWrapper from '@/components/utils/PluginWrapper';
|
|
9
|
+
import * as TextFormatting from '@/helpers/TextFormatting';
|
|
10
|
+
import { urlRegex } from './TextFormatting';
|
|
11
|
+
|
|
12
|
+
const strftimeUTC = strftime.timezone('+0');
|
|
13
|
+
/**
|
|
14
|
+
* Extract an array of buffers from a string, parsing multiple buffer names and channel keys
|
|
15
|
+
* "#chan,#chan2" => 2 channels without a key
|
|
16
|
+
* "#chan,#chan2 key" => 2 channels, the first having a key
|
|
17
|
+
* "#chan,#chan2 key1,key2" => 2 channels, both having a key
|
|
18
|
+
* "#chan,#chan2 ,key2" => 2 channels, the second having a key
|
|
19
|
+
* @param {string} str List of buffer names and channel keys
|
|
20
|
+
*/
|
|
21
|
+
export function extractBuffers(str) {
|
|
22
|
+
let spaceIdx = str.indexOf(' ');
|
|
23
|
+
if (spaceIdx === -1) spaceIdx = str.length;
|
|
24
|
+
|
|
25
|
+
let bufferNames = str.substr(0, spaceIdx).split(',');
|
|
26
|
+
let keys = str.substr(spaceIdx + 1).split(',');
|
|
27
|
+
|
|
28
|
+
let buffers = [];
|
|
29
|
+
bufferNames.forEach((bufferName, idx) => {
|
|
30
|
+
// return if bufferName is empty
|
|
31
|
+
if (!bufferName.trim()) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
buffers.push({
|
|
35
|
+
name: bufferName,
|
|
36
|
+
key: keys[idx] || '',
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return buffers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function extractURL(str) {
|
|
44
|
+
let matches = str.match(urlRegex);
|
|
45
|
+
return matches ? matches[0] : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function stripStyles(str) {
|
|
49
|
+
return str.replace(/(\x03[0-9]{0,2})?([\x02\x16\x1d\x1f]+)?/g, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Does a string mention a nickname?
|
|
54
|
+
* @param {string} input The string to search within
|
|
55
|
+
* @param {string} nick The nick to search for
|
|
56
|
+
*/
|
|
57
|
+
export function mentionsNick(input, nick) {
|
|
58
|
+
if (input.toLowerCase().indexOf(nick.toLowerCase()) === -1) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
let punc = '\\s,.!:;+()\\[\\]?¿\\/<>@-';
|
|
62
|
+
let escapedNick = _.escapeRegExp(nick);
|
|
63
|
+
let stylesStrippedInput = stripStyles(input);
|
|
64
|
+
let r = new RegExp(`(^|[${punc}])${escapedNick}([${punc}]|$)`, 'i');
|
|
65
|
+
return r.test(stylesStrippedInput);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get a query string value from the current URL
|
|
70
|
+
* @param {string} _name The query string variable name
|
|
71
|
+
* @param {string} _url The full URL to extract the variable from
|
|
72
|
+
*/
|
|
73
|
+
export function queryStringVal(_name, _url) {
|
|
74
|
+
let url = _url || window.location.href;
|
|
75
|
+
let name = _.escapeRegExp(_name);
|
|
76
|
+
let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
|
|
77
|
+
let results = regex.exec(url);
|
|
78
|
+
|
|
79
|
+
if (!results) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (!results[2]) {
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert a known error code to a human readable message
|
|
91
|
+
* @param {string} err The error message from the network connection
|
|
92
|
+
*/
|
|
93
|
+
export function networkErrorMessage(err) {
|
|
94
|
+
let errs = {
|
|
95
|
+
err_unknown_host: 'Unknown domain name or host',
|
|
96
|
+
err_forbidden: 'Forbidden to connect',
|
|
97
|
+
err_timeout: 'Took too long to connect',
|
|
98
|
+
err_refused: 'The server refused the connection',
|
|
99
|
+
err_tls: 'Could not connect securely',
|
|
100
|
+
err_proxy: 'The Kiwi IRC server had an error',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return errs[err] || 'Unknown error';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Take a users connection object (usually from startupOptions) and normalise a connection
|
|
108
|
+
* settings object. Parses websocket/direct/kiwiServer/etc options and creates a single
|
|
109
|
+
* object that Kiwi can consistently read from.
|
|
110
|
+
* @param {Object} config User provided connection config object
|
|
111
|
+
*/
|
|
112
|
+
export function connectionInfoFromConfig(config) {
|
|
113
|
+
let connection = {
|
|
114
|
+
tls: false,
|
|
115
|
+
port: 0,
|
|
116
|
+
hostname: '',
|
|
117
|
+
direct: false,
|
|
118
|
+
direct_path: '',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
let wsUri = config.websocket ?
|
|
122
|
+
matchUri(config.websocket) :
|
|
123
|
+
null;
|
|
124
|
+
|
|
125
|
+
if (wsUri) {
|
|
126
|
+
connection.direct = true;
|
|
127
|
+
connection.tls = ['wss', 'https', 'ircs'].indexOf(wsUri.protocol) > -1;
|
|
128
|
+
connection.port = wsUri.port;
|
|
129
|
+
connection.hostname = wsUri.hostname;
|
|
130
|
+
connection.direct_path = wsUri.path;
|
|
131
|
+
if (wsUri.search) {
|
|
132
|
+
connection.direct_path += '?' + wsUri.search;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
connection.tls = config.tls;
|
|
136
|
+
connection.port = config.port;
|
|
137
|
+
connection.hostname = config.server;
|
|
138
|
+
connection.direct = !!config.direct;
|
|
139
|
+
connection.direct_path = config.direct_path || '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return connection;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function matchUri(uri) {
|
|
146
|
+
let reg = /(?:([a-z]+):\/\/)?([a-z.0-9-]+)(?::(?:(\+)?([0-9]+)))?(?:\/([^?]*))?(?:\?(.*))?/;
|
|
147
|
+
/*
|
|
148
|
+
0: "ws://hostname.com:6676/erferf?foo=val"
|
|
149
|
+
1: "ws"
|
|
150
|
+
2: "hostname.com"
|
|
151
|
+
3: undefined
|
|
152
|
+
4: "6676"
|
|
153
|
+
5: "erferf"
|
|
154
|
+
6: "foo=var"
|
|
155
|
+
*/
|
|
156
|
+
let m = uri.match(reg);
|
|
157
|
+
if (!m) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
protocol: (m[1] || '').toLowerCase(),
|
|
163
|
+
hostname: m[2] || '',
|
|
164
|
+
port: parseIntZero(m[4] || ''),
|
|
165
|
+
path: '/' + (m[5] || ''),
|
|
166
|
+
search: m[6] || '',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parse a connection string into an object
|
|
172
|
+
* E.g. [ircs?://]irc.network.net:[+]6667/channel?nick=mynick;
|
|
173
|
+
* Multiple connections may be given, separated by ;
|
|
174
|
+
* @param {string} str The connection string URI
|
|
175
|
+
*/
|
|
176
|
+
export function parseIrcUri(str) {
|
|
177
|
+
let reg = /(?:(ircs?|wss?):\/\/)?([a-z.0-9-]+)(?::(?:(\+)?([0-9]+)))?(?:\/([^?]*))?(?:\?(.*))?/;
|
|
178
|
+
let connections = [];
|
|
179
|
+
str.split(';').forEach((connectionString) => {
|
|
180
|
+
if (!connectionString) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let m = connectionString.match(reg);
|
|
185
|
+
|
|
186
|
+
if (!m) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let tls = m[1] === 'ircs' || m[1] === 'wss' || !!m[3];
|
|
191
|
+
let direct = m[1] === 'ws' || m[1] === 'wss';
|
|
192
|
+
let params = Object.create(null);
|
|
193
|
+
(m[6] || '').split('&').forEach((p) => {
|
|
194
|
+
let parts = p.split('=');
|
|
195
|
+
if (parts.length === 2) {
|
|
196
|
+
params[parts[0].toLowerCase()] = parts[1];
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
let channels = (m[5] || params.channel || '');
|
|
201
|
+
channels = _(channels.split(','))
|
|
202
|
+
.compact()
|
|
203
|
+
.map((_channelName) => {
|
|
204
|
+
let hasPrefix = _channelName[0] === '#' ||
|
|
205
|
+
_channelName[0] === '&';
|
|
206
|
+
|
|
207
|
+
let channelName = hasPrefix ?
|
|
208
|
+
_channelName :
|
|
209
|
+
'#' + _channelName;
|
|
210
|
+
|
|
211
|
+
return channelName;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
connections.push({
|
|
215
|
+
tls: tls,
|
|
216
|
+
server: m[2],
|
|
217
|
+
port: parseInt(m[4] || (tls ? 6697 : 6667), 10),
|
|
218
|
+
channels: channels,
|
|
219
|
+
nick: params.nick || '',
|
|
220
|
+
encoding: (params.encoding || 'utf8'),
|
|
221
|
+
params: params,
|
|
222
|
+
direct: direct,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return connections;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Parse preset server string to an object
|
|
231
|
+
* format: freenode|irc.freenode.net:+6697
|
|
232
|
+
* @param {string} input Preset server string
|
|
233
|
+
*/
|
|
234
|
+
export function parsePresetServer(input) {
|
|
235
|
+
let ret = {
|
|
236
|
+
name: '',
|
|
237
|
+
server: '',
|
|
238
|
+
port: 6667,
|
|
239
|
+
tls: false,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
ret.toUri = () => `${ret.server}:${ret.tls ? '+' : ''}${ret.port}`;
|
|
243
|
+
|
|
244
|
+
let val = input;
|
|
245
|
+
|
|
246
|
+
let pipePos = val.indexOf('|');
|
|
247
|
+
if (pipePos > -1) {
|
|
248
|
+
ret.name = val.substr(0, pipePos);
|
|
249
|
+
val = val.substr(pipePos + 1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let colonPos = val.indexOf(':');
|
|
253
|
+
if (colonPos === -1) {
|
|
254
|
+
ret.server = val;
|
|
255
|
+
val = '';
|
|
256
|
+
} else {
|
|
257
|
+
ret.server = val.substr(0, colonPos);
|
|
258
|
+
val = val.substr(colonPos + 1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (val[0] === '+') {
|
|
262
|
+
ret.tls = true;
|
|
263
|
+
val = val.substr(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (val.length > 0) {
|
|
267
|
+
ret.port = parseInt(val, 10);
|
|
268
|
+
val = '';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!ret.name) {
|
|
272
|
+
ret.name = ret.server;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return ret;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Scan though an object and extend any dot notated keys
|
|
280
|
+
* @param {Object} confObj Source object to traverse
|
|
281
|
+
*/
|
|
282
|
+
export function dedotObject(confObj, _place) {
|
|
283
|
+
let place = _place || [];
|
|
284
|
+
let regex = /\w\.\w/;
|
|
285
|
+
|
|
286
|
+
_.each(confObj, (val, key) => {
|
|
287
|
+
let ourPlace = place.concat([key]);
|
|
288
|
+
if (typeof val === 'object') {
|
|
289
|
+
dedotObject(confObj[key], ourPlace);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (regex.test(key)) {
|
|
293
|
+
delete confObj[key];
|
|
294
|
+
_.set(confObj, ourPlace.join('.'), val);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Replace the target object with source, while keeping the target object reference intact.
|
|
301
|
+
* Delete all the properties from the target object and copy the properties from source
|
|
302
|
+
* over to the target.
|
|
303
|
+
* a = {one: 1, two: 2, three: 3}
|
|
304
|
+
* b = {four: 4, five: 5, six: 6}
|
|
305
|
+
* replaceObjectProps(a, b)
|
|
306
|
+
* a.one === undefined;
|
|
307
|
+
* a.six === 6;
|
|
308
|
+
* @param {Object} target The target object that will be replaced
|
|
309
|
+
* @param {Object} source The source object from which all properties will be copied from
|
|
310
|
+
*/
|
|
311
|
+
export function replaceObjectProps(target, source) {
|
|
312
|
+
Object.keys(target).forEach((prop) => delete target[prop]);
|
|
313
|
+
Object.keys(source).forEach((prop) => { target[prop] = source[prop]; });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create an ISO8601 formatted date
|
|
318
|
+
* @param {Date} date The date object to create the time from. Defaults to the current time
|
|
319
|
+
*/
|
|
320
|
+
export function dateIso(date) {
|
|
321
|
+
let d = date || new Date();
|
|
322
|
+
return strftimeUTC('%Y-%m-%dT%H:%M:%S.%LZ', d);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Cast a string to an int, returning 0 if it fails
|
|
327
|
+
* @param {String} inp The number as a string to convert to an int
|
|
328
|
+
*/
|
|
329
|
+
export function parseIntZero(inp) {
|
|
330
|
+
let int = parseInt(inp, 10);
|
|
331
|
+
return Number.isNaN(int) ?
|
|
332
|
+
0 :
|
|
333
|
+
int;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Prepare plugin provided object, for use as a plugin component
|
|
338
|
+
* @param {Number} pluginId The number to identify this plugin object
|
|
339
|
+
* @param {Component} componentOrElement The vue.js component object or html element
|
|
340
|
+
* @param {Object} args Optional arguments for this plugin { title: '', props: {} }
|
|
341
|
+
*/
|
|
342
|
+
export function makePluginObject(pluginId, componentOrElement, args = {}) {
|
|
343
|
+
const plugin = {
|
|
344
|
+
id: pluginId,
|
|
345
|
+
component: null,
|
|
346
|
+
title: () => '',
|
|
347
|
+
props: {},
|
|
348
|
+
tabName: '',
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// copy props from args
|
|
352
|
+
plugin.props = Object.assign(plugin.props, args.props);
|
|
353
|
+
plugin.tabName = args.tabName || args.title || 'plugin_tab_' + pluginId;
|
|
354
|
+
|
|
355
|
+
if (componentOrElement instanceof Element) {
|
|
356
|
+
// componentOrElement is an html element, so wrap it in a functional component
|
|
357
|
+
|
|
358
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
359
|
+
if (componentOrElement.__vue__ && !window.kiwi_deprecations_vueEl) {
|
|
360
|
+
window.kiwi_deprecations_vueEl = true;
|
|
361
|
+
// eslint-disable-next-line no-console
|
|
362
|
+
console.warn('deprecated component.$el added to plugin api, please switch to just passing the vue.js component object');
|
|
363
|
+
}
|
|
364
|
+
plugin.component = PluginWrapper;
|
|
365
|
+
plugin.props = Object.assign(plugin.props, {
|
|
366
|
+
pluginElement: componentOrElement,
|
|
367
|
+
});
|
|
368
|
+
} else if (componentOrElement instanceof Object) {
|
|
369
|
+
// componentOrElement is an object, attempt to make a vue component from it
|
|
370
|
+
plugin.component = Vue.extend(componentOrElement);
|
|
371
|
+
} else {
|
|
372
|
+
plugin.component = componentOrElement;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (typeof args.title === 'string') {
|
|
376
|
+
// if args.title exists wrap it in a function, that can also be a translation string
|
|
377
|
+
plugin.title = () => (args.title.slice(0, 2) === 't_' ? TextFormatting.t(args.title.slice(2)) : args.title);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return plugin;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// This provides a better sort for numbered nicks but does not work on ios9
|
|
384
|
+
let intlCollator;
|
|
385
|
+
if (global.Intl) {
|
|
386
|
+
intlCollator = new Intl.Collator({}, { numeric: true });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function strCompare(a, b) {
|
|
390
|
+
if (intlCollator) {
|
|
391
|
+
return intlCollator.compare(a, b);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (a === b) {
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return a > b ?
|
|
399
|
+
1 :
|
|
400
|
+
-1;
|
|
401
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'kiwi public';
|
|
2
|
+
|
|
3
|
+
/** @module */
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import * as ipRegex from 'ip-regex';
|
|
6
|
+
import i18next from 'i18next';
|
|
7
|
+
import * as murmurhash3 from 'murmurhash3js';
|
|
8
|
+
|
|
9
|
+
import getState from '@/libs/state';
|
|
10
|
+
import ThemeManager from '@/libs/ThemeManager';
|
|
11
|
+
|
|
12
|
+
export const urlRegex = new RegExp(
|
|
13
|
+
// Detect either a protocol or 'www.' to start a URL
|
|
14
|
+
/(([A-Za-z][A-Za-z0-9-]*:\/\/)|(www\.))/.source +
|
|
15
|
+
'(' +
|
|
16
|
+
// Hostname and tld
|
|
17
|
+
/([\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF.-]+\.[a-zA-Z]{2,63})/.source +
|
|
18
|
+
'|' +
|
|
19
|
+
'localhost' +
|
|
20
|
+
'|' +
|
|
21
|
+
// IPv4 address
|
|
22
|
+
ipRegex.v4().source +
|
|
23
|
+
'|' +
|
|
24
|
+
// IPv6 address
|
|
25
|
+
'(\\[?' +
|
|
26
|
+
ipRegex.v6().source +
|
|
27
|
+
'\\]?)' +
|
|
28
|
+
')' +
|
|
29
|
+
// Optional port..
|
|
30
|
+
/(:[0-9]+)?/.source +
|
|
31
|
+
// Optional path..
|
|
32
|
+
/(\/[\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!:.?$'()[\]*,;~+=&%@!\-/]*)?/.source +
|
|
33
|
+
// Optional fragment
|
|
34
|
+
/(#.*)?/.source,
|
|
35
|
+
'i'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const channelRegex = /(^|\s)([@+~&%}]*)([#&][^ ,\007<>\n\r]+?)([:;.,<>\n\r]+)?$/i;
|
|
39
|
+
|
|
40
|
+
export function linkifyChannels(word) {
|
|
41
|
+
// "@#kiwiirc," = 3 parts. (prefix=@)(channel=#kiwiirc)(suffix=,)
|
|
42
|
+
return word.replace(channelRegex, (match, mLead, mPrefix, mChannel, mSuffix) => {
|
|
43
|
+
let chan = _.escape(mChannel.trim());
|
|
44
|
+
let lead = _.escape(mLead);
|
|
45
|
+
let prefix = _.escape(mPrefix);
|
|
46
|
+
let suffix = _.escape(mSuffix);
|
|
47
|
+
|
|
48
|
+
let link = `<a class="u-link kiwi-channel" data-channel-name="${chan}">${chan}</a>`;
|
|
49
|
+
return `${lead}${prefix}${link}${suffix}`;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert a nickname string to a colour code
|
|
55
|
+
* Uses these properties from the CSS theme file:
|
|
56
|
+
* --kiwi-nickcolour-count: 200; - number of available nick colours
|
|
57
|
+
* --kiwi-nickcolour-hueoffset: 0; - moves the hue value by hueoffset amounts
|
|
58
|
+
* --kiwi-nickcolour-saturation: 70; - the nick saturation in HSL value
|
|
59
|
+
* --kiwi-nickcolour-lightness: 40; - the nick lightness in HSL value
|
|
60
|
+
*/
|
|
61
|
+
export function createNickColour(nick) {
|
|
62
|
+
let themeMngr = ThemeManager.instance();
|
|
63
|
+
let nickLower = (nick || '').toLowerCase();
|
|
64
|
+
let nickNum = murmurhash3.x86.hash32(nickLower);
|
|
65
|
+
|
|
66
|
+
let bucketSize = toInt(themeMngr.themeVar('nickcolour-count')) || 200;
|
|
67
|
+
let hueOffset = toInt(themeMngr.themeVar('nickcolour-hueoffset'));
|
|
68
|
+
let hsl = {
|
|
69
|
+
h: mapRange(Math.abs(nickNum) % bucketSize, 0, bucketSize, 0, 360) + hueOffset,
|
|
70
|
+
s: toInt(themeMngr.themeVar('nickcolour-saturation')) || 70,
|
|
71
|
+
l: toInt(themeMngr.themeVar('nickcolour-lightness')) || 40,
|
|
72
|
+
};
|
|
73
|
+
return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse a string to an int, where NaN is 0
|
|
78
|
+
* @param {string} inp
|
|
79
|
+
*/
|
|
80
|
+
export function toInt(inp) {
|
|
81
|
+
let int = parseInt(inp, 10);
|
|
82
|
+
if (Number.isNaN(int)) {
|
|
83
|
+
int = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return int;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extract a substring from a hex string and parse it as an integer
|
|
91
|
+
* @param {string} hash - Source hex string
|
|
92
|
+
* @param {number} index - Start index of substring
|
|
93
|
+
* @param {number} [length] - Length of substring. Defaults to 1.
|
|
94
|
+
*/
|
|
95
|
+
export function hexVal(hash, index, len) {
|
|
96
|
+
return parseInt(hash.substr(index, len || 1), 16);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* Re-maps a number from one range to another
|
|
101
|
+
* http://processing.org/reference/map_.html
|
|
102
|
+
*/
|
|
103
|
+
export function mapRange(value, vMin, vMax, dMin, dMax) {
|
|
104
|
+
let vValue = parseFloat(value);
|
|
105
|
+
let vRange = vMax - vMin;
|
|
106
|
+
let dRange = dMax - dMin;
|
|
107
|
+
|
|
108
|
+
return (vValue - vMin) * dRange / vRange + dMin;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Format a string according to the configured textFormats, including a
|
|
113
|
+
* translation for the %text formatting variable
|
|
114
|
+
*/
|
|
115
|
+
export function formatAndT(formatId, formatParams, localeId, localeParams) {
|
|
116
|
+
let body = t(localeId, localeParams);
|
|
117
|
+
if (formatParams) {
|
|
118
|
+
formatParams.text = body;
|
|
119
|
+
body = formatText(formatId, formatParams);
|
|
120
|
+
} else {
|
|
121
|
+
body = formatText(formatId, { text: body });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return body;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a user reference string similar to 'nick'
|
|
129
|
+
*/
|
|
130
|
+
export function formatUser(fNick) {
|
|
131
|
+
let nick = fNick;
|
|
132
|
+
|
|
133
|
+
// Allow passing of a user object or irc-framework event
|
|
134
|
+
if (typeof nick === 'object') {
|
|
135
|
+
nick = nick.nick;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return formatText('user', { nick });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a full user reference similar to 'nick (user@host)'
|
|
143
|
+
*/
|
|
144
|
+
export function formatUserFull(fNick, fUsername, fHost) {
|
|
145
|
+
let nick = '';
|
|
146
|
+
let username = '';
|
|
147
|
+
let host = '';
|
|
148
|
+
|
|
149
|
+
// Allow passing of a user object or irc-framework event
|
|
150
|
+
if (typeof fNick === 'object') {
|
|
151
|
+
let user = fNick;
|
|
152
|
+
nick = user.nick;
|
|
153
|
+
username = user.username || user.ident;
|
|
154
|
+
host = user.hostname || user.host;
|
|
155
|
+
} else {
|
|
156
|
+
nick = fNick;
|
|
157
|
+
username = fUsername;
|
|
158
|
+
host = fHost;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return formatText('user_full', { nick, username, host });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Format a string according to the configured textFormats
|
|
166
|
+
*/
|
|
167
|
+
export function formatText(formatId, formatParams) {
|
|
168
|
+
let format = getState().setting('textFormats.' + formatId);
|
|
169
|
+
let params = formatParams;
|
|
170
|
+
|
|
171
|
+
// Most texts only have a 'text' variable so allow passing of a string for this
|
|
172
|
+
// variable as shorthand.
|
|
173
|
+
if (typeof params === 'string') {
|
|
174
|
+
params = { text: params };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Expand a user mask into its individual parts (nick, ident, hostname)
|
|
178
|
+
if (params.user) {
|
|
179
|
+
params.nick = params.user.nick || '';
|
|
180
|
+
params.username = params.user.username || '';
|
|
181
|
+
params.host = params.user.hostname || '';
|
|
182
|
+
params.prefix = params.user.prefix || '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Do the magic. Use the %shorthand syntax to produce output.
|
|
186
|
+
let result = format.replace(/%([A-Z]{2,})/ig, (match, key) => {
|
|
187
|
+
let ret = '';
|
|
188
|
+
if (typeof params[key] !== 'undefined') {
|
|
189
|
+
ret = params[key];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return ret;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Convert a given duration in seconds to human readable weeks,days,hours,minutes,seconds
|
|
199
|
+
// only showing the duration parts that are used eg 3666 --> 1 hour, 1 minute, 6 seconds
|
|
200
|
+
export function formatDuration(timeSeconds) {
|
|
201
|
+
let seconds = timeSeconds;
|
|
202
|
+
|
|
203
|
+
const weeks = Math.floor(seconds / (3600 * 24 * 7));
|
|
204
|
+
seconds -= weeks * 3600 * 24 * 7;
|
|
205
|
+
|
|
206
|
+
const days = Math.floor(seconds / (3600 * 24));
|
|
207
|
+
seconds -= days * 3600 * 24;
|
|
208
|
+
|
|
209
|
+
const hours = Math.floor(seconds / 3600);
|
|
210
|
+
seconds -= hours * 3600;
|
|
211
|
+
|
|
212
|
+
const minutes = Math.floor(seconds / 60);
|
|
213
|
+
seconds -= minutes * 60;
|
|
214
|
+
|
|
215
|
+
const tmp = [];
|
|
216
|
+
(weeks) && tmp.push(t('week', { count: weeks }));
|
|
217
|
+
(weeks || days) && tmp.push(t('day', { count: days }));
|
|
218
|
+
(days || hours) && tmp.push(t('hour', { count: hours }));
|
|
219
|
+
(days || hours || minutes) && tmp.push(t('minute', { count: minutes }));
|
|
220
|
+
tmp.push(t('second', { count: seconds }));
|
|
221
|
+
|
|
222
|
+
return tmp.join(' ');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function formatNumber(num) {
|
|
226
|
+
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function t(key, options) {
|
|
230
|
+
let val = i18next.t(key, options);
|
|
231
|
+
if (!val) {
|
|
232
|
+
let opts = options || {};
|
|
233
|
+
opts.lng = 'en-us';
|
|
234
|
+
val = i18next.t(key, opts);
|
|
235
|
+
}
|
|
236
|
+
return val;
|
|
237
|
+
}
|